Skip to content

Instantly share code, notes, and snippets.

@patricksavalle
Last active February 23, 2026 15:31
Show Gist options
  • Select an option

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

Select an option

Save patricksavalle/03a97b9ec56ecc8d4505a1a683e5e07c to your computer and use it in GitHub Desktop.
name description
technical-design
Documents the implemented design patterns, data flow architecture, and component structure of this specific application. Reference this skill to understand how modules are wired together and which patterns are in use.

L-GEVITY Technical Design

This skill documents the application-specific implemented design patterns, data flows, and component structures of this project.

Scope Restriction:

  • For general architectural principles (SOLID, coupling, complexity), see architectural-discipline.
  • For domain-specific calculator/biomarker rules, see longevity-engine-protocol.

When to use this skill

  • When adding a new module or component and need to follow existing patterns
  • When tracing data flow through the application
  • When understanding how services and calculators are wired together
  • When trying to decide whether to use a runtime script or a build-time hook

1. Technical Stack & Constraints

  • Static Site Generation (Eleventy): Execute heavy processing at build-time. Parse content, correlations, and collections into static HTML. Runtime JavaScript is strictly for dynamic user interaction and personalization.
  • Vanilla Web Technologies: Use raw HTML, CSS, and Vanilla JavaScript (ES Modules). Heavy UI frameworks (React, Vue, Angular) are strictly prohibited.
  • Web Components: Encapsulate UI in custom elements (e.g., <androman-auth-status>), leveraging native connectedCallback and disconnectedCallback lifecycles.
  • Environment-Agnostic Core: The calculation engines and pipeline (biometric-engine) MUST NOT depend on browser globals (window, document, localStorage, IndexedDB).
  • Platform Coupling Boundaries: Only the top-level orchestration layer (app.js, event-bridge.js) and explicit UI/storage wrappers (storage-service.js) may interface with the DOM or browser APIs.

2. Decentralized Architecture

Decentralize when possible: Prefer client-side processing, relying on servers only for operations that inherently demand centralization.

Client-side by default:

  • Calculations, validation, data processing.
  • Persistent storage (IndexedDB) for the user's isolated data.
  • CDN-delivered static assets (HTML/JS/CSS).

Server-side when required:

  • Aggregating anonymized cross-user analytics.
  • Multi-device syncing.
  • OAuth Authentication.

3. Security & Privacy Architecture

  • Pseudonymous by Default: Store and transmit only pseudonymous data. Personally Identifiable Information (PII) must be kept out of calculation models and local storage mechanisms.
  • Client-Side Encryption at Rest: Sensitive domain objects (biomarkers, risk scores) must be locally encrypted before persisting to IndexedDB. Unencrypted data exists only in memory (Session Cache/Pipeline) during runtime.
  • Explicit Trust Boundaries: Data crossing between the in-memory domains and persistent storage layers must explicitly pass through encryption/decryption routines in the storage service.

4. Implemented Design Patterns

Pattern Where Example
Singleton Services and engines export a single shared instance export const biometricEngine = new BiometricEngine()
Facade BiometricEngine hides pipeline/calculator complexity behind one entry point biometric-engine.js
Enrichment Pipeline Processors sequentially enrich a shared mutable context via CALCULATOR_REGISTRY pipeline.jsrunCalculationPipeline()
Registry / Config-Driven Domain logic (HR tables, biomarker configs) as declarative data objects calculators/*-config.js, *-hr.js
Strategy Calculators are interchangeable modules with uniform (accumulator) → void interface Each *-calculator.js
Observer / EventTarget Services extend EventTarget; components listen via window events CalculationPipeline, event-bridge.js
Bridge event-bridge.js forwards service events to window for UI decoupling bridgeToWindow()
Template Method Base classes define hooks that subclasses override save-form.js, data-table-base.js
Layered Inheritance Form logic split into Lifecycle → EventState → CalcVisual layers AndromanLifecycleFormAndromanEventStateFormAndromanCalcVisualForm
Web Components UI as decoupled custom elements wrapping static HTML AndromanCohortSelector
11ty Component Pattern Folders containing .html (markup) and .js (Web Component logic) components/cohort-selector/
Composition Root orchestration modules prefixed with app- configure services and delegate app.jsapp-auth.js, app-bridge.js

4.1 Logical Application Layers

Our client-side architecture enforces strict horizontal layering. With the exception of the Orchestration layer (which wires the system together), no layer may bypass the layer directly beneath it. Crucially, no layer may ever reference a layer above it in the hierarchy.

  1. Orchestration Layer (app-*): The Composition Root. Bootstraps the application, configures dependencies, instantiates Singletons, and bridges Service events to the window.
  2. Presentation Layer (Web Components): 11ty-generated static HTML and "dumb" vanilla JS Custom Elements. They only dispatch DOM events or listen to globally bridged state events. They may not import Services or the Engine directly.
  3. Service Layer (*-service.js): Coordinates side effects and I/O (Storage, API calls, Auth). Must not assume anything about the DOM. Communicates with Presentation purely by dispatching typed events.
  4. Domain Layer (biometric-engine): Pure functional core. Processes inputs and returns computed results. Must be 100% environment-agnostic (no window, no localStorage, no I/O) and strictly deterministic.

4.2 Hybrid 11ty + Web Component Architecture

We encapsulate UI features using a specific directory-based pattern that marries Eleventy's build-time static generation with standard Web Components.

Directory Structure (_includes/components/{name}/):

  1. {name}.html: The Eleventy macro/include. This file defines the custom element tag (e.g., <androman-cohort-selector>), the inner static HTML structure, and explicitly loads the associated module (<script type="module" src="/components/{name}/{name}.js"></script>).
  2. {name}.js: The ES Module defining the Web Component class (class Androman... extends HTMLElement).

Rules for Components:

  • Zero Runtime Rendering: Components must rely on the inner HTML generated by Eleventy at build time. They must not use innerHTML or string templates to render their core UI at runtime.
  • Progressive Enhancement: The component's connectedCallback hooks into the existing server-rendered DOM nodes (this.querySelector).
  • Encapsulated Scope: All DOM manipulation logic must be contained entirely within the component class. No global scripts should query inside a component's tags.

Component ↔ Service Decoupling

Components MUST NOT import services directly to listen to events. The event flow is:

  1. Services dispatch custom events on their own EventTarget.
  2. app-bridge.js forwards these to the window object using typed classes from app-events.js.
  3. Components listen on the window using the static NAME property (e.g., window.addEventListener(CalculationStartEvent.NAME, ...)).

5. Enrichment Pipeline Architecture

The system transforms raw inputs via a shared mutable context passed through a declarative chain of idempotent processors.

Input → [Accumulator] → Processor₁ → Processor₂ → ... → Processorₙ → Output
              ↑____________↑____________↑
              (all read/write same accumulator)
  • Accumulator: A sealed mutable object containing biometrics (the shared R/W pool) and predefined flat result collections.
  • Uniform Strategy Interface: Processors must strictly conform to (accumulator) → void. They read from accumulator.biometrics, perform logic, and write to result layers or back to biometrics.
  • Isolation: Processors must never reference each other, only the context. Order is declared via a registry, not hardcoded execution paths.

Uniform Processor Interface (Strategy):

All processors share a single plug-and-play contract: (accumulator) → void. A processor reads what it needs from accumulator.biometrics, performs its computation, and writes results to the named flat collections (riskReductions, scores, etc.) and/or back to accumulator.biometrics for downstream processors to consume. This uniform interface means any processor can be added, removed, or reordered in the registry without changing the pipeline or other processors — the accumulator is the only coupling point.


6. Data Flow & Storage Models

6.1 Session Cache vs Storage Layer

  • Session Cache: This memory layer holds all transient and derived data. The UI must exclusively consume from this cache.
  • Storage Service (IndexedDB): Handles persistence filtering. Derived/computed metrics must be filtered out of storage, but never filtered out of the active Session Cache.
  • State Segregation: Never arbitrarily trim in-memory domain structures just because the persistence layer doesn't require saving every field.
  • Data Structure Ownership: A module that produces a data structure defines how it serializes itself. Consumers must not duplicate or assume internal structures.

6.2 Pipeline Output

The pipeline strictly returns results as a flattened map of pre-defined collections enforced via Object.seal().

const results = biometricEngine.execute(metrics);
// results = { metrics: {...}, riskReductions: {...}, percentiles: {...} }

6.3 Async Initialization

  • Idempotency: Application singletons use private promises (#initPromise) to prevent race conditions or duplicate boots.
  • Redirect Halts: If init() triggers a navigation event (reload()), it MUST return true. Callers must conditionally halt: if (await service.init()) return;.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment