npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@enclosurejs/core

v1.1.0

Published

> [!IMPORTANT] > This is the **zero-dependency kernel** of the Enclosure monorepo. Every other package — `platform-tauri`, `platform-electron`, `platform-web`, and all universal modules — depends on it. It defines the entire application shape without impo

Readme

@enclosurejs/core — Platform-agnostic application kernel

[!IMPORTANT] This is the zero-dependency kernel of the Enclosure monorepo. Every other package — platform-tauri, platform-electron, platform-web, and all universal modules — depends on it. It defines the entire application shape without importing a single platform API.

The Problem

Desktop frameworks lock you into a single runtime: Tauri apps can't run on Electron, Electron apps can't run on Capacitor. Business logic, lifecycle management, and service wiring get entangled with platform-specific APIs, making migration painful and testing slow.

@enclosurejs/core solves this by providing a zero-dependency kernel that defines the entire application shape — DI, lifecycle, modules, plugins, error handling, eventing — without importing a single platform API. Platform packages (platform-tauri, platform-electron, platform-web) implement the contracts; the kernel never knows which one is running.

Architecture

┌─────────────────────────────────────────────────────────┐
│                    Your Application                     │
│  ┌───────────────────────────────────────────────────┐  │
│  │  UI (Svelte / React / VanillaJS)                  │  │
│  │  FrontendAdapter.mount(container, context)        │  │
│  └───────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Modules (Logger, Config, DB, Auth, Renderer)     │  │
│  │  ───── topo-sorted, guaranteed after init ─────   │  │
│  │  Plugins (DevTools, Theme, Analytics)             │  │
│  │  ───── lightweight, may come and go ──────────    │  │
│  └───────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────┐  │
│  │  @enclosurejs/core  (this package — zero deps)      │  │
│  │  DI · Lifecycle · EventBus · Result · Deferred    │  │
│  │  Disposable · BackendAdapter · 26 Capabilities    │  │
│  └───────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────┤
│  BackendAdapter implementations (one per platform):     │
│  ┌────────┐  ┌──────────┐  ┌─────────┐                  │
│  │ Tauri  │  │ Electron │  │   Web   │                  │
│  └────────┘  └──────────┘  └─────────┘                  │
└─────────────────────────────────────────────────────────┘

Dependency rule: core imports nothing — every other package may import core.

Quick Start

import {
    createApp,
    type Module,
    type Plugin,
    type FrontendAdapter,
    createToken,
    WebBackend,
} from '@enclosurejs/core';

// 1. Define a service token
interface Logger {
    log(msg: string): void;
}
const LoggerToken = createToken<Logger>('logger');

// 2. Create a module (installed in topo-sorted order)
const loggerModule: Module = {
    id: 'logger',
    install(ctx) {
        ctx.provide(LoggerToken, { log: (msg) => console.warn(`[app] ${msg}`) });
    },
};

// 3. Create a plugin (lightweight extension)
const devToolsPlugin: Plugin = {
    id: 'devtools',
    backendInstall(ctx) {
        const logger = ctx.context.use(LoggerToken);
        logger.log('DevTools activated');
    },
};

// 4. Create a frontend adapter (framework-agnostic)
const frontend: FrontendAdapter = {
    mount({ container, context }) {
        const logger = context.use(LoggerToken);
        container.textContent = 'Hello from Enclosure!';
        logger.log('Frontend mounted');
    },
};

// 5. Wire everything together
const app = createApp({
    backend: new WebBackend(),
    frontend,
    container: document.getElementById('app')!,
    modules: [loggerModule],
    plugins: [devToolsPlugin],
});

await app.start();
// created → initializing (modules + backend plugins) → ready → running (frontend mount + frontend plugins)

How It Works

Two Error Paths

@enclosurejs/core separates errors by intent:

  • throw CoreError — for programming errors and invariant violations (token not found, circular dependency, invalid phase transition). These should never happen in correct code. CoreError carries structured context: { domain, code, entity, message }.
  • return Result<T, E> — for expected failures (no GPU adapter, invalid config, user cancellation). Callers must check .ok before accessing .value, making error handling explicit and exhaustive.

DI Resolution Chain

Context forms a tree. When you call context.use(token):

child context  →  check local bindings  →  found? return value
                                           ↓ not found
               →  walk to parent         →  check local bindings  →  found? return value
                                                                     ↓ not found
               →  walk to grandparent    →  ...
                                           ↓ root reached, still not found
               →  throw CoreError('context', 'TOKEN_NOT_FOUND', ...)

tryUse() follows the same chain but returns undefined instead of throwing. Singleton factories are cached per resolving context (not per defining context), so a child and parent resolve to the same singleton when the factory lives in the parent.

Lifecycle State Machine

created ──→ initializing ──→ ready ──→ running ──→ stopping ──→ stopped
                                                                  │
                                                             (terminal)

Each transition runs registered hooks sequentially. start() advances three steps (created → running), stop() advances to stopped from any non-terminal phase (even partial startup). The finally block guarantees the terminal stopped state is reached regardless of hook errors. Invalid transitions throw immediately — you can't go backwards or skip phases.

Module Install Order

Modules declare dependencies via requires: string[]. topoSortModules uses Kahn's algorithm to produce a deterministic install order. Ties are broken alphabetically. The algorithm detects three error classes before any install runs:

  1. Duplicate IDs — two modules with the same id
  2. Missing dependencies — a module requires an ID that isn't registered
  3. Circular dependencies — A requires B, B requires A (directly or transitively)

Plugin Isolation

Plugins can fail without crashing the app. If onPluginError is provided to createApp(), plugin exceptions during backendInstall, frontendInstall, or deactivate are caught, forwarded to the callback, and emitted as plugin:error events. The plugin is rolled back (provided tokens removed, disposables cleaned up) and plugin:installed/plugin:reloaded events are suppressed. Without the callback, plugin errors propagate as CoreError — fail-fast by default, opt-in resilience.

Plugin ctx.events.on()/ctx.events.once() subscriptions and ctx.onPhase() lifecycle hooks are automatically tracked and disposed when the plugin is uninstalled — no manual cleanup needed by plugin authors.

API

Kernel primitives

| Export | Kind | Purpose | | ------------------------ | ------- | ----------------------------------------------------------- | | CoreError | class | Structured error with domain, code, entity, message | | isCoreError(e) | guard | Narrows unknown to CoreError | | Result<T, E> | type | { ok, value } or { ok, error } — error-as-value | | ok(v) / err(e) | factory | Construct Result values | | unwrap / unwrapOr | fn | Extract value or throw / fallback | | mapResult / mapError | fn | Transform Result without unwrapping | | isOk / isErr | guard | Type-narrowing for Result | | BrandedId<Brand> | type | Compile-time safe numeric handle (number & { __brand }) | | createBrandedId<B>(n) | factory | Cast a number to a branded handle |

Dependency injection

| Export | Kind | Purpose | | ------------------------ | ------- | ------------------------------------------------------------ | | Token<T> | type | Identity key for DI (frozen object reference) | | createToken<T>(key) | factory | Create a typed token | | Context | class | Hierarchical DI container with parent chain | | Context.provide | method | Bind a value (throws on duplicate) | | Context.override | method | Replace or create binding (no throw) | | Context.provideFactory | method | Bind a lazy factory (singleton by default) | | Context.use | method | Resolve token (throws if missing) | | Context.tryUse | method | Resolve token (returns undefined if missing) | | Context.createChild | method | Scoped child that inherits from parent | | Context.useOrDefault | method | Resolve token or return a provided default | | Context.has | method | True if token exists in this or any ancestor | | Context.find | method | Returns nearest context that holds the token | | Context.removeLocal | method | Delete a binding from this layer only | | Context.clearLocal | method | Drop all local bindings (parent survives) | | Context.getAllTokens | method | Union of tokens across the full hierarchy | | Context.getTokensByTag | method | Discover bindings by tag across hierarchy | | Context.capabilities | method | ReadonlySet<string> of all token keys — for conditional UI | | Context.getStats | method | Depth, local/total binding counts |

Async & lifecycle

