# DivisionBox API

## 概述

DivisionBox 是一个轻量级的子窗口系统，基于 `WebContentsView` 实现，用于承载插件 UI、系统工具和调试界面。

## 介绍

**Scope**

本文档描述 **当前已落地** 的 DivisionBox 基础能力（open/close/state + 生命周期事件）。

- 不包含：多视图并行、复杂 Dock 布局等高级能力。
- 生命周期事件已通过 `DivisionBoxSDK` 暴露（`onLifecycleChange` / `onStateChange`）。

> 权限说明：DivisionBox 已接入权限中心（Permission Center），插件需具备 `window.create` 权限。

> 规范：优先使用 SDK；仅在 SDK 不覆盖时使用 transport 通道。

## 核心概念

**生命周期状态**

DivisionBox 有六个生命周期状态：

:::TuffCodeBlock{lang="vue"}
---
code: |
  prepare → attach → active → inactive → detach → destroy
---
:::

| 状态 | 说明 |
|------|------|
| `prepare` | 准备阶段，资源加载中 |
| `attach` | 已附加到窗口 |
| `active` | 活跃状态，用户正在交互 |
| `inactive` | 非活跃状态，可能被缓存 |
| `detach` | 已从窗口分离 |
| `destroy` | 已销毁，资源已释放 |

**DivisionBox 配置**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  interface DivisionBoxConfig {
    /** 加载的 URL */
    url: string

    /** 窗口标题 */
    title: string

    /** 图标（可选） */
    icon?: string

    /** 窗口大小 */
    size?: 'compact' | 'medium' | 'expanded'

    /** 是否保持活跃（后台缓存） */
    keepAlive?: boolean

    /** 关联的插件 ID */
    pluginId?: string

    /** Header 配置（可选） */
    header?: {
      show: boolean
      title?: string
      icon?: string
    }

    /** CoreBox 头部 UI 控制（可选） */
    ui?: {
      showInput?: boolean
      inputPlaceholder?: string
      showResults?: boolean
      initialInput?: string
    }
  }
---
:::

## 快捷键

| 快捷键 | 功能 |
|--------|------|
| `Command/Ctrl+D` | 将当前选中项分离到 DivisionBox |

快捷键可在系统设置中自定义。

## 使用方式

**插件 SDK（推荐）**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  import { useDivisionBox } from '@talex-touch/utils/plugin/sdk'

  const divisionBox = useDivisionBox()

  // 打开 DivisionBox
  const { sessionId } = await divisionBox.open({
    url: 'https://example.com/tool',
    title: '我的工具',
    size: 'medium',
    keepAlive: true
  })

  // 监听生命周期变化
  const unsubscribe = divisionBox.onLifecycleChange((event) => {
    console.log(event.sessionId, event.oldState, event.newState)
  })

  // 关闭 DivisionBox
  await divisionBox.close(sessionId)
  unsubscribe()
---
:::

**从渲染进程打开 DivisionBox**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  import { useTuffTransport } from '@talex-touch/utils/transport'
  import { DivisionBoxEvents } from '@talex-touch/utils/transport/events'

  async function openDivisionBox() {
    const transport = useTuffTransport()
    const response = await transport.send(DivisionBoxEvents.open, {
      url: 'plugin://my-plugin/panel.html',
      title: '我的面板',
      icon: 'ri:dashboard-line',
      size: 'medium',
      keepAlive: true,
      pluginId: 'my-plugin'
    })

    if (response?.success) {
      console.log('Session ID:', response.data.sessionId)
    }
  }
---
:::

**关闭 DivisionBox**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  async function closeDivisionBox(sessionId: string) {
    const transport = useTuffTransport()
    await transport.send(DivisionBoxEvents.close, {
      sessionId,
      options: {
        delay: 0,
        animation: false,
        force: false
      }
    })
  }
---
:::

**获取会话状态**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  async function getSessionState(sessionId: string) {
    const transport = useTuffTransport()
    const response = await transport.send(DivisionBoxEvents.getState, {
      sessionId
    })

    if (response?.success) {
      console.log('State:', response.data.state)
    }
  }
