Skip to content

Instantly share code, notes, and snippets.

@patricksavalle
Last active March 1, 2026 17:43
Show Gist options
  • Select an option

  • Save patricksavalle/e61455e03be546a451d608f38eaf340c to your computer and use it in GitHub Desktop.

Select an option

Save patricksavalle/e61455e03be546a451d608f38eaf340c to your computer and use it in GitHub Desktop.
name description
coding-standard
Enforces the project's JavaScript coding standard and UI architecture rules. Use this skill when writing any JavaScript, HTML, or CSS code to ensure no inline scripts, no inline styles, and proper Web Component patterns are followed.

L-GEVITY Coding Standard

Modern JavaScript coding standard for reliability and maintainability, plus UI architecture rules for this project.

For architectural principles (SOLID, SoC, platform independence, dependency management), see architecture-guidelines. For ESLint fix procedures, see eslint-fix-protocol.

When to use this skill

  • When writing any JavaScript, HTML, or CSS code
  • When creating or modifying components or pages

Build System Rules (Eleventy)

Build-Time Processing (Critical)

For static site generation, always prefer build-time over runtime processing.

Rules:

  • Generate at build, not at runtime — transform Markdown, process data, and generate HTML during build. Never fetch/parse content client-side.
  • Static template values over dynamic arguments — resolve template variables at build time. Avoid passing runtime configuration that could be determined during build.
  • Leverage Eleventy's capabilities first — use collections, data files, and filters before writing custom build scripts.

Examples:

Scenario ❌ Runtime ✅ Build-time
Displaying changelog Fetch CHANGELOG.md client-side, parse with JS Copy CHANGELOG.md to articles/ with frontmatter, let Eleventy generate page
Template configuration Pass dynamic site name to templates at runtime Use SITE env var to select correct layout during build
Content transformation Parse Markdown in browser with library Let Eleventy process .md files to HTML
Data formatting Format dates/numbers client-side Use Eleventy filters in templates

JavaScript Coding Standard

1. Variables and Declaration

  • const by default. Only use let for reassignment. Never use var.
  • No globals. Encapsulate in modules or closures.
  • Literals over constructors: {}, [] — not new Object().
  • Prefer standard APIs (EventTarget, URLSearchParams, URL, Intl) before writing custom utilities.

2. Naming Conventions

Convention Use for Example
camelCase Variables, functions, methods userProfile, calculateHeartRate
PascalCase Classes, constructors HealthMonitor, AndromanBiometryLog
UPPER_SNAKE_CASE Constants MAX_RETRY_ATTEMPTS, API_BASE_URL
is/has/can prefix Booleans isVisible, hasAccess
on prefix Event handlers onSubmit, onDownloadClick
  • No Magic Strings: Move user-facing display text to const declarations at the top of the file. Technical HTML strings (selectors, tag names) may remain inline unless heavily repeated.

3. Comparisons and Types

  • Strict equality only: === and !==. Never ==/!=.
  • Explicit coercion: String(), Number() — not implicit.

4. Functions and Control Flow

  • Arrow functions for callbacks and anonymous functions.
  • async/await over raw .then()/.catch().
  • for...of or array methods (.map(), .filter(), .reduce()) for iteration.

4.1 Async/Await Discipline (Critical)

Rules:

  • Always await calls to async functions (no floating promises).
  • If using await, the enclosing function MUST be async.
  • Propagate async up the entire call chain.
  • Never mix await with .then() in the same function.
// ❌ Floating promise — bug!
async function bad() {
    saveMetric('key', 'value'); // Not awaited!
    console.log('Saved!'); // Runs BEFORE save completes!
}

// ✅ Always await
async function good() {
    await saveMetric('key', 'value');
    console.log('Saved!');
}

Refactoring async code:

  1. If a function is async, keep it async unless ALL async operations removed.
  2. Never remove await without confirming the callee is now synchronous.
  3. Search all call sites when changing async signatures.

4.2 UI/Data Synchronization (Critical)

  • Data First, UI Second: Never update the DOM until the data operation has fully completed (await).

  • No Optimistic UI unless explicitly required.

  • Async event handlers: Declare async, await internally, use isLoading flags to prevent re-entry.

  • Halt on Terminal Side Effects: If an async operation triggers a reload or redirect, the current execution thread MUST halt. See architecture-guidelines Section 8.

// ✅ Correct sequencing
async function handleDateChange(newDate) {
    showLoadingSpinner();
    try {
        await recalculateBiometrics(newDate);
        updateDashboard();
    } finally {
        hideLoadingSpinner();
    }
}

5. Security and DOM

  • No innerHTML with user-generated content. Use textContent.
  • Sanitize all external inputs at entry points.

