Rajnish Noonia

Category: General

  • LiveLens: Two Languages, One Browser Tab, Zero Servers — Now with In-Browser AI Chat

    A real-time trading blotter and a webcam object detector have more in common than they look like they should. Both are event-driven: a stream of updates arrives continuously, something needs to render the current state without falling behind, and something else needs to watch that stream for patterns worth flagging – a position that’s moved too far, a count that’s spiked. I’ve spent most of the last decade building the first kind of system for capital markets. LiveLens is what happens when you point the same architectural instincts at a webcam instead of a market feed, and run the whole thing client-side, in a single browser tab, with no backend at all.

    Since the first version the app has grown quite a bit. What started as an object detection demo with a Python analytics layer now also includes a full AI chat interface that runs either entirely in the browser (no API key, no server) or connects to Claude, OpenAI, or Gemini via their streaming APIs. All in the same tab.

    What it does

    Grant camera access and LiveLens detects objects in the live feed, tracks each one’s identity across frames, and continuously computes rolling statistics: per-class counts, average dwell time, flagging anything that looks like a genuine anomaly rather than normal frame-to-frame noise. None of the video, and none of the analysis, ever leaves the tab.

    Flip to the Chat tab and you get a conversational AI interface that lets you run language models completely offline in the browser, or switch over to a cloud provider if you need more horsepower. The two modes share the same UI, same settings panel, same streaming experience. It’s one interface with a source selector, not two separate features bolted together.

    Three layers, three tools, on purpose

    It would be tempting to reach for one language and use it for everything. I split LiveLens into three layers instead, each running the tool that’s actually good at that job:

    • Perception: TensorFlow.js runs a MobileNet-based object detector (coco-ssd) directly in the browser, WebGL-accelerated.
    • Identity: a small TypeScript tracker turns a stream of unlabelled per-frame detections into persistent tracked objects.
    • Statistics: a genuine Python module, running in-browser via Pyodide, does the counting, aggregation, and anomaly detection with pandas and numpy.
    • Chat: Transformers.js v3 runs ONNX-quantised language models directly in the browser via WebGPU or WASM, no API key required. Optionally routes to Claude, OpenAI or Gemini instead.

    Why the neural net runs in JavaScript, not Python

    This is worth being upfront about, because “Python in the browser” is the headline feature and it would be easy to assume the model runs there too. It doesn’t, deliberately.

    In-browser neural-net inference is a mature, well-supported path in the JavaScript ecosystem. TensorFlow.js and transformers.js both have WebGL/WebGPU-accelerated runtimes, wide model support, and years of production hardening. Pyodide, which compiles the CPython interpreter and the scientific Python stack to WebAssembly, doesn’t have an equivalent neural-net execution engine yet. Asking it to run the detector would mean either a much slower pure-Python inference path or shipping a second runtime just to reinvent what TensorFlow.js already does well.

    So the detector runs where the ecosystem is strongest, and Python gets used for what it’s actually strong at: data manipulation and statistics. That division of labour is the whole point of the architecture, not an incidental detail.

    The tracker: turning detections into objects

    coco-ssd gives you a fresh, unlabelled set of bounding boxes on every frame. It has no concept of “the same coffee cup as last frame” – as far as the model is concerned, every detection is a new observation with no history.

    To compute dwell time, or to feed any kind of coherent time series into the analytics layer, that identity has to come from somewhere. LiveLens uses a lightweight IoU-based tracker: on each frame, every new detection is matched to the closest existing track of the same class by bounding-box overlap, using greedy matching against an intersection-over-union threshold.

    function intersectionOverUnion(a, b) {
    const [ax, ay, aw, ah] = a;
    const [bx, by, bw, bh] = b;
    const x1 = Math.max(ax, bx);
    const y1 = Math.max(ay, by);
    const x2 = Math.min(ax + aw, bx + bw);
    const y2 = Math.min(ay + ah, by + bh);
    const interArea = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
    const unionArea = aw * ah + bw * bh - interArea;
    return unionArea <= 0 ? 0 : interArea / unionArea;
    }

    Tracks that go unmatched for more than 1.5 seconds are dropped. Long enough to bridge a missed frame or brief occlusion without inventing a new identity for the same physical object, short enough that a track doesn’t linger indefinitely after something actually leaves the frame. It’s a simplified relative of the greedy-matching idea behind SORT-style trackers, without a Kalman filter. Accurate enough for a browser demo, not for a production tracking system.

    Pyodide: real pandas, real numpy, zero server

    This is the part that made the project worth writing up. Pyodide loads the full CPython interpreter, plus numpy and pandas, compiled to WebAssembly, and runs it in the same tab as the React app:

    def analyze(window_json: str) -> str:
    objects = json.loads(window_json)
    df = pd.DataFrame(objects)
    counts = df.groupby("class")["id"].nunique().to_dict()
    df["dwell"] = df["last_seen"] - df["first_seen"]
    dwell_ms = df.groupby("class")["dwell"].mean().to_dict()
    anomalies = []
    for cls, count in counts.items():
    hist = _history[cls]
    if len(hist) >= 5:
    mean, std = np.mean(hist), np.std(hist)
    z = (count - mean) / std if std > 0 else 0
    if abs(z) >= 2.5:
    anomalies.append({"class": cls, "count": count, "z": round(z, 2)})
    hist.append(count)
    return json.dumps({"counts": counts, "dwell_ms": dwell_ms, "anomalies": anomalies})

    _history is module-level state inside the Pyodide runtime, so it persists across calls for the life of the tab without React having to manage a rolling buffer itself. React just calls analyze() on an interval with the current tracked-object snapshot and gets back a JSON summary, exactly as if it were talking to a REST endpoint. The difference is there’s no network hop, no serialization over the wire beyond a function call boundary, and no server to deploy or pay for.

    The anomaly check itself is deliberately simple: a rolling per-class count history, a z-score against the mean and standard deviation of the last ~60 windows, and a threshold. It won’t catch anything subtle, but it’s the same basic shape as the alerting logic behind far more sophisticated systems, computed live, over a real stream, with real statistics.

    The AI Chat layer: running language models in the browser

    The newer addition to LiveLens is a chat tab that lets you talk to a language model without leaving the browser. There’s no proxy, no serverless function, nothing phoning home. The model weights download once, get cached by the browser, and run locally on WebGPU or fall back to WASM if WebGPU isn’t available.

    Under the hood it’s Transformers.js v3 running ONNX-quantised models in a Web Worker so the inference doesn’t block the UI thread. The worker loads the pipeline, receives messages, streams tokens back, and the main thread just renders whatever it receives. It’s a clean separation: the camera detection and Python analytics keep running even while the language model is thinking.

    Which models are available offline

    The model picker offers two groups, general purpose and coding, so you can pick based on what you’re actually doing:

    • SmolLM2 135M / 360M / 1.7B: HuggingFace’s small but surprisingly capable series, good for quick questions and demos. The 135M model loads in seconds even on slower connections.
    • Qwen 2.5 0.5B / 1.5B: strong multilingual reasoning from Alibaba, fits comfortably in browser memory.
    • Llama 3.2 1B: Meta’s smallest Llama, good general-purpose model, 2GB download.
    • Phi 3.5 Mini: Microsoft’s model that punches above its weight for reasoning and code, 2.2GB.
    • Qwen2.5-Coder 0.5B / 1.5B / 3B: dedicated code models that are genuinely useful for explaining snippets, debugging, and answering tech questions without sending your code to a third-party server.

    Switching to a coding model automatically swaps the system prompt to something more appropriate. It makes a noticeable difference in the quality of responses for technical questions. The system prompt is fully editable anyway, so you can tune it however you want.

    Cloud API fallback: Claude, OpenAI, Gemini

    The offline models are genuinely useful but they have obvious limits. A 1B parameter model is not going to match GPT-4o on complex reasoning tasks. So the same chat interface also supports switching to a cloud provider, using the exact same streaming UX.

    All three providers stream tokens over server-sent events. There’s a readSSE async generator that handles the data: line parsing, [DONE] termination, and abort signals, and each provider has its own thin wrapper on top that handles the auth header and extracts the right field from the delta object:

    • Claude: API key stored in localStorage, anthropic-dangerous-direct-browser-access: true header to allow direct browser calls, extracts content_block_delta.text_delta from the SSE stream.
    • OpenAI: API key in localStorage, standard chat/completions stream, extracts choices[0].delta.content.
    • Gemini: uses OAuth 2.0 via Google Identity Services rather than an API key, because Gemini supports it and it’s a nicer UX than asking users to generate and paste service account credentials. You provide an OAuth client ID, click Sign in with Google, and it handles the token flow. Roles get mapped to Gemini’s convention (assistant becomes model) before the request goes out.

    API keys and the client ID are stored in localStorage only. They never leave the browser except in the Authorization header of the request to the respective API. No telemetry, no backend logging, nothing persisted anywhere else.

    Settings UX: floating panels, not a modal

    The settings live behind two icon buttons in the chat topbar: a gear icon for model and provider config, and a pencil icon for the system prompt. Clicking either one opens a floating panel that overlays the messages area without pushing any content around or blocking interaction with the rest of the page. The panel is non-modal, so you can still scroll through the conversation and type in the input while it’s open.

    Keeping them separate felt right. Model selection is something you do once per session. The system prompt is something you might actually want to tweak mid-conversation, so giving it its own dedicated panel makes it feel less buried.

    Interrupt and send

    One thing that bothered me about a lot of chat demos is that the Send button disappears while the model is responding and you can’t send anything new until it’s done. If you realise mid-response that your question was badly phrased, or you want to follow up immediately, you have to wait.

    In LiveLens you can send a new message at any point. If generation is in progress, the current response gets aborted, the partial text gets saved into the conversation history, and the new request starts immediately. The button label changes to “Interrupt & Send” during generation so it’s clear what’s going to happen. There’s also a separate Stop button if you just want to halt the current response without sending anything new.

    For offline models the abort signal goes to the Web Worker which terminates the generation loop. For API sources it cancels the fetch via AbortController. Either way the partial text is already in state when the abort fires so nothing gets lost.

    Context management

    A small token usage indicator in the input bar shows roughly what percentage of the model’s context window is consumed. It turns amber at 80%. At that point you probably want to either clear the context or be a bit more concise. “Clear context” is one click and it’s right there next to the usage pill, not buried in a settings menu somewhere.

    The app also remembers the last selected tab, model, and provider across reloads. The offline model restarts loading automatically on page load if that was what you had selected. It’s already cached locally after the first download so it’s fast.

    Why this matters beyond a toy demo

    This is the same shape as a real-time trading blotter watching market data for anomalous price moves, or an operations dashboard flagging unusual order flow: a continuous event stream, an identity/state layer sitting between raw events and anything downstream, and a statistics layer watching for deviation from a rolling baseline. The domain changed from financial instruments to webcam detections. The architecture didn’t. Being able to build that shape entirely client-side, with a real statistical computing stack and no backend at all, says something about how far browser runtimes have come.

    The chat layer adds something different: a language model that runs in the same tab as the rest of the app, that can be pointed at what the detection pipeline is seeing, and that doesn’t require an account or API key to use. Whether that’s useful in production depends on the use case, but the fact that it’s a viable option at all is worth noting. A 1B parameter model that runs locally in a browser tab and streams tokens in real time without a server would have been a strange claim to make even two or three years ago.

    Practical implications

    Detection quality depends on lighting and the model’s speed/accuracy trade-off. lite_mobilenet_v2 is tuned to keep up with live video, not to win accuracy benchmarks. Expect it to miss small or partially occluded objects.

    WebGL matters. TensorFlow.js prefers a WebGL backend; without it the app falls back to WASM, which is noticeably slower. Most modern browsers and GPUs handle this fine, but it’s worth knowing if performance looks off on an older machine.

    Pyodide’s first load has a real cost. Fetching the CPython interpreter plus numpy and pandas as WASM packages is a few megabytes of one-time download, cached by the browser afterwards. The analytics panel shows “Loading Python…” for exactly this reason – it’s not a bug, it’s an honest reflection of what’s happening.

    The tracker is intentionally simple. Greedy IoU matching with no motion prediction means fast-moving objects or heavy occlusion can split one physical object into two tracked IDs. A production system would add a motion model; a demo doesn’t need one to make the point.

    Offline model quality scales with size. The 135M SmolLM2 is genuinely fast but don’t expect it to write production code or reason through anything complicated. The 3B Qwen-Coder is a lot more capable but it’s a 3GB download, so pick based on what you actually need. For anything serious the API providers are the more pragmatic choice anyway. The offline path is there for when you want zero data leaving the machine.

  • Building a TypeScript-First Frontend SDK: An Architectural Journey

    Over the past few years I have spent much of my time on one recurring problem: how do you let many teams build many data-intensive web applications without each of them reinventing the same foundations? This post walks through the architectural journey of designing a TypeScript-first frontend SDK to answer that question. It focuses on the patterns and trade-offs rather than any particular implementation, because the ideas travel well beyond the project that prompted them.

    Application layer Individual applications built on the platform Data grid apps Dashboards Workflow tools Admin tools Others… Framework layer Native React and Angular — thin bindings over the core Angular bindings Components · Directives · DI bridge React bindings Components · Hooks · DI bridge Core Framework-agnostic TypeScript — no UI framework dependency MVVMViewModel · State IoC / DIContainer · Discovery Data gridConfig · Streaming FormsRules · Validation ShellConfig-driven LayoutPanels · Persist ThemingTokens · Dark mode MessagingEvents · Notifications VisualisationCharts · Maps StreamingObservables ObservabilityLogging · Tracing · APM SecurityAuth · Permissions AI-ready contextContext files · Agent rules Scaffolding CLI + delivery pipeline CI/CD · Containers · AWS · dev / test / prod environments
    A layered SDK: framework-agnostic core, thin React/Angular bindings, and applications on top

    The Starting Point: Why a Component Library Is Not Enough

    The usual answer to “we need consistency across teams” is a component library: styled buttons, inputs and grids published as a package. In my experience that solves perhaps a fifth of the real problem. It says nothing about where state lives, how view logic is tested without rendering, how the same feature behaves identically in two UI frameworks, how dependent form fields validate each other, or how any of it stays coherent across years of changing teams.

    So the goal shifted from “shared components” to “shared architecture”: a TypeScript-first, framework-agnostic SDK in which React and Angular are equal, native rendering targets. The choice of view framework becomes a team preference, not an architectural fork.

    Principle 1: MVVM Keeps the View Thin

    The most important decision was to adopt Model–View–ViewModel as the backbone. The view is a template that binds to properties and commands. The ViewModel is a plain TypeScript class that owns state and behaviour. The model is data and services. Nothing else is allowed to blur those lines.

    Three benefits follow. Testability: a ViewModel has no JSX and no decorators, so business logic is tested as ordinary unit tests instead of by mounting components and poking the DOM. Reuse across frameworks: the same ViewModel drives a React view and an Angular view, so behaviour is written once. Clarity: engineers always know where logic belongs, which removes a whole category of code-review debate.

    Principle 2: Dependency Injection Owned by the Core

    MVVM only works well when ViewModels can ask for what they need without knowing where it comes from. Neither framework’s native mechanism fits: Angular’s injector only exists in Angular, and React context is not really DI. The answer was an IoC container that lives in the core, independent of any UI framework, with thin bridges into each one – a hook-based resolver for React and an injector bridge for Angular.

    Services are registered against interfaces, and can be discovered by capability rather than concrete type. This is where SOLID stops being a slide and becomes the path of least resistance: new behaviour arrives as a new registration, not an edit to existing classes, and every dependency can be swapped for a test double.

    Principle 3: Layers With Enforced Boundaries

    The SDK is organised as a monorepo with three layers:

    • Core: pure TypeScript – MVVM primitives, the container, state, messaging, streaming, configuration, theming tokens and platform services. No UI framework dependency.
    • Framework bindings: thin React and Angular packages that render the core’s concepts natively and bridge the container into each framework.
    • Applications: products built on a shared, configuration-driven shell, deployable to the browser or to desktop containers.

    Build-time boundary rules stop applications from reaching into the core directly and reject circular dependencies. Architecture that is only documented erodes; architecture that fails the build stays intact.

    Principle 4: Solve Cross-Cutting Concerns Once

    Most of the value of a platform is in the unglamorous capabilities that every application needs and no team wants to build. Rather than leave them to each project, the SDK provides them as shared services:

    • Configuration-driven shell and layout: navigation, panels and workspaces declared in configuration, with dockable panels whose arrangement is persisted per user.
    • State persistence: any screen can opt in to having column widths, filters, sort order and layout restored on reload.
    • Data grid abstraction: grids configured declaratively, with streaming updates delivered through observables, so teams write configuration and a ViewModel rather than grid plumbing.
    • Forms with a rule engine: field dependencies, visibility and validation expressed as rules; client and server validation surface through one pipeline.
    • Design tokens: semantic colours, spacing and typography exposed as typed constants and CSS variables, with dark mode as a first-class theme.
    • Observability and security: structured logging, tracing and application performance monitoring wired in by default, alongside authentication and permission handling.

    Principle 5: Start Every Project From Working, Not Blank

    A scaffolding CLI turns the SDK into a starting point rather than a set of instructions. A few commands produce an application that already has the shell, theming, authentication, observability, CI/CD pipelines, container configuration and cloud deployment templates for AWS across dev, test and production environments. The decisions that usually consume a project’s first sprint are already made, and every project starts in the same shape – which makes moving between them much easier for engineers.

    Principle 6: Design for AI-Assisted Development

    A newer lesson: an SDK now has two audiences, human developers and AI coding agents. Each package carries a concise context file describing its patterns, naming conventions and rules, and a routing structure points an agent at only the context relevant to its task. Generated projects inherit these files automatically.

    Treating that context as a maintained deliverable – updated alongside code and checked in CI – keeps agents inside the architecture instead of inventing their own. The result is AI assistance that accelerates teams without quietly eroding standards.

    Principle 7: Governance That Scales

    • Enforced module boundaries at build time
    • Semantic versioning with deprecation windows, so nothing disappears without warning
    • Living documentation: component catalogues and visual regression tests in the pipeline
    • Context files as a contract for both humans and agents

    What I Took Away

    The measurable outcome was that applications which once took months could be delivered in weeks, with a consistent look, feel and behaviour. But the more lasting lessons were architectural:

    • Separate behaviour from rendering early; it is very hard to retrofit.
    • Own your dependency injection in the core if you want framework independence.
    • Encode rules in the build, not in wiki pages.
    • Invest in the boring cross-cutting services – that is where teams lose the most time.
    • Treat AI context as part of the product, not an afterthought.

    A good platform is measured by how rarely the teams building on it need to think about it.

  • The JavaScript Execution Engine: Event Loop, Microtasks, Macrotasks, Web Workers and Service Workers

    JavaScript is single-threaded. There is one call stack, one thread of execution, and at any given moment exactly one thing is running. Yet it handles timers, network requests, user events, animations, background computation, and offline support without blocking. Understanding how it works changes how you write async code, debug race conditions, and reason about performance. This post covers the full picture: the event loop, the queues, Web Workers, and Service Workers.

    JS engine (single thread) Call stack Executes synchronous code console.log(‘hello’) main() Heap Object memory allocation { name: ‘obj’, val: 42 } Web APIs / Node.js setTimeout Timer (0ms+ delay) fetch / XHR Network I/O DOM events click, scroll, resize rAF / Promises Async coordination Microtask queue Drains completely before next task Promise.then() / .catch() / .finally() queueMicrotask() / MutationObserver Macrotask queue One task per loop iteration setTimeout / setInterval I/O callbacks / UI events Event loop offload Priority: (1) sync (2) microtask – drains fully (3) macrotask – one per iteration
    JavaScript execution engine: call stack, Web APIs, microtask queue, macrotask queue and event loop

    The Moving Parts

    Four components interact to make async JavaScript work on the main thread.

    The call stack

    The call stack is where synchronous code executes. When you call a function, a frame is pushed onto the stack. When it returns, the frame is popped. The engine can only execute code at the top of the stack. If a function takes 500ms of CPU work, nothing else can happen during those 500ms – the stack is blocked. This is why long-running synchronous operations freeze the browser: the event loop cannot process anything while the stack is occupied.

    Web APIs (or Node.js equivalents)

    The browser provides APIs that live outside the JavaScript engine: timers, network I/O, DOM events, file system access. When you call setTimeout, the JS engine hands the callback and delay to the browser’s timer mechanism and immediately returns. The actual waiting happens in a separate system thread managed by the browser. When the timer fires, the browser posts the callback into a queue.

    This is the key insight: JavaScript itself never does I/O. It delegates to the runtime, registers a callback, and moves on. The runtime notifies JavaScript when the work is done by placing callbacks into one of two queues.

    The microtask queue

    Microtasks are high-priority callbacks that run immediately after the current task completes, before the event loop picks up any macrotask and before the browser renders the next frame. Sources of microtasks include:

    • Promise.then(), .catch(), .finally()
    • queueMicrotask()
    • MutationObserver callbacks
    • await continuations (which desugar to Promise .then())

    The critical rule: the microtask queue drains completely before the event loop moves on. If a microtask callback queues another microtask, that microtask also runs before any macrotask. This can theoretically starve the macrotask queue – and the rendering pipeline – if microtasks keep spawning more microtasks.

    The macrotask queue (task queue)

    Macrotasks are lower-priority callbacks scheduled by the runtime. Sources include:

    • setTimeout and setInterval callbacks
    • I/O callbacks (file reads, network in Node.js)
    • UI event callbacks (click, keydown, scroll)
    • setImmediate in Node.js
    • MessageChannel port messages

    The event loop picks exactly one macrotask per iteration. After that one macrotask runs, it drains the entire microtask queue again before picking the next macrotask.

    The event loop algorithm

    while (true) {
      executeCurrentTask();
      while (microtaskQueue.length > 0) {
        const task = microtaskQueue.shift();
        task();
      }
      maybeRender();
      if (macrotaskQueue.length > 0) {
        const task = macrotaskQueue.shift();
        task();
      }
    }

    Why Promise.then() always beats setTimeout(fn, 0)

    setTimeout(fn, 0) does not mean “run immediately”. It means “run as soon as possible, but only after the current task and all microtasks have finished, and only as a macrotask”. A Promise.then() registered at the same time will always run first because it goes into the microtask queue, which drains before any macrotask is picked.

    setTimeout(() => console.log('timeout'), 0);
    Promise.resolve().then(() => console.log('promise'));
    
    // Output:
    // promise  (microtask - runs first)
    // timeout  (macrotask - runs after all microtasks)

    How fetch actually works

    When you call fetch('/data'):

    1. fetch() returns a Promise immediately. The browser’s networking layer starts the HTTP request in a separate thread.
    2. JavaScript continues executing synchronously – nothing waits.
    3. When the response arrives, the browser resolves the promise, queuing any .then() handlers as microtasks.
    4. The next time the microtask queue drains, those handlers run.
    5. Each .then() in a chain is a separate microtask, queued lazily when the previous one resolves.

    How async/await desugars

    async/await is syntactic sugar over promises. Every await suspends the async function (pops its frame off the call stack) and queues the continuation as a microtask when the awaited promise resolves.

    // async/await
    async function load() {
      const res = await fetch('/data');
      const data = await res.json();
      console.log(data);
    }
    
    // Is approximately:
    function load() {
      return fetch('/data')
        .then(res => res.json())
        .then(data => { console.log(data); });
    }

    A worked example: what does this log?

    console.log('A');
    setTimeout(() => console.log('B'), 0);
    Promise.resolve()
      .then(() => console.log('C'))
      .then(() => console.log('D'));
    console.log('E');
    
    // Output: A E C D B

    Sync runs first: A, then E. Microtask queue drains: C resolves and queues D, so C then D. Finally the macrotask runs: B. The second .then() was not enqueued until the first one ran – promise chains are lazy microtask sequences, not a pre-loaded batch.

    Main thread UI + event loop Call stack Sync execution DOM access Microtask queue Promise callbacks Macrotask queue setTimeout, events postMessage() Send / receive data Render pipeline Layout – Paint Blocked by JS Workers never block this Web Worker Separate OS thread Own call stack CPU-heavy work No DOM access Own microtask queue Promises work here Own macrotask queue setTimeout works here postMessage() Send results back Service Worker Network proxy thread Lifecycle events install – activate idle / terminated fetch event Intercepts all requests respondWith(promise) Cache API Serve offline / stale Cache-then-network Push + sync events Background wakeup No DOM / no window postMessage to page send result fetch() intercepted by SW cached / network response All three have their own event loop – only the main thread can access the DOM
    Main thread, Web Worker, and Service Worker – three independent event loops with distinct responsibilities

    Web Workers: true parallelism for JavaScript

    The event loop handles async waiting by delegating I/O to the browser and resuming when ready. But it does not solve CPU-intensive computation. If you need to parse a 50MB JSON file, run a physics simulation, encrypt a large payload, or process an image, doing that on the main thread blocks the call stack and freezes the UI regardless of how cleverly you structure your promises.

    Web Workers give you a genuine second OS thread. A Worker runs in a completely separate execution context with its own call stack, its own microtask and macrotask queues, its own memory heap, and its own event loop. The main thread and workers run in parallel on multi-core hardware.

    What Workers can and cannot do

    A Worker has access to: fetch, setTimeout, setInterval, Promise, IndexedDB, WebSockets, Cache API, crypto, console, and most Web APIs that don’t require a rendering context. What it does not have access to is the DOM – no document, no window, no direct manipulation of page elements. The rendering pipeline lives exclusively on the main thread, and the browser enforces this hard boundary.

    Communication via postMessage

    The main thread and workers communicate exclusively through postMessage(). Data is serialised (structured clone algorithm) and deserialised on the other side; each side gets its own copy, not a shared reference. For large data (images, audio buffers, typed arrays) you can use Transferable Objects to hand ownership to the other thread without copying, which is zero-copy and fast.

    // main.js
    const worker = new Worker('worker.js');
    worker.postMessage({ action: 'process', data: largeArray });
    worker.onmessage = (event) => {
      console.log('Result:', event.data);
    };
    
    // worker.js
    self.onmessage = (event) => {
      const result = heavyComputation(event.data.data);
      self.postMessage(result);
    };

    When a worker calls postMessage(), the message arrives on the main thread as a macrotask – it queues a message event on the worker object. The main thread’s event loop picks it up in the normal way: after all current microtasks have drained. This means worker communication is non-blocking in both directions.

    Transferable Objects for zero-copy transfer

    const buffer = new ArrayBuffer(100 * 1024 * 1024); // 100MB
    worker.postMessage({ buffer }, [buffer]);
    // buffer is now detached in main - worker owns it
    
    self.onmessage = (e) => {
      processBuffer(e.data.buffer);
      self.postMessage({ buffer: e.data.buffer }, [e.data.buffer]);
    };

    Shared memory with SharedArrayBuffer

    For high-frequency communication (game engines, audio processing, real-time data pipelines), copying data on every message is too expensive. SharedArrayBuffer creates a memory region that both the main thread and workers can read and write without copying. You coordinate access using Atomics – atomic operations that prevent race conditions by guaranteeing visibility and mutual exclusion.

    const shared = new SharedArrayBuffer(4);
    const view = new Int32Array(shared);
    worker.postMessage({ shared });
    Atomics.store(view, 0, 1);
    Atomics.notify(view, 0, 1);

    Worker types

    There are three kinds of Worker: Dedicated Workers (owned by one page, created with new Worker()), Shared Workers (shared across multiple pages from the same origin, accessible via new SharedWorker()), and Service Workers (covered in the next section). Dedicated Workers are by far the most common.

    Service Workers: the network proxy

    A Service Worker is a type of worker with a fundamentally different purpose. Where a Dedicated Worker offloads CPU computation, a Service Worker sits between your application and the network, intercepting and handling every fetch request the page makes. It is the foundation of Progressive Web Apps (PWAs) – offline support, background sync, push notifications, and fine-grained caching strategies all live here.

    The Service Worker lifecycle

    A Service Worker has a distinct lifecycle that separates it from ordinary workers:

    1. Registration: the page calls navigator.serviceWorker.register('/sw.js'). The browser downloads and parses the worker script.
    2. Install: the browser fires the install event. This is where you pre-cache static assets. You call event.waitUntil(promise) to tell the browser not to proceed until your caching is complete. If the promise rejects, the install fails and the worker is discarded.
    3. Activate: once installed, the worker waits to activate. A new worker only activates when no existing controlled pages are open (or when you call self.skipWaiting()). The activate event is where you clean up old caches from previous versions.
    4. Idle: the worker is now active and controlling pages, but the browser may terminate it at any time to save memory. It is relaunched on demand when a controlled page makes a fetch or a push notification arrives.
    const CACHE = 'v1';
    const STATIC = ['/index.html', '/app.js', '/style.css'];
    
    self.addEventListener('install', event => {
      event.waitUntil(
        caches.open(CACHE).then(cache => cache.addAll(STATIC))
      );
      self.skipWaiting();
    });
    
    self.addEventListener('activate', event => {
      event.waitUntil(
        caches.keys().then(keys =>
          Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
        )
      );
      self.clients.claim();
    });

    Intercepting fetch requests

    The fetch event is the core of a Service Worker. Every network request made by a controlled page – including fetch(), XMLHttpRequest, CSS imports, image loads, script tags – passes through this handler. You use event.respondWith(promise) to provide the response, which can come from the cache, the network, or be constructed programmatically.

    self.addEventListener('fetch', event => {
      event.respondWith(
        caches.match(event.request).then(cached => {
          if (cached) return cached;
          return fetch(event.request).then(response => {
            const toCache = response.clone();
            caches.open(CACHE).then(cache => cache.put(event.request, toCache));
            return response;
          });
        })
      );
    });

    Caching strategies

    • Cache first: serve from cache if present, otherwise fetch from network and cache the result. Best for assets that change infrequently (fonts, versioned JS bundles).
    • Network first: try the network, fall back to cache if offline. Best for frequently updated content (API responses, news feeds).
    • Stale while revalidate: serve from cache immediately (fast), then fetch from network in the background to update the cache for the next request. Best for content where showing slightly stale data is acceptable.
    • Network only: never use the cache. For analytics, payment flows, anything where stale data is dangerous.
    • Cache only: never hit the network. For pre-cached static assets in a fully offline app.

    Background sync and push notifications

    Service Workers can be woken up by the browser even when no page is open. The sync event fires when connectivity is restored after a period offline – you can queue writes made while offline and flush them here. The push event fires when a push message arrives from your server, allowing you to display a notification even if the user doesn’t have your site open.

    self.addEventListener('sync', event => {
      if (event.tag === 'submit-form') {
        event.waitUntil(flushPendingSubmissions());
      }
    });
    
    self.addEventListener('push', event => {
      const data = event.data.json();
      event.waitUntil(
        self.registration.showNotification(data.title, {
          body: data.body,
          icon: '/icon.png',
        })
      );
    });

    Communicating with the page

    A Service Worker communicates with its controlled pages via postMessage, just like a Dedicated Worker. The worker can send messages to all controlled clients via self.clients.matchAll(), and individual pages can send messages to the worker via navigator.serviceWorker.controller.postMessage(). As with Dedicated Workers, these messages arrive as macrotasks on the receiving end.

    self.clients.matchAll().then(clients => {
      clients.forEach(client => client.postMessage({ type: 'CACHE_UPDATED' }));
    });
    
    navigator.serviceWorker.addEventListener('message', event => {
      if (event.data.type === 'CACHE_UPDATED') {
        showUpdateBanner();
      }
    });

    Web Worker vs Service Worker: when to use which

    The two are often confused because both are “workers that run off the main thread”. The distinction is purpose, not mechanism.

    Web Worker Service Worker
    Primary purpose CPU-intensive computation Network proxy and caching
    Lifetime As long as the page holds a reference Persists independently, browser controls termination
    Scope Single page All pages on the same origin
    fetch access Can make fetch calls Intercepts all fetch calls from the page
    DOM access No No
    Offline support No Yes, via Cache API
    Push notifications No Yes
    Background sync No Yes
    Typical use cases Image processing, data parsing, encryption, physics, ML inference Caching strategy, offline PWA, push, background sync

    Practical implications

    Long microtask chains can block rendering

    The browser cannot render a new frame until the microtask queue is empty. For large data processing, break work into chunks using setTimeout to yield to the rendering pipeline, or use a Web Worker to move the work off the main thread entirely.

    Service Workers require HTTPS

    Service Workers can intercept and modify any network request, which makes them powerful – and dangerous if compromised. Browsers only register Service Workers on HTTPS origins (and localhost for development). There are no exceptions.

    Service Workers are version-sensitive

    When you deploy a new Service Worker, users with the old version keep it until all their tabs are closed and reopened. skipWaiting() + clients.claim() force immediate takeover – useful during development but potentially disruptive if the new worker uses an incompatible cache structure. Design your activate handler to clean up old caches explicitly.

    Workers have their own event loops

    Both Web Workers and Service Workers have their own complete event loop – their own call stack, microtask queue, and macrotask queue. Promise, setTimeout, fetch, and async/await all work inside a worker the same way they work on the main thread. The only difference is the absence of the DOM and window APIs.

    Summary

    JavaScript’s concurrency model has three layers. The event loop handles async waiting on the main thread – sync code runs first, then all microtasks drain (Promises, await continuations), then one macrotask runs, then microtasks drain again. Web Workers handle CPU parallelism – genuine OS threads running their own event loops, communicating with the main thread via postMessage without blocking the UI. Service Workers handle network and persistence – a long-lived proxy thread that intercepts fetch requests, implements caching strategies, enables offline use, and receives push notifications and background sync events independently of any open page.

    Together they give JavaScript – a language with one main thread – a complete answer to async I/O, CPU parallelism, and network resilience.

  • Introduction to Design Patterns

    A pattern describes a recurring problem that occurs in a given context and, based on a set of guiding forces, recommends a solution. The solution is usually a simple mechanism, a collaboration between two or more data objects, services, processes, threads, components, or nodes that work together to resolve the problem identified in the pattern.

    Mainly, there are three levels of patterns :

    • Design Patterns (e.g. GoF patterns)
    • Architectural Patterns (e.g. Layers, MVC,MVP,MVVM, P2P )
    • Implementation patterns (Idioms) (e.g. language specific patterns like Pimpl, RAII in C++)

    In my previous article “Design Patterns” we have discussed about Design Patterns,I will briefly re-define then in this article and will discuss Architectural patterns specifically MVC,MVP and MVVM patterns and their implementation.At the end of this article i will take you to Microsoft Enterprise Library (applications blocks) version 5.0 which was recently released (April 2010) and will also discuss about few guidance from microsoft like Composite Application Guidance (CAG).

    Design Patterns

    The Gang of Four (GoF) patterns are generally considered the foundation for all other patterns. The authors Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides are often referred to as the GoF, or Gang of Four.

    They are categorized in three groups:

    • Creational
    • Structural and
    • Behavioral

    Lets discuss them in brief (for detail please visti my previous article “Design Patterns“)

    • Creational Patterns
      • Abstract Factory : Creates an instance of several families of classes.
      • Builder :  Separates object construction from its representation.
      • Factory Method :  Creates an instance of several derived classes.
      • Prototype  :  A fully initialized instance to be copied or cloned.
      • Singleton  :  A class of which only a single instance can exist.
    • Structural Patterns
      • Adapter : Match interfaces of different classes.
      • Bridge :  Separates an object’s interface from its implementation.
      • Composite :  A tree structure of simple and composite objects.
      • Decorator :  Add responsibilities to objects dynamically.
      • Facade :  A single class that represents an entire subsystem.
      • Flyweight :  A fine-grained instance used for efficient sharing.
      • Proxy :  An object representing another object.
    • Behavioral Patterns
      • Chain of Resp. :  A way of passing a request between a chain of objects.
      • Command  :  Encapsulate a command request as an object.
      • Interpreter  : A way to include language elements in a program.
      • Iterator : Sequentially access the elements of a collection.
      • Mediator : Defines simplified communication between classes.
      • Memento : Capture and restore an object’s internal state.
      • Observer :  A way of notifying change to a number of classes.
      • State : Alter an object’s behavior when its state changes.
      • Strategy : Encapsulates an algorithm inside a class.
      • Template Method : Defer the exact steps of an algorithm to a subclass.
      • Visitor : Defines a new operation to a class without change.

    Architectural Patterns

    An Architectural Pattern expresses a fundamental structural organization or schema for software systems. It provides a set of predefined subsystems, specifies their responsibilities, and includes rules and guidelines for organizing the relationships between them.

    Model View Controller (MVC), Model View Presenter (MVP) , Model View ViewModel (MVVM) falls under architectural pattern, to be more precise they are architectural presentation patterns. In this article we will discuss about MVC, MVP and MVVM and their implementation using .net c#. There are other patterns like Application Architecture Pattern (Client-Proxy Server,Customer Support,Reactor,Replicated Servers,Layered Architecture, Pipe and Filter Architecture … and so on ) which are not discussed here.

    Model View Controller (MVC)

    Model-View-Controller (MVC) is a architectural Patternoften used by applications that need the ability to maintain multiple views of the same data. The MVC pattern hinges on a clean separation of objects into one of three categories — models for maintaining data, views for displaying all or a portion of the data, and controllers for handling events that affect the model or view(s).

    Because of this separation, multiple views and controllers can interface with the same model. Even new types of views and controllers that never existed before can interface with a model without forcing a change in the model design.

    It is important to note that both the view and the controller depend on the model. However, the model depends on neither the view nor the controller. This is one the key benefits of the separation. This separation allows the model to be built and tested independent of the visual presentation. The separation between view and controller is secondary in many rich-client applications, and, in fact, many user interface frameworks implement the roles as one object. In Web applications, on the other hand, the separation between view (the browser) and controller (the server-side components handling the HTTP request) is very well defined.

    Impelmenting MVC

    Microsoft has published ASP.NET MVC Framework if you want to use MVC in your web porject.

    The Model-View-Controller (MVC) pattern is an architectural design principle that separates the components of a Web application. This separation gives you more control over the individual parts of the application, which lets you more easily develop, modify, and test them.

    ASP.NET MVC is part of the ASP.NET framework. Developing an ASP.NET MVC application is an alternative to developing ASP.NET Web Forms pages; it does not replace the Web Forms model.

    Scott Gu says “If you are looking to build your web applications using a MVC approach, I think you’ll find this new ASP.NET MVC Framework option very clean and easy to use.  It will enable you to easily maintain separation of concerns in your applications, as well as facilitate clean testing and TDD.” Scott has written a series of blog posts on this new addition to the ASP.NET family. Read them:

    Note: However, please do not blindly use MVC pattern for each and every website that you create. Like most of the design Patterns, the MVC has its own disadvantages like performance hits and writing extra code.Make sure you dont take the pain without a reason.

    Again, MVC model is only an additional model/approach to develop ASP.NET applications and not a replacement for the existing rendering ASP.NET framework.

    When to Create an MVC Application

    You must consider carefully whether to implement a Web application by using either the ASP.NET MVC framework or the ASP.NET Web Forms model. The MVC framework does not replace the Web Forms model; you can use either framework for Web applications. (If you have existing Web Forms-based applications, these continue to work exactly as they always have.)

    Before you decide to use the MVC framework or the Web Forms model for a specific Web site, weigh the advantages of each approach.

    Advantages of an MVC-Based Web Application

    The ASP.NET MVC framework offers the following advantages

    • It makes it easier to manage complexity by dividing an application into the model, the view, and the controller.
    • It does not use view state or server-based forms. This makes the MVC framework ideal for developers who want full control over the behavior of an application.
    • It uses a Front Controller pattern that processes Web application requests through a single controller. This enables you to design an application that supports a rich routing infrastructure. For more information, see Front Controller.
    • It provides better support for test-driven development (TDD).
    • It works well for Web applications that are supported by large teams of developers and for Web designers who need a high degree of control over the application behavior.

    Advantages of a Web Forms-Based Web Application

    The Web Forms-based framework offers the following advantages:

    • It supports an event model that preserves state over HTTP, which benefits line-of-business Web application development. The Web Forms-based application provides dozens of events that are supported in hundreds of server controls.
    • It uses a Page Controller pattern that adds functionality to individual pages. For more information, see Page Controller.
    • It uses view state on server-based forms, which can make managing state information easier.
    • It works well for small teams of Web developers and designers who want to take advantage of the large number of components available for rapid application development.
    • In general, it is less complex for application development, because the components (the Page class, controls, and so on) are tightly integrated and usually require less code than the MVC model.

    I would like to discuss about ASP.Net MVC in separate dedicated article, for the time being you may explore the contents published by microsoft.

    Note : Because ASP.NET MVC does not maintain state information by using view state, you must find other ways to manage state information, if you need it. In addition, server controls that rely on view state and postback will not work as designed in an ASP.NET MVC application. Therefore, you should not use controls such as the GridView, Repeater, and DataList controls.

    Model View Presenter (MVP)

    MVP is a derivative of MVC, mostly aimed at addressing the “Application Model” portion of MVC and focusing around the observer implementation in the MVC triad. Instead of a Controller, we now have a Presenter, but the basic idea remains the same – the model stores the data, the view is a representation of that data (not necessarily graphical), and the presenter coordinates the application.

    Separate the responsibilities for the visual display and the event handling behavior into different classes named, respectively, the view and the presenter. The view class  manages the controls on the page and it forwards user events to a presenter class. The presenter contains the logic to respond to the events, update the model (business logic and data of the application) and, in turn, manipulate the state of the view.

    To facilitate testing the presenter, make the presenter have a reference to the view interface instead of to the concrete implementation of the view. By doing this, you can easily replace the real view with a mock implementation to run tests.

    When the model is updated, the view also has to be updated to reflect the changes. View updates can be handled in several ways. The Model-View-Presenter variants, Passive View and Supervising Controller, specify different approaches to implementing view updates.

    In Passive View, the presenter updates the view to reflect changes in the model. The interaction with the model is handled exclusively by the presenter; the view is not aware of changes in the model.

    In Supervising Controller, the view interacts directly with the model to perform simple data-binding that can be defined declaratively, without presenter intervention. The presenter updates the model; it manipulates the state of the view only in cases where complex UI logic that cannot be specified declaratively is required.

    The decision to use Passive View or Supervising Controller primarily depends on how testable you want your application to be. If testability is a primary concern in your application, Passive View might be more suitable because you can test all the UI logic by testing the presenter. On the other hand, if you prefer code simplicity over full testability, Supervising Controller might be a better option because, for simple UI changes, you do not have to include code in the presenter that updates the view. When choosing between Passive View and Supervising Controller, consider the following:

    • Both variants allow you to increase the testability of your presentation logic.
    • Passive View usually provides a larger testing surface than Supervising Controller because all the view update logic is placed in the presenter.
    • Supervising Controller typically requires less code than Passive View because the presenter does not perform simple view updates.

    You can implement the interaction with the model in several ways. For example, you can implement the Observer pattern. This means that the presenter receives events from the model and updates the view as required. You may explore the Observer pattern in this artcile.

    MVC vs MVP

    • With MVC, it’s always the controller’s responsibility to handle mouse and keyboard events.
    • With MVP, GUI components themselves initially handle the user’s input, but delegate to the interpretation of that input to the presenter.
    • In modern GUI systems, GUI components themselves handle user input such as mouse movements and clicks, rather than some central controller. Thus MVP pattern is widely used in WinForms, .NET SmartClient Factory, etc.
    • In most web architectures, the MVC pattern is used (e.g. Struts, ASP.NET MVC etc)
    • MVP is a derivative of MVC, mostly aimed at addressing the “Application Model” portion of MVC and focusing around the observer implementation in the MVC triad. Instead of a Controller, we now have a Presenter, but the basic idea remains the same – the model stores the data, the view is a representation of that data (not necessarily graphical), and the presenter coordinates the application.
    • In MVP the Presenter gets some extra power. It’s purpose is to interpret events and perform any sort of logic necessary to map them to the proper commands to manipulate the model in the intended fashion. Most of the code dealing with how the user interface works is coded into the Presenter, making it much like the “Application Model” in the MVC approach.

    Presentation Model (PM)

    Model View ViewModel (MVVM)

    Continues with article “All About MVVM“

  • BoundedContext – DDD

    Earlier in the article Software Architecture Patterns we briefly discussed domain driven design. In this article we will take a real example and dive into best practices and design of solution based on hypothetical problem or scenario.

    Bounded Context is a central pattern in Domain-Driven Design and It is the focus of DDD’s strategic design section which is all about dealing with large models and teams. The DDD deals with large models by dividing them into different Bounded Contexts and being explicit about their interrelationships.

    Strategic design  deals with situations that arise in complex systems, larger organizations, interactions with external system.Strategic design decisions are made by teams, or even between teams. Strategic design enables the goals of DDD to be realized on a larger scale, for a big system or in an application that fits in an enterprise-wide network.

    DDD is about designing software based on models of the underlying domain. A model acts as a Ubiquitous language to help communication between software developers and domain experts. It also acts as the conceptual foundation for the design of the software itself.

    It is hard to model a larger domain and build a single unified model. In real world, small domain models are build and together they represent the larger domain. Now lets image a real world example from electricity utility – smart meters ! –  here the word “meter” meant subtly different things to different domain experts coming from different parts of the organization. Lets try to understand the domain and try to break into sub domain models.

    Smart meters are the next generation of gas and electricity meters and offer a range of intelligent functions.The smart metering system is made up of: one electricity smart meter, one gas smart meter, a communications hub and an in-home display unit-the smart energy monitor on which you can view your energy.Smart meters measure actual, total gas and electricity usage and put consumers in control of their energy use, allowing them to adopt energy efficiency measures that can help save money on their energy bills.

     

    smart meter

     

    Now lets split the into domains and sub domains

    smart domain

     

    In the above diagram the subdomain build on foundation however one sub domain is interrelated to one or more other sub domain. They don’t exists in isolation in real world. The total unification of the domain model for a large system will not be feasible. So instead DDD divides up a large system into Bounded Contexts, each of which can have a unified model.

    A bounded context typically represents a slice of the overall system with clearly defined boundaries separating it from other bounded contexts within the system. If a bounded context is implemented by following the DDD approach, the bounded context will have its own domain model and its own ubiquitous language.

    A bounded context is the context for one particular domain model. Similarly, each bounded context (if implemented following the DDD approach) has its own ubiquitous language, or at least its own dialect of the domain’s ubiquitous language, entities, services etc. as shown below.

    Bounded Context

    Bounded Contexts have both unrelated concepts – such as a support ticket only existing in a customer support context, but also share concepts such as products and customers both exists in sales and support contexts.

    A large complex system can have multiple bounded contexts that interact with one another in various ways. A context map is the documentation that describes the relationships between these bounded contexts. It might be in the form of diagrams, tables, or text.

    In the next series we will dive into how we can apply CRQS (Command Query Responsibility Segregation Pattern) and vertically slice the layered architecture to deliver the highly scalable yet composite solution.