---
:::

**更新会话状态**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  async function updateSessionState(sessionId: string, key: string, value: any) {
    const transport = useTuffTransport()
    await transport.send(DivisionBoxEvents.updateState, {
      sessionId,
      key,
      value
    })
  }
---
:::

**获取所有活跃会话**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  async function getActiveSessions() {
    const transport = useTuffTransport()
    const response = await transport.send(DivisionBoxEvents.getActiveSessions, {})

    if (response?.success) {
      console.log('Active sessions:', response.data)
    }
  }
---
:::

## 插件 SDK

**完整 API**

**`open(config)`**

打开新的 DivisionBox 窗口。

:::TuffCodeBlock{lang="typescript"}
---
code: |
  import { useDivisionBox } from '@talex-touch/utils/plugin/sdk'

  const divisionBox = useDivisionBox()

  const { sessionId } = await divisionBox.open({
    url: 'https://example.com',
    title: 'Web Tool',
    icon: 'ri:tools-line',
    size: 'medium',
    keepAlive: true,
    header: {
      show: true,
      title: 'Custom Title'
    }
  })
---
:::

**`close(sessionId, options?)`**

关闭 DivisionBox 窗口。

:::TuffCodeBlock{lang="typescript"}
---
code: |
  // 简单关闭
  await divisionBox.close(sessionId)

  // 带延迟和动画
  await divisionBox.close(sessionId, {
    delay: 1000,
    animation: true
  })

  // 强制关闭（忽略 keepAlive）
  await divisionBox.close(sessionId, { force: true })
---
:::

**`onStateChange(handler)`**

监听状态变化。

> `onStateChange` 提供简化的状态变更回调；需要完整生命周期信息时使用 `onLifecycleChange`。

:::TuffCodeBlock{lang="typescript"}
---
code: |
  const unsubscribe = divisionBox.onStateChange((data) => {
    console.log(`Session ${data.sessionId} changed to ${data.state}`)
  })

  // 停止监听
  unsubscribe()
---
:::

**`onLifecycleChange(handler)`**

监听完整生命周期变化事件。

:::TuffCodeBlock{lang="typescript"}
---
code: |
  const unsubscribe = divisionBox.onLifecycleChange((event) => {
    console.log(event.sessionId, event.oldState, event.newState)
  })

  // 停止监听
  unsubscribe()
---
:::

**`updateState(sessionId, key, value)`**

更新会话状态数据。

:::TuffCodeBlock{lang="typescript"}
---
code: |
  // 保存滚动位置
  await divisionBox.updateState(sessionId, 'scrollY', 150)

  // 保存草稿
  await divisionBox.updateState(sessionId, 'draft', {
    text: 'Hello world',
    timestamp: Date.now()
  })
---
:::

**`getState(sessionId, key)`**

获取会话状态数据。

:::TuffCodeBlock{lang="typescript"}
---
code: |
  const scrollY = await divisionBox.getState(sessionId, 'scrollY')
  const draft = await divisionBox.getState(sessionId, 'draft')
---
:::

## URL 协议

DivisionBox 支持多种 URL 协议：

| 协议 | 说明 | 示例 |
|------|------|------|
| `plugin://` | 插件资源 | `plugin://my-plugin/index.html` |
| `file://` | 本地文件 | `file:///path/to/file.html` |
| `http(s)://` | 网络资源 | `https://example.com` |
| `tuff://` | 系统内置页面 | `tuff://detached?itemId=xxx` |

## 与 Flow Transfer 集成

DivisionBox 可以作为 Flow Transfer 的目标：

:::TuffCodeBlock{lang="json"}
---
code: |
  {
    "flowTargets": [
      {
        "id": "open-in-panel",
        "name": "在面板中打开",
        "supportedTypes": ["json", "text"],
        "featureId": "open-panel"
      }
    ]
  }
---
:::

当 Flow 触发时，可以在 Feature 中打开 DivisionBox：