6. Error Handling

  • No silent failures: Never use empty catch blocks.
  • Always log: At minimum console.error or console.warn.
  • Report critical errors: Re-throw or emit via global event (e.g., persistence-error).
  • Safe defaults: If returning a fallback on error, log the reason.
// ❌ NEVER
try {
    executeCalculation();
} catch (e) {}

// ✅ At minimum
try {
    executeCalculation();
} catch (e) {
    console.warn('Calculation failed, using fallback:', e);
    return FALLBACK_VALUE;
}

7. Dependency & CDN Management

  • Vendor CDNs for SDKs (e.g., js.monitor.azure.com).
  • Stable versions only: Never @nightly, @beta, or unversioned links.
  • Minimize dependencies: Prefer Bootstrap 5 / standard Web APIs first.
  • Preload critical assets: <link rel="preload"> for core CSS.

UI Architecture Rules

🚫 Absolute Prohibitions

No Inline JavaScript — always external files:

<!-- ❌ NEVER -->
<script>
    document.addEventListener('DOMContentLoaded', () => { ... });
</script>

<!-- ✅ ALWAYS -->
<script type="module" src="/js/pages/my-page.js"></script>

No Inline CSS — no <style> tags, no style="" attributes:

<!-- ❌ -->
<div style="margin: 10px;">...</div>
<!-- ✅ -->
<div class="m-3 text-danger">...</div>

CSS Rules (Critical)

Components either have no custom CSS or use Shadow DOM with fully encapsulated CSS.

  • Default (no Shadow DOM): All styling via Bootstrap 5 utility classes. No custom CSS files for the component.
  • Shadow DOM components: When a component uses <template shadowrootmode="open">, it may include its own CSS file loaded inside the shadow root. The CSS is fully encapsulated and does not leak.
  • Prefer standard Bootstrap utilities (e.g., border-5) before adding classes to inline-utilities.css.
  • Only use inline-utilities.css when no reasonable Bootstrap alternative exists.

Semantic HTML & Accessibility

  • Semantic elements over <div>: Use <article>, <section>, <nav>, <main>, <aside>, <header>, <footer> where they convey meaning.
  • Heading hierarchy: One <h1> per page, then <h2><h3> in order. Never skip levels.
  • Form labels: Every <input> must have a <label for="..."> or aria-label.
  • Alt text: All <img> elements must have descriptive alt attributes (or alt="" for decorative images).
  • ARIA: Use native HTML semantics first. Only add aria-* attributes when no native element conveys the role (e.g., custom widgets).
  • Keyboard navigability: Interactive elements must be focusable and operable via keyboard. Use <button> for actions, <a> for navigation — never <div onclick>.

UI & Form Standards

1. Input Types for Formatted Values

  • Use type="text" for inputs that display non-numeric characters (e.g., arrows , , units %, prefixes p).
  • Avoid type="number" for derived fields, as browsers strictly enforce numeric values and will strip or reject formatted strings.
  • Read-only computed fields: Always use readonly and tabindex="-1" to prevent user interaction.

2. Table Layouts

  • Explicit Column Widths: Use percentage widths (e.g., width="15%") for data columns to ensure consistent alignment across multiple tables.
  • No width="1%": Avoid the "shrink to fit" hack (width="1%") for data columns, as it causes layout instability when content varies.
  • Compact Inputs: Use form-control-sm inside tables to maximize data density.
  • Prevent Wrapping: Use text-nowrap on headers and unit columns to maintain single-line vertical rhythm.
  • Visual Feedback: Calculated fields should default to a "safe" or "neutral" visual state (e.g., green/success) when empty or not yet calculated, rather than looking broken.

Component Structure (Web Components)

This project uses three component patterns. Build-time components are preferred because the HTML is visible immediately — before JavaScript runs.

Type Location When to use
Build-time (preferred) packages/shared-ui/_includes/components/{name}/ Default — static HTML structure rendered at build time, JS enhances it
Build-time (parameterized) Same, with .html.js shortcode file Multi-instance with identical structure but different config (e.g., tier)
Runtime-only / base class packages/shared-ui/js/components/{name}/ Base classes, or components with zero meaningful static HTML

Which pattern to use:

  1. Does the component have static HTML structure (tables, headings, wrappers)? → Yes: Build-time component. HTML lives in the DOM, JS enhances it. → No: Runtime-only component (JS creates all DOM).
  2. Is the build-time component used multiple times on the same page with different config but identical structure? → Yes: Use a parameterized component (.html.js shortcode). → No: Use a regular single-instance build-time component.

Build-Time Components (Preferred)

The component's static structure lives as real HTML inside the custom element tag — visible immediately on page load. JavaScript only enhances it (populates data, adds interactivity).

my-component/
├── my-component.html   # Static HTML inside custom element tag + script
└── my-component.js     # Web Component class

Component HTML Pattern

<!-- Static structure is REAL DOM, not hidden in a <template> -->
<androman-my-component>
    <table class="table table-sm">
        <thead>
            <tr>
                <th>Name</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>

    <!-- Row template for repeating elements only -->
    <template id="my-row-template">
        <tr>
            <td data-field="name"></td>
            <td data-field="value"></td>
        </tr>
    </template>

    <script
        type="module"
        src="/components/my-component/my-component.js"
    ></script>
</androman-my-component>

[!IMPORTANT] The <template> tag is only for repeating elements (e.g., table rows cloned per data item). The component's static structure (table headers, wrappers, headings) must be real DOM — not hidden inside a <template>.

Component JS Pattern

  • Private fields/methods: Use # prefix for all non-public members.
  • No DOMContentLoaded: Use connectedCallback() for initialization.
  • Encapsulation: Only access own DOM via this.querySelector().
export class AndromanMyComponent extends HTMLElement {
    #data = null;

    connectedCallback() {
        this.#initialize();
    }

    disconnectedCallback() {
        // Cleanup
    }

    #initialize() {
        // Internal implementation
    }
}

customElements.define('androman-my-component', AndromanMyComponent);

Including Components (Nunjucks)

Single-instance components — include once:

{% include "components/my-component/my-component.html" %}

Build-Time Components (Parameterized / Multi-Instance)

When a component is used multiple times with different configuration but identical structure, use a .html.js shortcode file to avoid duplicating HTML.

my-tier-table/
├── my-tier-table.html      # Shared row templates + script tag
├── my-tier-table.html.js   # Build-time HTML generator (default export)
└── my-tier-table.js        # Web Component class

Shortcode File (.html.js)

The .html.js extension indicates a JS file that returns an HTML string. Uses a default export function that takes parameters and returns the component's static HTML via a template literal.

// my-tier-table.html.js
export default function (tier) {
    return `<androman-my-tier-table tier="${tier}">
    <div class="table-responsive">
        <table class="table table-sm small">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Value</th>
                </tr>
            </thead>
            <tbody></tbody>
        </table>
    </div>
</androman-my-tier-table>`;
}

[!NOTE] The "no HTML in JS strings" rule does not apply to .html.js files. These are build-time generators — their output becomes static HTML in the built page, equivalent to Nunjucks templates.

Registration in Eleventy Config

// eleventy.config.js
const myTierTable = (
    await import('./shared-ui/_includes/components/my-tier-table/my-tier-table.html.js')
).default;
eleventyConfig.addShortcode('myTierTable', myTierTable);

Usage in Templates

{%- comment -%} Include shared templates + script once {%- endcomment -%}
{% include "components/my-tier-table/my-tier-table.html" %}

{%- comment -%} Instances via shortcode — build-time rendered {%- endcomment -%}
{% myTierTable "1" %}
{% myTierTable "2" %}
{% myTierTable "3" %}

Component HTML File (Shared Resources Only)

For parameterized components, the .html file contains only the shared resources that all instances need — row templates and the script tag:

<!-- Shared row template for all instances -->
<template id="my-tier-row-template">
    <tr>
        <td data-field="name"></td>
        <td data-field="value"></td>
    </tr>
</template>

<script type="module" src="/components/my-tier-table/my-tier-table.js"></script>

Runtime-Only Components

When a component depends entirely on runtime data and has no meaningful static HTML, it lives in packages/shared-ui/js/components/{component-name}/:

my-component/
└── my-component.js     # Web Component class (extends HTMLElement)

These components generate their entire DOM programmatically in connectedCallback(). They follow the same JS patterns (private fields, androman- prefix, encapsulation) as build-time components.

Use runtime-only components when:

  • The component's content is fully determined by runtime data (e.g., user session, API responses)
  • There is no useful static HTML to render at build time
  • The component serves as a base class for other components

Existing base classes:

Base class Purpose Subclasses
save-form Form lifecycle, state sync, calculation, auto-save (5 layers) Calculator components
cognitive-test-base Test screen management, fullscreen, result saving Cognitive test components
chart-base Chart.js setup, BiometricsUpdatedEvent, score-to-color Radar chart components
data-table-base Build-time table structure + runtime row population Data table components

[!IMPORTANT] Prefer build-time components. Only use runtime-only components when the page cannot render any meaningful HTML for the component at build time. Build-time components provide faster perceived load times and better SEO because the HTML is already in the page before JavaScript executes.

Naming Conventions

  • Custom elements: Prefix androman- (e.g., <androman-biometry-log>)
  • Classes: Prefix Androman in PascalCase (e.g., AndromanBiometryLog)

Encapsulation (Critical)

Components interact only with their own internal DOM or data passed via attributes/properties/imports:

// ❌ Reaching outside
document.querySelector('#some-external-element').value = 'test';

// ✅ Own content only
this.querySelector('.my-internal-element').textContent = 'test';

Template Elements

No HTML in JS strings (except .html.js shortcode files). Use <template> elements for repeating content that must be cloned at runtime.

<template> is for repeating elements only — content that is cloned N times from data (e.g., table rows, list items). Static structure (table headers, wrappers) belongs in the real DOM.

<!-- ✅ Static structure in DOM, only rows as <template> -->
<androman-my-table>
    <table class="table">
        <thead>
            <tr>
                <th>Name</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>

    <template id="my-row-template">
        <tr>
            <td data-field="name"></td>
            <td data-field="value"></td>
        </tr>
    </template>
</androman-my-table>
// In connectedCallback: find existing tbody, clone row templates, append
const tbody = this.querySelector('tbody');
const rowTemplate = this.querySelector('#my-row-template');

data.forEach(item => {
    const row = rowTemplate.content.cloneNode(true);
    row.querySelector('[data-field="name"]').textContent = item.name;
    tbody.appendChild(row);
});

Services

Shared JS modules in packages/shared-ui/js/ (e.g., auth.js, storage-service.js).

Services must be classes with # private methods, exported as singletons:

class AuthHelper {
    #user = null;

    async getUserInfo() {
        /* public API */
    }
    #validateToken() {
        /* private */
    }
}

const auth = new AuthHelper();
export { AuthHelper, auth };

Service Encapsulation (Critical)

Services have NO knowledge of the DOM:

  • ❌ No document.querySelector(), no DOM manipulation
  • ✅ Only manage data, state, and API calls
  • ✅ Return data — let components handle UI
  • ✅ Environment-agnostic (no direct browser globals in core logic)

Imports

Always use absolute paths starting with /js/:

// ❌ import { auth } from '../auth.js';
// ✅ import { auth } from '/js/auth.js';

File Move / Refactoring Protocol (Critical)

When moving or renaming JS files:

  1. Before moving: grep -r "from '/js/old/path" --include="*.js" packages/ to find all import references.
  2. Update ALL imports to the new path.
  3. After moving: Re-run the grep — should return no results.
  4. Run build (npm run build) and verify no 404s in browser console.

Import references may exist in: components (components/*/), page scripts (js/pages/), services (js/), and calculators.


Documentation (JSDoc)

Every class and public function must have JSDoc:

/**
 * Calculate the user's biological age based on biomarkers.
 * @param {Object} biomarkers - The biomarker data
 * @param {number} biomarkers.vo2max - VO2 max value
 * @returns {number} Calculated biological age
 */
function calculateBiologicalAge(biomarkers) { ... }

File Location Reference

Content Type Location
Site pages packages/{site-name}/
Component folder (2 files) packages/shared-ui/_includes/components/{name}/
Runtime-only components packages/shared-ui/js/components/{name}/
Global/shared CSS packages/shared-ui/css/
Page-specific CSS packages/shared-ui/css/pages/
Shared JS modules / Services packages/shared-ui/js/
Page-specific JS packages/shared-ui/js/pages/

Creating New Pages

  1. Create HTML in the appropriate site package
  2. Create page CSS in packages/shared-ui/css/pages/{page-name}.css
  3. Create page JS in packages/shared-ui/js/pages/{page-name}.js
  4. Reference external files with <link> and <script type="module" src="...">

ESLint Enforcement

All coding standard rules are enforced by ESLint. See eslint-fix-protocol skill for fix procedures.

Key enforced rules:

Rule What It Checks
eqeqeq Must use ===/!==
no-var Must use const/let
prefer-const Use const when never reassigned
curly Braces required for multi-line blocks
no-unused-vars No unused vars (prefix _ if intentional)
no-unsanitized/property No unsafe innerHTML

Disabling rules (last resort) — always include justification:

// eslint-disable-next-line no-unsanitized/property -- trusted static HTML
element.innerHTML = templateContent;

TypeScript Rules (API Package)

The packages/api/ directory uses TypeScript with stricter rules.

No any type (@typescript-eslint/no-explicit-any = error):

// ❌ function processData(data: any): any { ... }

// ✅ Use proper types
interface DataPayload {
    value: string;
    timestamp: number;
}
function processData(data: DataPayload): string {
    return data.value;
}

// ✅ For unknown data, use 'unknown' + type guards
function handleUnknown(data: unknown): string {
    if (typeof data === 'object' && data !== null && 'value' in data) {
        return String((data as { value: unknown }).value);
    }
    throw new Error('Invalid data format');
}

Type alternatives to any:

Instead of any Use
Unknown JSON response unknown + type guards
Dynamic keys Record<string, ValueType>
Accepts anything Generic <T>
Mixed array Array<string | number> (union)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment