Lifecycle

An Egglet has three relevant phases: boot (when Egg launches and the host scans manifests), activate (when the entry module runs and registers what it needs), and deactivate (when the Egglet is stopped, reloaded, or uninstalled). This page covers the order, what state survives each transition, and the operational surfaces (Settings visibility, usage rollups) that depend on the lifecycle.

Boot and activate

On every app launch, the host runs a single pass over src/egglets/. For each Egglet folder, in order:

  1. Read manifest.json (eagerly resolved at build time by the host’s discovery mechanism).
  2. Validate the manifest. Internal-consistency checks run here, including the rule that synced tables require uses.gateway.syncs.
  3. Run any schema files declared in the manifest’s tables[]. Schema SQL is idempotent and re-runs every boot.
  4. Lazily import the entry module.
  5. Call activate(ctx). The Egglet registers tools, mounts its route, registers ref resolvers, subscribes to IPC, and starts pollers.

If any step throws, the Egglet is marked failed and Settings shows the error on the Egglet’s card. Other Egglets continue to load. A failed Egglet’s tools are not registered, its route is not mounted, and its pollers do not fire. Reload the Egglet from Settings after fixing the cause to retry without restarting Egg.

activate() runs once per process. It is the place to set up everything the Egglet needs for the rest of the session. The function may be async; the host awaits it before activating the next Egglet so registrations land in a deterministic order.

export default defineEgglet({
  async activate(ctx) {
    ctx.log.info("activated v" + ctx.manifest.version);
    ctx.routes.mount(() => import("./MyEggletPage"));
    ctx.tools.register("myEgglet.example", async () => ({ ok: true }));
    ctx.alwaysOn.poller({
      name: "refresh",
      interval: 600,
      handler: refreshAll,
    });
  },
});

Deactivate

An Egglet is deactivated when:

On deactivate, the host:

Tables are not touched by deactivate. The Egglet’s data survives. Re-activating restores everything from the same tables.

export default defineEgglet({
  async activate(ctx) { /* ... */ },
  async deactivate() {
    // Optional. Use for resources the platform doesn't track:
    // open WebSockets, third-party SDKs, manual timers.
  },
});

Uninstall has two paths offered in Settings. Keep data deactivates the Egglet but leaves its tables intact, so reinstalling is a clean restore. Purge data drops the Egglet’s owned tables (those declared with schema; tables declared with physical_name are not touched). Purge cannot be undone.

Versioning

The version field is SemVer and is shown next to the Egglet’s label in Settings. The host treats it as informational; there is no behavioral difference between bumps today.

What does change between versions, in practice:

Future versions of the platform will use uses changes between releases to drive a re-consent prompt to the user. Treat uses as something that may grow over time but should not silently expand without bumping version.

Settings visibility

Each Egglet has a per-user toggle in Settings under its card: Show in menus. When off, the Egglet is omitted from the top-bar Eggs menu, the Egg landing page, the grid menu, and the left sidebar nav. The Egglet still runs, registers its tools, and serves its route; it just does not appear as a navigational target.

The manifest’s menu field controls the default. "auto" (the default) means the Egglet shows in menus unless the user has hidden it. "hidden" means it is hidden by default; the user can enable it from Settings. The user’s explicit choice always wins.

There is no in-menu affordance to hide an Egglet. Settings is the only path for both hiding and unhiding. This is deliberate: an Egglet a user has hidden should not be one click away from re-appearing in its own menu.

Usage rollups

The host tracks every ctx.* call at its chokepoint and rolls up counts per Egglet, per backing, per day. The Settings card surfaces this data in three densities:

The window picker in the section header switches every card and the breakdown between Today (24 hourly buckets), 7d, and 30d (daily buckets in both cases). Rollups persist across restarts.

What the rollups do not cover yet: any activity that happens outside the SDK (an Egglet that calls a daemon endpoint directly, for example), and the sync push axis (uses.gateway.syncs), which is daemon-driven. These appear as Idle in the breakdown until daemon-side reporting lands. The status pill’s tooltip explains the two interpretations: genuinely quiet, or active but invisible to the in-process tracker.

An Egglet author building a new Egglet can use the breakdown as a sanity check. After a session of testing, the Usage tab should reflect the Egglet’s real behavior. If a backing the manifest declares is stuck at Idle, either the code never exercises it or the call is going around the SDK.