Skip to content

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fladapt Logo

An explainable, deterministic adaptive layout engine that transforms a single content specification into optimized layouts for multiple surfaces — mobile, social, kiosks, digital billboards, and broadcast displays.

TypeScript React Vite Node.js Vercel Claude Code OpenCode


🎯 Overview

Modern content (broadcast lower-thirds, kiosks, mobile apps) must present the same logical information across radically different physical surfaces — different aspect ratios, resolutions, orientations, and safe-area constraints. Traditional responsive design solves this with hardcoded breakpoints and CSS media queries: a fixed layout per known screen size. This does not generalize to unseen surfaces, and it hides the actual decision logic inside CSS, making layout choices unexplainable.

image

  • 🎯 Surface-agnostic resolution — One layout spec resolves correctly across mobile portrait, mobile landscape, broadcast lower-third, and square kiosk out of the box
  • 🔍 Explainable — Every layout decision (position, size, hide/show, degradation) carries a human-readable trace explaining exactly why it occurred
  • 🧩 Graceful degradation — Lower-priority elements shrink, reflow, or hide before higher-priority elements are affected
  • 📐 Framework-agnostic core — Pure TypeScript resolver with zero DOM/React dependency; React only for painting
  • 🚀 Unseen surface support — Any new surface profile (smartwatch, print, etc.) flows through the identical code path without code changes

Not just a CSS media query system. Fladapt uses a priority-ordered greedy allocator over a general constraint solver — preferring full explainability over mathematical generality. Every outcome can be traced to one priority comparison and one space check.


✨ Key Features

Feature Description
🎯 Priority-Ordered Allocation Elements processed from highest to lowest priority; each gets the best size it can given what's left — a single linear pass
📐 Geometry-Driven Classification Orientation and size tier inferred from width, height, orientation alone — no if (surfaceId === "kiosk") branches
📉 Degradation Ladder Each element has an authored fallback sequence: shrink → reflow → truncate → hide — first step that fits wins
🔗 Effective Canvas Computation Surface bounds minus safeArea/bleed insets computed before any allocation — both compose additively
📝 Inline Trace Generation Every decision appends a trace entry at the point of making it — the explanation can never drift from actual behavior
🖼️ React + CSS Grid Renderer Pure projection layer — takes ResolvedLayout and paints it with CSS Grid/Flexbox, no layout logic
⚡ Sub-5ms Resolution Full resolution of a ~20-element spec across a surface in under 5ms — no async/network calls
🧪 4 Built-in Surfaces Mobile portrait, mobile landscape, broadcast lower-third, square kiosk — all working out of the box

🛠️ Tech Stack

Layer Technologies
Backend (Core) TypeScript (strict mode), zero DOM/React dependency
Frontend (Demo) React 18, TypeScript, CSS Grid/Flexbox
Build Tooling Vite (fast dev server, TS + React support)
Testing Vitest (unit tests for the resolver)
Deployment Vercel / Netlify / GitHub Pages (static build)
Containerization Docker Compose (optional)

🏗️ Architecture

Fladapt System Architecture

Fladapt is split into two cleanly separated layers:

Resolution Core — Pure TypeScript, no rendering concerns. Converts (LayoutSpec, SurfaceProfile) → ResolvedLayout.

Presentation Layer — React components that take a ResolvedLayout and paint it using CSS Grid/Flexbox, plus a demo harness for switching surfaces and injecting new ones.

Module Breakdown

fladapt/
├── backend/
│   ├── src/
│   │   ├── model.ts          # Type definitions (LayoutSpec, SurfaceProfile, ElementSpec, etc.)
│   │   ├── classify.ts       # Surface classification (effective canvas, orientation, size tier)
│   │   ├── resolver.ts       # The engine: resolve(spec, surface): ResolvedLayout
│   │   ├── trace.ts          # Trace string generation helper
│   │   ├── textMeasure.ts    # Text measurement engine (canvas 2D + typographic fallback)
│   │   ├── surfaces/
│   │   │   └── builtin.ts    # 4 built-in SurfaceProfile instances
│   │   └── specs/
│   │       └── example.ts    # Example layout spec (logo, headline, CTA)
│   ├── package.json
│   └── tsconfig.json
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── App.tsx       # Demo application (surface picker, trace panel)
│   │   │   ├── Renderer.tsx  # CSS Grid/Flexbox renderer
│   │   │   ├── CanvasRenderer.tsx  # Canvas-based rendering
│   │   │   └── styles.css    # Component styles with design tokens
│   │   ├── main.tsx          # React entry point
│   │   └── vite-env.d.ts     # Vite type declarations
│   ├── public/
│   │   └── favicon.png       # Site favicon
│   ├── index.html            # HTML entry point
│   ├── package.json
│   ├── package-lock.json
│   ├── tsconfig.json
│   └── vite.config.ts
├── docs/                     # Architecture, Design, PRD, TRD documentation
├── assets/                   # Icons, UI samples, product card images
├── vercel.json               # Vercel deployment configuration
├── package.json              # Root workspace package.json
└── .gitignore