| Export | Kind | Purpose | | -------------------------- | --------- | -------------------------------------------------------------------------------------------- | | retry(fn, opts) | fn | Repeats fn with exponential backoff, jitter, and AbortSignal support | | withTimeout(promise, ms) | fn | Races a promise against a timer — rejects with CoreError on timeout | | Deferred<T> | class | Externally-controlled Promise with .resolve() / .reject() | | Deferred.timeout(ms) | static | Create a deferred that auto-rejects after timeout | | Deferred.resolve(value) | static | Ready-settled instance for bridging or tests | | Deferred.reject(reason) | static | Pre-rejected instance (suppresses unhandled rejection) | | deferred.timeout(ms) | method | Set auto-reject timer on existing instance | | deferred.abort(signal) | method | Reject on AbortSignal | | DisposableGroup | class | LIFO cleanup group (sync dispose + async disposeAsync) | | DisposableGroup.add | method | Register Disposable | | DisposableGroup.addAsync | method | Register AsyncDisposable | | DisposableGroup.addFn | method | Register plain teardown function | | DisposableGroup.addChild | method | Nest another group for async LIFO ordering | | Pool<T> | interface | Generic resource pool (acquire / release lifecycle) | | createPool(opts) | factory | Creates a pool with create, reset, destroy, maxIdle options | | EventBus<E> | class | Typed pub/sub with on, once, emit, off, waitFor | | EventBus.waitFor | method | One-shot promise, integrates with Deferred (timeout + signal) | | EventBus.getStats | method | Topic and listener counts for metrics/debugging | | EventBus.clear | method | Remove all topics and listeners (teardown / tests) | | Lifecycle | class | Deterministic state machine: created → initializing → ready → running → stopping → stopped | | Lifecycle.advance | method | One forward step in the phase graph | | Lifecycle.start | method | Advance created → running (3 steps) | | Lifecycle.stop | method | Advance any non-terminal phase → stopped | | Lifecycle.onEnter | method | Register hook for a specific phase — returns Disposable that removes it |

Application layer

| Export | Kind | Purpose | | ---------------------- | --------- | ------------------------------------------------------------------------------ | | App | class | Convergence point: modules + backend + frontend + plugins | | App.reloadPlugin(id) | method | Deactivate → dispose → remove tokens → re-install a single plugin at runtime | | createApp(opts) | factory | Create an App instance | | BackendAdapter | interface | Platform bridge (invoke + on + once) | | WebBackend | class | Default in-browser adapter | | TestBackend | class | Test double (records calls, push events) | | FrontendAdapter | interface | VanillaJS mount(ctx) — core doesn't know your framework | | Module | interface | Skeleton service (id, requires?, install, dispose?) | | Plugin | interface | Extension (backendInstall?, frontendInstall?, deactivate?) | | topoSortModules | fn | Kahn's algorithm — deterministic, detects cycles | | WidgetRegistry | interface | Slot-based UI composition registry with ordering and events | | createWidgetRegistry | factory | In-memory WidgetRegistry — lazy re-sort per slot | | registerWidget | fn | Register widget + auto-schedule unregister in ctx.disposable | | WidgetRegistryToken | token | Well-known: resolves WidgetRegistry (auto-provided when frontend is present) | | BackendToken | token | Well-known: resolves BackendAdapter | | LifecycleToken | token | Well-known: resolves Lifecycle | | EventBusToken | token | Well-known: resolves EventBus | | AppToken | token | Well-known: resolves App | | capabilityTokenMap | const | Maps 26 capability short names to DI tokens (for plugin-loader) | | ENCLOSURE_VERSION | const | Semver of @enclosurejs/core (for plugin compatibility checks) |

Capability contracts (26 services — interfaces + tokens)

Every capability lives in core/src/capabilities/ and exports a Token + a service interface. Platform packages provide implementations; application code consumes tokens. One exception: src/path.ts exports createPathService — a portable pure-string implementation of PathService shared by all browser-based platforms. Full API reference with method signatures, types, and usage examples: capabilities/README.md.

| Tier | Tokens | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 — Core | DialogToken, WindowToken, IpcToken, FileSystemToken, ShellToken, ClipboardToken | | 2 — Extended | NotificationToken, StorageToken, HttpClientToken, UpdaterToken, PathToken, SystemInfoToken, TrayToken, ShortcutToken, DisplayToken, PrintToken, SerialToken, FileWatcherToken, DeepLinkToken, DatabaseToken, AppLifecycleToken, ThemeToken, AutoLaunchToken, PowerMonitorToken, KeychainToken, UsbToken |

Configuration

Zero configuration. @enclosurejs/core has no config files, no build-time flags, no environment variables. It exports pure TypeScript — the consuming app decides what to wire.

Types Exported

Types other packages depend on:

