# Troubleshooting

Start with the extension's Console in Grove Manager. It combines `console` output, `grove.log` messages, uncaught JavaScript errors, blocked navigation and sandbox failures, and WebContent termination notices.

When asking for help, include the Mango version, `manifest.json`, the relevant bridge call, and the smallest safe Console excerpt. Remove tokens, message text, local paths, and other private data.

## The package will not load

Check these causes in order:

- `manifest.json` is missing from the selected directory;
- `grove` is not the integer `1`;
- `id` has no dot or starts or ends with a dot;
- a capability string is misspelled or undocumented;
- `entry`, `settings`, `icon`, `readme`, or a screenshot does not exist;
- a path is absolute, escapes with `..`, resolves through a symlink outside the package, or names a directory;
- the README is not Markdown or a screenshot is not PNG, JPEG, or WebP;
- an upload contribution is not a headless Root, lacks `uploads.provide`, duplicates an identifier, or has an invalid limit.

For a built project, inspect the final output directory or `.grove` archive rather than the source tree. Vite and similar tools may copy `manifest.json` but omit files that are not part of their public/static asset pipeline.

## An event never arrives

Verify all three parts of the route:

1. the manifest requests the documented capability;
2. the user accepted that capability for the installed package;
3. the running instance is bound to the channel, query, or server scope where the event occurs.

Application-scoped Roots intentionally receive no IRC event stream.

Ordinary `message` delivery is live-only. A Root that starts after bouncer history arrives should not expect those historical rows as new `message` events.

Reserved capability names such as `server.events` and `identity` do not have API 1 events or getters. `channel.members` and `channel.metadata` are implemented through the retained `channelSnapshots` event.

## A bridge call does nothing

Most Grove methods are fire-and-forget; a native rejection appears in the Console rather than as a JavaScript exception. Confirm:

- the matching capability is granted;
- the call is valid for the surface and scope;
- a targeted send uses the correct query permission;
- a server-scoped `{ channel }` send has `network.send`, names one strict channel, and the network has a confirmed matching join;
- a registered identifier was declared and has a valid shape;
- a composer method is called only by an exclusive composer utility;
- the Root has not stopped or lost its grant.

Built-in slash commands cannot be overridden. Command collisions between extensions are first-wins.

## Fetch or WebSocket is blocked

The exact scheme and host must appear in `hostPermissions`. `https://api.example.com` does not grant `wss://api.example.com`, a sibling subdomain, or cleartext access. The only narrow wildcard is a complete leftmost label such as `https://*.example.com`; it matches one direct subdomain, not the apex or a deeper name. Other partial wildcards are not supported.

A settings page always has an empty network allow-list. Save configuration there, then let the running extension perform the request.

Navigation, `window.open`, frames, forms, WebRTC, and undeclared beacons are intentionally blocked and are not supported network strategies.

## Persistent state disappears or leaks across scopes

Request and obtain `storage` for persistent Web storage. Without it, local storage is ephemeral.

API 1 shares the WebKit store between instances of the same extension identifier. Include `grove.info.networkID`, scope kind, and target in keys when data must be per-network or per-buffer.

Use `storage.secure` and `grove.secrets` for credentials. The default secret namespace is per network; `grove.secrets.application` is shared across network instances.

## An upload provider is unavailable

Confirm that:

- the installed manifest declares an upload provider;
- the extension is an exclusive headless Root with `scope: "application"` for one destination shared across networks, or `scope: "server"` for deliberately per-network configuration;
- `uploads.provide` is granted;
- the Root is running for that IRC session, or the application-scoped Root has been started and mirrored into it;
- JavaScript called `grove.uploads.register` with the exact declared identifier;
- the selected file matches image and size restrictions;
- endpoint configuration is present and its origin is allowed.

File reads must be positive, inside the file, and no larger than 1 MiB. Return an `http` or `https` URL. A TUS upload location is not necessarily a recipient-accessible download URL.

## A filter behaves unexpectedly

Filters are ordered and may see changes made by an earlier filter. Use `wireText` for what IRC will receive and `displayText` for what Mango will show. Avoid the legacy string replacement form when those values may differ.

A timeout, thrown error, WebContent crash, or invalid result fails open and allows the message. Only a valid explicit `deny` drops it. Inspect the Console and keep filter work bounded so it can complete before the native timeout.

## The extension stops in the background

Grove Roots are client-side WebKit runtimes, not server workers. Listen for lifecycle events, reconnect sockets after `foreground` or `pulse`, persist necessary state, and accept that iOS suspension stops reliable execution. Move truly always-on work to a backend and keep the Grove extension as its capability-scoped client.
