# Migration Guide — GENC-1351 (Chat persistence & single-element popout)

GENC-1351 adds session **persistence**, **compaction**, a lifecycle menu, and
server-saved **diagnostics** — all additive, no migration needed to adopt or ignore
them. It also reworks the **pop-out/dock model** to a single moving element, and that
change **removes** the multi-view interaction-ownership API that the old two-instance
model needed.

This document covers only the **breaking** changes. They affect you **only if your
host integrates docking** (implements `AiDockProvider`) **or calls the interaction-
ownership API**. Hosts that just embed `<foundation-ai-assistant>` (with or without the
floating bubble) are unaffected.

> Both surfaces are `@beta`. These are deliberate, documented breaking changes on that
> surface, made possible by the single-element refactor below.

---

## Background: one element that moves, not two that clone

Previously the pop-out/dock lifecycle ran **two** assistant instances sharing one
driver/store via a common key: a floating-bubble element plus an auto-created docked
clone. Coordinating those two views required an ownership/election layer so a
side-effecting interaction widget ran on exactly one of them.

GENC-1351 removes the second instance. `FoundationAiPopoutManager` now **moves the one
real element** between the bubble and the page layout. The session survives the move
because the driver, store, and persister live in module-level registries keyed by the
element's identity — not on the element. One connected instance means there is nothing
to elect and nothing to deduplicate, so the ownership API is gone.

---

## 1. Interaction-ownership / host-election API removed

The following are **removed** with no replacement — a single moving element is always
the sole owner of every interaction, so there is nothing to claim or elect:

| Removed | Where |
| --- | --- |
| `FoundationAiAssistant.claimInteractionOwnership()` | `@genesislcap/ai-assistant` |
| `AiDriver.registerHost?` / `unregisterHost?` / `setActiveHost?` / `claimSideEffect?` | `AiDriver` interface |
| `ChatInteraction.ownerHostId` | `@genesislcap/foundation-ai` |

### Migration

**Delete the calls.** If your host called `claimInteractionOwnership()` — e.g. after
restoring a docked assistant from a persisted layout — remove it:

**Before**

```ts
const assistant = getMyAssistantElement();
layout.addPane(assistant);
assistant.claimInteractionOwnership(); // seize ownership for this view
```

**After**

```ts
const assistant = getMyAssistantElement();
layout.addPane(assistant);
// Nothing to claim — the single element is always the interaction owner.
```

- Custom `AiDriver` implementations: drop any `registerHost` / `unregisterHost` /
  `setActiveHost` / `claimSideEffect` methods. They were optional (`?`) and are no
  longer called.
- Nothing reads `ChatInteraction.ownerHostId` anymore; remove any code that set it.

> If you had a defensive optional call — `assistant.claimInteractionOwnership?.()` — it
> already degrades to a no-op and will not throw. You can leave it and remove it later,
> but a direct (non-optional) call now throws `TypeError` and must be removed.

---

## 2. Dock providers MOVE the assistant element (no more clone)

`AiDockProvider.onDock(element)` is unchanged in signature but changed in **contract**:
the `element` you receive is now the **one live assistant**, and you must **re-parent
it by reference** into your layout. Do **not** clone it — a clone is a second, dead
instance with no driver/store wiring.

Correspondingly, the manager **no longer auto-creates a docked (`popout-mode="collapse"`)
element** for you. It hands you the real one and re-homes it on pop-in.

### Before — insert a cloned/placeholder element

```ts
const ASSISTANT_REGISTRATION = {
  // the manager auto-created a collapse-mode element; the layout inserted a
  // clone/placeholder of it
  onDock: async (element: HTMLElement) => {
    const clone = element.cloneNode(true) as HTMLElement;
    layout.addItem({ content: clone });
  },
  onUndock: async () => {
    layout.removeItem('assistant');
  },
  initiateCollapse: () => {
    document.querySelector('my-assistant[popout-mode="collapse"]')
      ?.dispatchEvent(/* … */);
  },
};
```

### After — move the one real element

```ts
const ASSISTANT_REGISTRATION: AiDockProvider = {
  // `element` IS the single live assistant — re-parent it, never clone.
  onDock: async (element: HTMLElement) => {
    layout.addItem({ content: element }); // moves the node into the pane
  },
  // By the time this runs the manager has already moved the element back into the
  // bubble, so only tear down the now-empty layout slot.
  onUndock: async () => {
    layout.removeEmptySlot();
  },
  initiateCollapse: () => {
    getAiPopoutManager()?.collapseIfDocked();
  },
};

getAiPopoutManager()?.registerDockProvider(ASSISTANT_REGISTRATION);
```

Registration/deregistration is unchanged: `registerDockProvider(provider)` on mount,
`deregisterDockProvider()` on unmount, via `getAiPopoutManager()`.

### `adoptAssistant` — for hosts that create the assistant directly in a layout

If your host **creates** the assistant element inside a layout pane (rather than
statically in the bubble's `dialog-content` slot), tell the manager about the one
element and its current docked state so it tracks it, reflects the correct
`popout-mode` (which drives the header button), and hides the empty bubble:

```ts
const assistant = document.createElement('my-assistant');
layoutPane.appendChild(assistant);
getAiPopoutManager()?.adoptAssistant(assistant, /* docked */ true);
```

### Remove second-instance workarounds

Any code that existed to tolerate the old two-instance model is now unnecessary and
should be removed, e.g.:

- de-duplicating shared-bus events emitted by "the other" instance;
- reconciling loading/halo/interaction state between a bubble and a docked view;
- copying identity attributes to a second element to share the session key.

There is one element now, so these have nothing to act on.

---

## Quick reference checklist

- [ ] Remove every call to `claimInteractionOwnership()` (and any
      `?.claimInteractionOwnership()` once you've confirmed it's unused).
- [ ] Custom `AiDriver`: delete `registerHost` / `unregisterHost` / `setActiveHost` /
      `claimSideEffect`.
- [ ] Remove any use of `ChatInteraction.ownerHostId`.
- [ ] `onDock`: re-parent the given element (move it); never `cloneNode` it.
- [ ] Stop expecting an auto-created `popout-mode="collapse"` element — you get the real
      one.
- [ ] `onUndock`: only tear down the empty slot; the element is already back in the
      bubble.
- [ ] Host that creates the assistant in a layout pane: call
      `adoptAssistant(el, docked)`.
- [ ] Delete second-instance dedup / state-reconciliation / attribute-copying
      workarounds.