| Type | Used by | | ----------------------------------------------- | ---------------------------------- | | BackendAdapter | all platform-* packages | | FrontendAdapter | all frontend integrations | | Module / InstallContext | any package providing a module | | Plugin / PluginFrontendContext | any package providing a plugin | | Token<T> / Context | everything that participates in DI | | Result<T, E> / CoreError | all packages for error handling | | Disposable / AsyncDisposable / TeardownFn | cleanup across the codebase | | Pool<T> / PoolOptions<T> | resource pool consumers | | RetryOptions | retry configuration | | EventMap / Listener | custom event buses | | Phase / LifecycleHook | lifecycle integration | | BrandedId<B> | SoA/ECS numeric handles | | AppOptions (incl. appId) | every app entry / example | | WidgetRegistry / WidgetDescriptor | UI composition in shell layouts | | All 26 capability interfaces + tokens | platform packages + consumer code |

Safety

Type Safety

  • Token<T> guarantees correct types at use<T>() / provide<T>() call sites.
  • BrandedId<Brand> prevents mixing up numeric handles at compile time.
  • Result<T, E> forces callers to check .ok before accessing .value or .error.
  • EventBus<E> constrains event names and payload types via the E type parameter.
  • All capability interfaces are purely typed — no any, no unknown in public surface.

Error Safety

  • Two explicit error paths: throw CoreError for invariant violations, return Result for expected failures.
  • CoreError carries structured context (domain, code, entity) — no plain new Error().
  • DisposableGroup continues through errors in LIFO order, then throws AggregateError if any failed.
  • Deferred suppresses unhandled rejection on timeout/abort cleanup and pre-rejected instances.

Runtime Safety

  • Lifecycle enforces a strict state machine — invalid transitions throw immediately.
  • Context.provide() prevents silent token shadowing (throws on duplicate).
  • topoSortModules detects missing dependencies and circular references before any install runs.
  • Plugin errors are isolated via onPluginError callback — a failing plugin doesn't crash the app.

Benchmarks

[!NOTE] Environment: Intel Core i7-10510U @ 1.80 GHz (4C/8T, 15W TDP), Windows 10 (22631), Node.js 22.22.0, Vitest 3.2.4. Results collected from clean PowerShell terminal, no IDE.

Context (DI)

| Operation | ops/sec | mean | | ----------------------------------- | ------- | ------- | | provide (first binding) | 2,367 | 0.42 ms | | use — value hit | 2,694 | 0.37 ms | | tryUse — value hit | 2,632 | 0.38 ms | | tryUse — miss (no throw) | 7,142 | 0.14 ms | | has — miss | 8,095 | 0.12 ms | | singleton factory — warm cache | 1,677 | 0.60 ms | | transient factory — every call | 1,899 | 0.53 ms | | child depth=1 — resolve from parent | 2,255 | 0.44 ms | | child depth=5 — resolve from root | 933 | 1.07 ms | | child depth=10 — resolve from root | 508 | 1.97 ms | | child shadows parent — local hit | 4,359 | 0.23 ms |

EventBus

| Operation | ops/sec | mean | | ----------------------------- | ------- | -------- | | construct | 9,733 | 0.10 ms | | emit → 1 listener | 1,263 | 0.79 ms | | emit → 10 listeners | 391 | 2.56 ms | | emit → 100 listeners | 53 | 18.84 ms | | emit → 1,000 listeners | 51 | 19.46 ms | | once (subscribe + first emit) | 1,452 | 0.69 ms | | subscribe + dispose cycle | 175 | 5.73 ms |

Lifecycle

| Operation | ops/sec | mean | | ------------------------------------- | ------- | ------- | | construct + single advance | 1,693 | 0.59 ms | | construct + start (3 advances) | 1,127 | 0.89 ms | | construct + start + stop (5 advances) | 1,341 | 0.75 ms | | advance with 10 hooks per phase | 341 | 2.93 ms | | phase getter (100k reads) | 13,809 | 0.07 ms |

App (end-to-end)

| Operation | ops/sec | mean | | ----------------------------------------------- | ------- | ------- | | createApp() — bare | 1,209 | 0.83 ms | | createApp() — 5 modules, 3 plugins (no start) | 1,137 | 0.88 ms | | bare start + stop | 1,117 | 0.90 ms | | 5 modules (linear deps): start + stop | 865 | 1.16 ms | | 5 modules + 3 plugins: start + stop | 619 | 1.62 ms |

Deferred

| Operation | ops/sec | mean | | ------------------------------ | ------- | ------- | | construct | 1,032 | 0.97 ms | | construct + resolve | 939 | 1.07 ms | | create → resolve → await cycle | 3,814 | 0.26 ms | | static Deferred.resolve | 948 | 1.05 ms | | construct + timeout setup | 862 | 1.16 ms |

DisposableGroup

| Operation | ops/sec | mean | | --------------------------------------------- | ------- | ------- | | construct | 5,126 | 0.20 ms | | 10 items LIFO dispose | 1,411 | 0.71 ms | | 100 items LIFO dispose | 319 | 3.13 ms | | 1,000 items LIFO dispose | 145 | 6.92 ms | | mixed add/addFn/addChild (realistic teardown) | 363 | 2.75 ms |

Result

| Operation | ops/sec | mean | | ---------------------------- | ------- | ------- | | ok() construction | 501 | 2.00 ms | | err(string) construction | 500 | 2.00 ms | | unwrapOr on Ok | 413 | 2.42 ms | | unwrapOr on Err (fallback) | 422 | 2.37 ms | | chained mapResult x 5 | 340 | 2.94 ms |

Analysis

DI resolution is the most performance-critical path — it runs on every context.use() call during module install and at runtime. The Map.has() + Map.get() chain resolves a value binding in ~0.37 ms (batch of operations), with linear degradation through the parent chain: depth=1 adds ~0.07 ms, depth=5 adds ~0.70 ms. For typical apps with 2-3 levels of DI hierarchy, resolution overhead is negligible. tryUse miss (the "token not found" path) is the fastest DI operation at 7.1K ops/sec because it exits immediately without creating an Error object.

EventBus emit scales linearly with listener count: 1 → 10 listeners costs ~3.2x, but 100 → 1,000 shows near-constant cost (53 vs 51 ops/sec) because the snapshot Array.from() dominates. For apps with typical event fan-out (1-10 listeners per topic), emit overhead is under 3 ms.

App lifecycle completes a full 5-module + 3-plugin start/stop cycle in 1.62 ms. This is a one-time cost at startup and shutdown — well within any interactive budget. The topoSortModules overhead for 85 modules (fanout=4, depth=3) is ~31 ms, which means even large applications can boot module ordering in a single frame.

DisposableGroup disposes 1,000 items in LIFO order in ~6.9 ms. Realistic app teardown (mixed add/addFn/addChild) completes in ~2.8 ms. The LIFO guarantee (most recently added is disposed first) ensures correct shutdown ordering.

Bundle Size

| Output | File | Size | | ------------ | ------------ | --------- | | Runtime (JS) | index.js | 50.70 KB | | Types (DTS) | index.d.ts | 51.88 KB | | Total | | 102.58 KB |

Zero external dependencies — the package is pure TypeScript with no runtime imports. The JS bundle contains the entire kernel: DI, Lifecycle, EventBus, Result, Deferred, DisposableGroup, App, BackendAdapter, topoSort. The DTS file includes all 26 capability interfaces + tokens.

Quality

| Metric | Value | | --------------------- | -------------------------------------------------------------------------------------- | | Unit tests | 345 (all pass) | | Benchmarks | 9 suites (DI, EventBus, Lifecycle, Deferred, Disposable, Result, Module, Backend, App) | | Source files | 19 kernel + 26 capabilities = 45 | | Test files | 16 test + 9 bench = 25 | | External dependencies | 0 (devDependencies only: tsup) | | Coverage thresholds | statements >= 90%, branches >= 85%, functions >= 95%, lines >= 90% |

Quality Layers

Layer 1: STATIC ANALYSIS (every commit)
  tsc --noEmit        strict mode, zero errors
  eslint              ESLint 9 flat config, zero warnings
  prettier --check    formatting

Layer 2: UNIT TESTS (every commit)
  345 tests           all kernel primitives, edge cases, path service
  v8 coverage         statements >= 90%, branches >= 85%, functions >= 95%, lines >= 90%

Layer 3: BENCHMARKS (package ready)
  9 suites            DI, EventBus, Lifecycle, Deferred, Disposable, Result, Module, Backend, App

Layer 4: PACKAGE HEALTH
  0 external deps     pure TypeScript, no runtime imports
  tsup build          ESM + DTS output

File Structure

src/
├── errors.ts                 CoreError, isCoreError
├── result.ts                 Result<T,E>, ok, err, unwrap, mapResult, ...
├── branded.ts                BrandedId<Brand>, createBrandedId
├── di.ts                     Token, Context (hierarchical DI container)
├── async.ts                  retry (backoff + jitter), withTimeout
├── deferred.ts               Deferred<T> (externally-controlled Promise)
├── disposable.ts             Disposable, AsyncDisposable, DisposableGroup
├── pool.ts                   Pool<T>, createPool (generic resource pool)
├── events.ts                 EventBus<E> (typed pub/sub + waitFor)
├── lifecycle.ts              Lifecycle (phase state machine)
├── backend.ts                BackendAdapter, WebBackend, TestBackend
├── frontend.ts               FrontendAdapter, FrontendContext
├── module.ts                 Module, InstallContext, topoSortModules
├── plugin.ts                 Plugin, PluginFrontendContext
├── widgets.ts                WidgetRegistry, WidgetDescriptor, createWidgetRegistry, registerWidget
├── app.ts                    App, createApp, well-known tokens, scoped EventBus proxy
├── capability-tokens.ts      capabilityTokenMap (26 short names → Token)
├── version.ts                ENCLOSURE_VERSION
├── path.ts                   createPathService (shared by all platforms)
├── index.ts                  Barrel (re-exports only)
└── capabilities/
    ├── app-lifecycle.ts      AppLifecycleService, AppState
    ├── auto-launch.ts        AutoLaunchService, AutoLaunchOptions
    ├── clipboard.ts          ClipboardService
    ├── database.ts           DatabaseService, Database, DbStatement
    ├── deep-link.ts          DeepLinkService, DeepLinkEvent
    ├── dialogs.ts            DialogService, FileDialogOptions, ...
    ├── display.ts            DisplayService, Display, DisplayBounds
    ├── file-watcher.ts       FileWatcherService, FileWatcher
    ├── fs.ts                 FileSystem, DirEntry, FileStat
    ├── http.ts               HttpClient, HttpRequestOptions, HttpResponse
    ├── ipc.ts                IpcService, IpcMessage
    ├── keychain.ts           KeychainService
    ├── notifications.ts      NotificationService, NotificationOptions
    ├── path.ts               PathService, ParsedPath
    ├── power-monitor.ts      PowerMonitorService, BatteryInfo
    ├── print.ts              PrintService, PrintOptions
    ├── serial.ts             SerialService, SerialConnection
    ├── shell.ts              ShellService, ChildProcess, ExecResult
    ├── shortcuts.ts          ShortcutService
    ├── storage.ts            StorageService, StorageSize
    ├── system-info.ts        SystemInfoService, CpuInfo, MemoryInfo
    ├── theme.ts              ThemeService, Theme, ThemeSource
    ├── tray.ts               TrayService, TrayMenuItem, TrayOptions
    ├── updater.ts            UpdaterService, UpdateInfo, DownloadProgress
    ├── usb.ts                UsbService, UsbDevice, UsbDeviceInfo
    └── windows.ts            WindowService, AppWindow, WindowOpenOptions

__tests__/
├── app.test.ts               62 tests — App lifecycle, module/plugin wiring, installPlugin, reloadPlugin, rollback, scoped EventBus, onPhase disposable
├── app.bench.ts              startup/stop/module-install throughput
├── async.test.ts             20 tests — retry backoff/jitter/abort, withTimeout
├── backend.test.ts           12 tests — WebBackend, TestBackend
├── backend.bench.ts          invoke/event throughput
├── branded.test.ts            7 tests — BrandedId type safety, validation
├── deferred.test.ts          27 tests — resolve/reject/timeout/abort
├── deferred.bench.ts         creation/resolve/timeout throughput
├── di.test.ts                32 tests — Context provide/use/factory/child/tags/capabilities
├── di.bench.ts               provide/use/factory/hierarchy throughput
├── disposable.test.ts        19 tests — sync/async/LIFO/error collection
├── disposable.bench.ts       add/dispose throughput
├── events.test.ts            21 tests — on/off/once/emit/waitFor
├── events.bench.ts           emit/subscribe/waitFor throughput
├── lifecycle.test.ts         14 tests — phase transitions, hooks, errors, removable hooks
├── lifecycle.bench.ts        advance/start/stop throughput
├── module.test.ts            20 tests — topoSort, cycles, duplicates, missing deps
├── module.bench.ts           sort throughput at scale
├── path.test.ts              30 tests — posix/windows separators, edge cases
├── pool.test.ts              19 tests — acquire/release/maxIdle/reset/dispose
├── result.test.ts            18 tests — ok/err/unwrap/map
├── result.bench.ts           creation/unwrap/map throughput
├── capability-tokens.test.ts  3 tests — capabilityTokenMap completeness and token validity
├── version.test.ts            3 tests — ENCLOSURE_VERSION format and package.json sync
└── widgets.test.ts           38 tests — register/unregister/slots/groups/ordering/when/events, registerWidget helper

License

MIT