🎯 How It Works

The resolution engine follows a deterministic, priority-ordered algorithm:

Step 1 — Compute effective canvas

effectiveCanvas = {
  width:  surface.width  - (insets.left + insets.right),
  height: surface.height - (insets.top + insets.bottom)
}

where insets merges safeArea and bleed if both are present (they compose additively).

Step 2 — Classify surface (generic, id-agnostic) Derive from geometry alone:

  • orientation: explicit if provided, else width > height ? "landscape" : width < height ? "portrait" : "square"
  • sizeTier: bucket by effective canvas area against fixed thresholds

Step 3 — Sort elements by priority Ascending priority value = processed first = most protected. Stable sort so tie-break order is deterministic.

Step 4 — Allocate per element (loop) For each element, in priority order:

  1. Try preferredSize. If it fits → place it there
  2. Else walk the degradation ladder in order authored on the spec:
    • shrink → to: check if the shrink target fits
    • reflow → stack|inline: change internal arrangement
    • truncate → maxLines: reduce content footprint
    • hide: element is dropped
  3. The first ladder step that fits wins; later steps are not attempted

Step 5 — Position placed elements Higher-priority elements get first claim on the anchor region for their kind; remaining elements fill leftover space top-to-bottom / left-to-right.

Step 6 — Emit trace + ResolvedLayout Every element carries its ordered trace: string[], generated inline during steps 4–5, not reconstructed afterward.

Example Trace

logo: preferred size 200×80 fits in 640×140; placed at preferred size.
headline: preferred size 480×120 does not fit in 640×140; attempting degradation ladder.
headline: shrink to 320×80 fits; placed at that size.
cta: preferred size 160×48 does not fit in remaining 120×140; no further degradation steps besides hide → hidden.

Unseen Surface Handling

Because Steps 1–2 only consume width, height, orientation, and insets, a brand-new profile (e.g., a smartwatch face, 1:1 at 200×200, with a small circular safe inset) flows through the identical code path. No special-casing is required or permitted — this is the core proof point for the live demo.


📂 The Four Required Surfaces

Fladapt resolves the same layout spec correctly across all four built-in surface types out of the box:

image

Surface Dimensions Category Safe Area
📱 Mobile Portrait 360 × 640 mobile 40px all sides
📱 Mobile Landscape 640 × 360 mobile 40px all sides
📺 Broadcast Lower-Third 640 × 140 broadcast 0px
🖥️ Square Kiosk 800 × 800 kiosk 50px all sides

⚡ Quick Start

Prerequisites

Tool Version Purpose
Node.js 18+ Frontend runtime
TypeScript 5.x Core engine language
Vite 5.x Build tool & dev server

Option A: Vite Dev Server (Recommended) 🚀

The fastest way to see the demo:

# 1. Navigate to the frontend directory
cd frontend

# 2. Install dependencies
npm install

# 3. Start the dev server
npm run dev
Service URL
🌐 Demo App http://localhost:5173
📖 Backend API http://localhost:5173

Option B: Build & Preview ⚙️

# Build the frontend
cd frontend && npm run build

# Preview the production build
npm run preview

Option C: Docker Compose 🐳

# Build and run all services
docker compose up --build

Option D: Deploy to Vercel ☁️

# Deploy with the vercel.json configuration
vercel deploy

📽️ Video Demo

A video demonstration of the system resolving layouts across surfaces can be accessed here:

  • Demo Video Link: click the icon for Video

image


💡 Approach

Fladapt was built around explainability and generalizability.

  1. Stack Selection (TypeScript + React + Vite): We chose TypeScript for the core engine to ensure framework-agnostic resolution with strict type safety. The resolver has zero DOM/React dependency — it is a pure function that can run in any environment. React is used only in the presentation layer to paint already-resolved layouts.

  2. Greedy Priority Allocator Over Constraint Solver: We deliberately chose a greedy, priority-ordered allocator over a general constraint solver (LP-style). Elements are processed from highest to lowest priority; each element gets the best size it can given what's left. This trades mathematical generality for full explainability — every outcome can be traced to one priority comparison and one space check.

  3. Geometry-Driven Classification: Surfaces are classified by their dimensions and orientation alone — never by an ID lookup. This means a brand-new surface (smartwatch, print, kiosk) flows through the identical code path without any code changes. This is the architectural answer to "what happens when the interviewer gives you a 5th surface live?"

  4. Inline Trace Generation: Every decision appends a trace entry at the point of making it. The trace array is never reconstructed after the fact from the final output — this guarantees the explanation can never diverge from actual behavior.

  5. Inset Merging: safeArea and bleed compose additively — they shrink/expand usable space from the outer edge by the same amount. This means future constraint types (print-bleed, broadcast-safe-area) are modeled uniformly as Inset fields, with no new control flow needed.

AI Tools Used

Tool Purpose
Claude Code AI pair-programming for development, debugging, and testing
Vite Build tooling and dev server
React Presentation layer rendering

📂 Project Structure

fladapt/
├── backend/
│   ├── src/
│   │   ├── model.ts          # Type definitions (LayoutSpec, SurfaceProfile, etc.)
│   │   ├── classify.ts       # Surface classification (effective canvas, orientation, size tier)
│   │   ├── resolver.ts       # The engine: resolve(spec, surface): ResolvedLayout
│   │   ├── trace.ts          # Trace string generation helper
│   │   ├── textMeasure.ts    # Text measurement engine (canvas 2D + typographic fallback)
│   │   ├── surfaces/
│   │   │   └── builtin.ts    # 4 built-in SurfaceProfile instances
│   │   └── specs/
│   │       └── example.ts    # Example layout spec (logo, headline, CTA)
│   ├── package.json
│   └── tsconfig.json
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── App.tsx       # Demo application (surface picker, trace panel)
│   │   │   ├── Renderer.tsx  # CSS Grid/Flexbox renderer
│   │   │   ├── CanvasRenderer.tsx  # Canvas-based rendering
│   │   │   └── styles.css    # Component styles with design tokens
│   │   ├── main.tsx          # React entry point
│   │   └── vite-env.d.ts     # Vite type declarations
│   ├── public/
│   │   └── favicon.png       # Site favicon
│   ├── index.html            # HTML entry point
│   ├── package.json
│   ├── package-lock.json
│   ├── tsconfig.json
│   └── vite.config.ts
├── docs/                     # Architecture, Design, PRD, TRD documentation
├── assets/                   # Icons, UI samples, product card images
├── vercel.json               # Vercel deployment configuration
├── package.json              # Root workspace package.json
└── .gitignore

🔑 API Reference

Resolution Engine

Method Endpoint Description
function resolve(spec, surface) Resolve a LayoutSpec against a SurfaceProfile → ResolvedLayout
function classifySurface(surface) Derive orientation and size tier from geometry alone
function computeEffectiveCanvas(surface, insets) Compute canvas bounds minus safeArea/bleed insets
function measureRenderedText(text, options) Measure text width/height using canvas 2D or typographic fallback
function computeContrastRatio(fg, bg) Calculate WCAG 2.1 contrast ratio between two hex colors

Data Contracts

Type Description
LayoutSpec Surface-independent layout description with elements and their priorities
ElementSpec Content element with preferred size, min size, degradation ladder, a11y constraints
SurfaceProfile Target physical surface with dimensions, orientation, category, and optional insets
ResolvedLayout Output: concrete layout with position, size, visibility, and trace per element
ResolvedElement Single resolved element with applied degradation steps and trace entries
DegradationStep Fallback behavior: shrink, reflow, truncate, or hide
Inset Rectangular inset (safeArea or bleed) with top/right/bottom/left

React Components

Component Description
App.tsx Demo application with surface picker, new-surface form, and trace panel
Renderer.tsx Pure CSS projection — takes ResolvedLayout and paints with CSS Grid/Flexbox
CanvasRenderer.tsx Canvas-based rendering for advanced visualization

🧪 Testing

cd backend

# Run the test suite
npm test

Test Coverage:

Test File Covers
resolver.test.ts Element fits without degradation, single degradation step, full ladder exhausted → hidden, safe-area/bleed canvas, unseen surface
classify.test.ts Orientation inference, size tier bucketing, effective canvas computation
Trace tests Assert trace text references actual reason (priority, remaining space)

🚀 Deployment

Vercel (Recommended) ☁️

vercel deploy

This launches the frontend with the Vite build pipeline configured in vercel.json.

Docker Compose 🐳

docker compose up --build

This launches all services: Frontend + Backend.

Individual Services

Service Deployment Target Notes
Frontend Vercel / Netlify Set VITE_API_URL to your backend URL
Backend Render / Railway Set NODE_ENV and TS_NODE
Database Supabase / Neon Optional, for persisted specs

Built with ❤️ by M Harish Gautham

⭐ If you find this project impressive, give it a star! ⭐

About

An explainable, deterministic adaptive layout engine that transforms a single content specification into optimized layouts for multiple surfaces, including mobile, social, kiosks, digital billboards, and broadcast displays.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages