# Plugin Kit Dialog

<PubVersion />

Plugin Kit Dialog is a Flutter dialog that inspects and edits any [`PluginRuntime`](https://plugin-kit.saad-ardati.dev/concepts/runtime/) at runtime. Drop it in once and your users get a three-tab UI for toggling plugins, editing service config, and inspecting the registry. You write zero settings-screens-per-plugin code.

The dialog is built from internal `plugin_kit` plugins. Tabs and field renderers come from the dialog's internal runtime, so host app plugins cannot shadow or replace them. Header actions (Reset all, Cancel, Save) are built-in widgets.

## Install

```yaml
dependencies:
  plugin_kit: ^PUBVER_plugin_kit
  plugin_kit_dialog: ^PUBVER_plugin_kit_dialog
```

`plugin_kit` carries the dart-only declaration types ([`UiConfigurableCapability`](#declaring-configurable-services), `ConfigField`, etc.). `plugin_kit_dialog` adds the Flutter UI on top.

## Quick start

```dart
extractRegion(dialogSnippets, 'dialog-show-dialog')
```

That is it. If your plugins already attach `UiConfigurableCapability`, the Services tab populates itself.

`onSave` is awaited before the dialog closes, so you can persist (write to disk, push to a server, call `runtime.updateSettings(...)`) without racing the dismissal.

## What the three tabs do

### Plugins tab

Enable and disable plugins live. Stable plugins toggle freely; experimental plugins are flagged; plugins declaring `FeatureFlag.locked` cannot be turned off and the runtime logs a configuration error if their dependencies disappear.

![Plugin Kit Dialog Plugins tab showing a grid of registered plugins with enable/disable toggles, stable/experimental tiers, and per-plugin icons and colors](https://plugin-kit.saad-ardati.dev/images/dialog/plugins_tab_dark.png)

*Each tile is one plugin. Decorated tiles get their icons, labels, and accent colors from `PluginKitVisualsPlugin`, a single host-app plugin that maps visuals across the plugin, namespace, and service axes.*

### Services tab

Each resolved winner in `runtime.sessions.lastOrNull?.registry ?? runtime.globalRegistry` that ships a `UiConfigurableCapability` becomes an editable card. Text, numbers, dropdowns, switches, multiline, password, grouped, or custom fields are all supported.

### Advanced tab

A registry inspector with priority chains, winners, shadowed contenders, and a JSON view of the working draft. It inspects `runtime.globalRegistry`, even when sessions are active.

![Plugin Kit Dialog Advanced tab showing the service registry inspector with namespaces, competing registrations, priority badges, and the current winner picked out](https://plugin-kit.saad-ardati.dev/images/dialog/advanced_tab_dark.png)

*Each row is a slot. Chips on the right show registrations in priority order, with the current winner picked out and any meta entries (visuals contributions, wildcard fallbacks) marked. The JSON preview shows the current working `RuntimeSettings` snapshot, not a baseline diff.*

## Declaring configurable services

Configurability is opt-in per registration. Attach a `UiConfigurableCapability` next to any service:

```dart
extractRegion(dialogSnippets, 'dialog-ui-configurable-capability')
```

Saved values flow through `RuntimeSettings.services[Pin('main_agent', ['agent', 'temperature'])].config` (or, if you prefer the dotted shorthand, `pluginId.namespace('agent').service('temperature')`). From there, [Configuration](https://plugin-kit.saad-ardati.dev/concepts/configuration/) covers how the service reads them via `config.getDouble('temperature')` and similar typed accessors.

### Built-in field types

All field types live in `plugin_kit` (Dart-only):

| Field | Renders as |
|---|---|
| `TextConfigField` | Single-line `TextField`. |
| `MultilineConfigField` | Multiline editor with optional moustache-tag chips. |
| `PasswordConfigField` | Obscured input with show/hide toggle. |
| `NumberConfigField` | Slider when both `min` and `max` are set; numeric `TextField` otherwise. `style: NumberFieldStyle.textInput` forces text mode. `isInteger: true` stores `int` and snaps to whole numbers. |
| `DropdownConfigField<T>` | Typed dropdown over `List<DropdownOption<T>>`. |
| `BoolConfigField` | Switch with label and helper text. |
| `GroupConfigField` | Indented sub-section grouping nested fields under a heading. |
| `ExtensionConfigField` | Escape hatch for custom Flutter renderers. See [Custom field renderers](#custom-field-renderers) below. |

Each field carries `key`, `label`, `helperText`, and `defaultValue`. Dotted keys (`provider.api_key`) write to nested maps automatically.

## Visuals: icons, colors, labels

Visuals are a Flutter-only concern, so the canonical attachment path is a single locked `GlobalPlugin` that carries host-app overrides. Three independent maps cover the three things the dialog renders: plugin tiles, namespace section headers, and individual service cards.

```dart
extractRegion(dialogSnippets, 'dialog-visuals-plugin')
```

Because the visuals plugin lives in your host app (which has Flutter), Dart-only plugins still get icons, labels, and colors without importing Flutter. The decoration is keyed by `PluginId`, `Namespace`, or `ServiceId`, so the host owns the map and the plugin source code stays portable.

The plugin registers at priority `1000`, above the default registry priority of `500`, so host overrides beat anything a Flutter plugin self-attaches from its own `register()`. Unknown keys (a plugin or service that does not currently exist) are accepted silently; this lets you keep visuals for plugins that may be enabled later. When no visual is found, cards fall back to a generic gear icon and the theme's primary color.

## Custom field renderers

Need a color picker, file selector, or any other widget? Declare an `ExtensionConfigField` from anywhere (no Flutter dependency at the field site):

```dart
extractRegion(configFieldsSnippets, 'config-field-extension')
```

The default `showPluginKitDialog` and `PluginKitDialogBody` entry points use a private dialog runtime with fixed built-in plugins, so host-runtime renderer registrations are not used.

```dart
extractRegion(dialogSnippets, 'dialog-color-picker-renderer')
```

If a renderer key is unknown when the dialog tries to resolve it, an inline placeholder card surfaces the missing key. The dialog never throws at paint time.

## Theming

Pass a `PluginKitDialogTheme` to override accents, surfaces, and badges:

```dart
extractRegion(dialogSnippets, 'dialog-show-dialog-themed')
```

Or wrap your app with `buildPluginKitDialogDarkTheme()` / `buildPluginKitDialogLightTheme()` to adopt the full Material 3 `ThemeData`.

## Why dart-only declarations matter

The capability and field types live in `plugin_kit`, not in this package. That means a non-Flutter package (server-side service, CLI, shared `common/` library) can declare configurable services without taking a Flutter dependency. The Flutter UI layers on top through:

- `PluginKitVisualsPlugin` (Flutter, host-app side) for icons, labels, and colors across the plugin, namespace, and service axes
- `ExtensionConfigField` plus a registered Flutter renderer for custom widgets

That keeps your shared plugin packages portable. The host app owns the Flutter-only glue.

## Saving and dirty state

The dialog is non-destructive. Edits accumulate in a working draft; nothing reaches the runtime until the user hits **Save**. `onSave` receives the merged `RuntimeSettings`; persist it however you like. Cancel discards the draft, with a confirm prompt if it is dirty.

Overrides matching the active baseline are pruned automatically, so the resulting `RuntimeSettings` stays minimal. Two users with different starting points can save the same diff.
**Reconciling vs persisting:** `onSave` runs before the dialog closes. If you only persist (write to disk), the runtime stays on its old settings until something calls `runtime.updateSettings(...)`. If you want the runtime to converge live as the user clicks Save, call `await runtime.updateSettings(settings)` from `onSave`. See [Settings & Overrides](https://plugin-kit.saad-ardati.dev/guides/settings/) for the reconciliation model.

## Example app

A runnable demo with 20 competing plugins (priority towers on `agent.model`, `agent.system_message`, `retry.policy`, `search.provider`, plus locked and experimental tiers) plus one `PluginKitVisualsPlugin` decorating every plugin, namespace, and service (21 total runtime plugins) lives at [`example/plugin_kit_dialog_demo`](https://github.com/SaadArdati/plugin_kit/tree/main/example/plugin_kit_dialog_demo). Open the [live web build](https://plugin-kit.saad-ardati.dev/dialog), or run it locally with `flutter run` from that directory.

The screenshots above are golden-tested against that demo, so what you see here is exactly what the demo renders.

## Public API

```dart
extractRegion(dialogSnippets, 'dialog-show-dialog')
```

Declarative types come from `plugin_kit`:

```dart
import 'package:plugin_kit/plugin_kit.dart';

UiConfigurableCapability({label, fields, description});
TextConfigField, MultilineConfigField, PasswordConfigField,
NumberConfigField (NumberFieldStyle, isInteger),
DropdownConfigField<T>, DropdownOption<T>,
BoolConfigField, GroupConfigField,
ExtensionConfigField (rendererKey, args),
ConfigField                          // sealed base
ConfigFieldHandle                    // value/reset handle for renderers
```

## Related reading

[Capabilities](https://plugin-kit.saad-ardati.dev/concepts/capabilities/)
  [Configuration](https://plugin-kit.saad-ardati.dev/concepts/configuration/)
  [Settings & Overrides](https://plugin-kit.saad-ardati.dev/guides/settings/)