:::TuffCodeBlock{lang="typescript"}
---
code: |
  function onFeatureTriggered(featureId: string, query: TuffQuery) {
    if (isFlowTriggered(query)) {
      const flowData = extractFlowData(query)

      // 打开 DivisionBox 显示 Flow 数据
      divisionBox.open({
        url: `/viewer.html?sessionId=${flowData.sessionId}`,
        title: '查看数据'
      })
    }
  }
---
:::

## IPC 通道

| 通道 | 方向 | 说明 |
|------|------|------|
| `division-box:open` | 渲染 → 主 | 打开新会话 |
| `division-box:close` | 渲染 → 主 | 关闭会话 |
| `division-box:get-state` | 渲染 → 主 | 获取会话状态 |
| `division-box:update-state` | 渲染 → 主 | 更新会话状态 |
| `division-box:get-active-sessions` | 渲染 → 主 | 获取所有活跃会话 |
| `division-box:state-changed` | 主 → 渲染 | 状态变化通知 |
| `division-box:session-destroyed` | 主 → 渲染 | 会话销毁通知 |

## 资源限制

- 资源限制与缓存策略以主进程实现为准（`apps/core-app/src/main/modules/division-box/`）。

## 技术原理

- DivisionBox 以 `WebContentsView` 作为承载层，由主进程统一管理生命周期。
- SDK 负责封装窗口创建、状态变更与事件订阅，隔离底层实现。

## 生命周期详解

### prepare 阶段

`prepare` 是 DivisionBox 会话的初始状态，发生在 `open()` 调用后、窗口创建前。

**触发时机**：
- 调用 `divisionBox.open(config)` 且通过下述前置检查后进入；被拒绝的 open 不会到达该状态
- 随后主进程分配 sessionId、创建 DivisionBoxSession 实例

**资源管理**：
- 先检查全局会话上限，通过后才分配任何资源
- 生成 sessionId 并登记到 session map
- 验证配置并应用默认值；构造窗口前先解析插件归属
- 配置了 `keepAlive` 时只是安装状态变更回调，此时**尚未**进入 LRU 缓存——缓存条目在会话首次进入 `inactive` 时加入，回到 `active` 时刷新访问时间，`destroy` 时移除

**异常处理**：

以下三种都会让 `open()` 直接失败，不会产生一个随后再发生状态转换的会话。

- 超过全局会话上限 `MAX_ACTIVE_SESSIONS` → 抛出 `DivisionBoxError`（`LIMIT_EXCEEDED`），在生成 sessionId 之前
- 请求 UI view 但缺少 `pluginId`，或 `pluginId` 找不到已加载插件 → 抛出 `DivisionBoxError`（`CONFIG_ERROR`）
- 非 authoritative 调用方请求 UI view，或 `pluginId` 与调用插件不符 → 抛出 `DivisionBoxError`（`PERMISSION_DENIED`），在 IPC 边界拦截，不会进入 manager

### attach 阶段

`attach` 发生在窗口创建完成后。

**触发时机**：
- 窗口创建完成后，`createWindow()` 末尾显式调用 `setState(attach)` 进入该状态
- 状态推进完全由显式 `setState` 调用驱动，主进程并未监听 `did-start-loading` 等 Electron 加载事件

**资源管理**：
- 创建 WebContentsView 实例
- 加载指定 URL
- 注册 IPC 通道监听
- 如果是从 CoreBox detach，转移 UI view 所有权

**异常处理**：
- 窗口创建失败 → 清理窗口并抛出 `DivisionBoxError`（`RESOURCE_ERROR`）
- URL 加载失败 → `loadURL()` 抛错并传递给调用方，管理器将会话移出注册表；当前没有 `did-fail-load` / `render-process-gone` 的事件监听

### active 阶段

`active` 表示 DivisionBox 正在被用户交互。

**触发时机**：
- `attachUIView()` 在 `loadURL()` 完成后显式调用 `setState(active)`
- 从 CoreBox 转移已有视图时，`attachExistingUIView()` 附加完成后显式进入 `active`；无 UI 视图的会话由管理器在窗口创建后直接置为 `active`
- 从 `inactive` 恢复同样通过显式 `setState` 完成，没有基于窗口焦点的自动切换

**资源管理**：
- 正常运行状态，无需特殊资源管理
- 可以安全地执行 IPC 通信

### inactive 阶段

`inactive` 表示 DivisionBox 处于非活跃状态，可能被缓存。

**触发时机**：
- 当前没有自动触发来源：主进程未监听 `blur` 或窗口遮挡等事件
- 目前只能通过 `setState` / SDK 编程式进入；管理器监听到该状态后会把 `keepAlive` 会话放入 LRU 缓存

**资源管理**：
- 如果 `keepAlive=false`，可能触发延迟销毁
- 如果 `keepAlive=true`，保持资源但降低优先级
- LRU 缓存可能在此阶段回收低优先级会话

**异常处理**：
- 长时间 inactive 可能触发内存压力回收
- 超过 LRU 缓存上限时，最久未使用的会话被销毁

### detach 阶段

`detach` 表示会话已从窗口显式分离。

**触发时机**：
- 当前没有自动触发来源，只能通过 `setState` / SDK 编程式进入该状态
- `divisionBox.close(sessionId)` 与窗口 `closed` 事件都会直接进入 `destroy`，不会经过 `detach`

**资源管理**：
- 如果 `keepAlive=true`，会话在首次 `inactive` 时就已进入 LRU 缓存，此处从缓存中等待恢复
- 如果 `keepAlive=false`，直接进入 `destroy` 阶段
- 释放 WebContentsView 和关联资源

**异常处理**：
- 关闭动画可能被中断（`force: true`）
- 缓存满时可能立即销毁而非缓存

### destroy 阶段

`destroy` 是会话的最终状态，资源已完全释放。

**触发时机**：
- 调用 `divisionBox.close(sessionId)` 时，`destroySession()` 直接调用 `destroy()`
- 窗口被用户关闭时，`closed` 事件监听器调用 `destroy()`
- `keepAlive` 会话被 LRU 缓存回收时（缓存溢出触发驱逐）
- 内存压力触发的强制销毁（堆内存超限时驱逐部分缓存会话）

**资源管理**：
- 释放所有关联资源
- 从 session map 中移除
- 从 LRU 缓存中移除
- 注销 IPC 通道监听
- 触发 `session-destroyed` 事件

**异常处理**：
- 此阶段不应有异常，资源必须完全释放
- 如果释放失败，记录错误日志但不阻止状态转换

### 状态转换图

```mermaid
stateDiagram-v2
    [*] --> prepare: open()
    prepare --> attach: 窗口创建完成
    attach --> active: 窗口获得焦点
    active --> inactive: 窗口失去焦点
    inactive --> active: 窗口重新获得焦点
    inactive --> detach: close() / 资源回收
    active --> detach: close() / 资源回收
    attach --> detach: close() / 资源回收
    detach --> [*]: keepAlive=false
    detach --> prepare: keepAlive=true, 恢复
    prepare --> destroy: 加载失败
    attach --> destroy: 加载失败 / 崩溃
    active --> destroy: 崩溃
    inactive --> destroy: LRU 回收 / 内存压力
    destroy --> [*]
```

## 最佳实践

1. **使用 keepAlive**：对于频繁使用的面板，启用 `keepAlive` 提升响应速度
2. **合理设置大小**：根据内容选择合适的 `size`
3. **保存状态**：使用 `updateState/getState` 保存用户数据
4. **监听状态变化**：及时响应 `inactive` 状态，释放不必要的资源
5. **处理销毁事件**：监听 `session-destroyed` 清理相关资源
6. **理解生命周期**：根据业务需求选择合适的生命周期钩子

## 相关文档

- [Flow Transfer API](./flow-transfer.zh.mdc) - 插件间数据流转
- [Plugin Manifest](../reference/manifest.zh.mdc) - 插件配置
