docs: add milestone 5b report (frontend bundle host, VerstakPluginAPI)

This commit is contained in:
mirivlad 2026-06-17 17:40:08 +08:00
parent 73c5da8eb5
commit 97989fb282
1 changed files with 187 additions and 0 deletions

View File

@ -0,0 +1,187 @@
# Milestone 5b — Frontend Bundle Host / Plugin API Stub
## Цель
Первый настоящий frontend plugin host слой: shell умеет загружать frontend bundle плагина, давать ему ограниченный VerstakPluginAPI stub и рендерить view/settings panel через зарегистрированный component id.
## Что реализовано
### 1. Bundle Contract
Плагин регистрирует компоненты через глобальную функцию:
```javascript
window.VerstakPluginRegister('verstak.platform-test', {
components: {
'DiagnosticsPanel': {
mount: function(containerEl, props, api) {
// Рендерит UI в containerEl
// containerEl — div, созданный PluginBundleHost
// api — ограниченный VerstakPluginAPI
},
unmount: function(containerEl) {
// Очистка при смене view/unmount
containerEl.innerHTML = '';
}
},
'PlatformTestSettings': {
mount: function(containerEl, props, api) { /*...*/ },
unmount: function(containerEl) { /*...*/ }
}
}
});
```
**VerstakPluginAPI** — ограниченный API, передаваемый в mount():
| Метод | Статус | Описание |
|---|---|---|
| `api.pluginId` | ✅ Работает | ID плагина |
| `api.capabilities.has(id)` | ✅ Stub | Возвращает false (planned: реальный запрос к registry) |
| `api.events.publish(type, payload)` | ✅ Stub | Логирует в console (planned: event bus bridge) |
| `api.events.subscribe(type, handler)` | ✅ Stub | Логирует в console (planned: event bus bridge) |
| `api.settings.read(key)` | ✅ Stub | Возвращает null (planned: backend storage namespace) |
| `api.settings.write(key, value)` | ✅ Stub | Логирует в console (planned: backend storage) |
| `api.commands.execute(id, args)` | ✅ Stub | Логирует в console (planned: command execution) |
### 2. Безопасная резолюция asset path
**Backend методы:**
| Метод | Описание |
|---|---|
| `GetPluginFrontendInfo(pluginID)` | Возвращает frontend metadata (entry, style, rootPath, name, icon, version) |
| `GetPluginAssetContent(pluginID, assetPath)` | Читает файл из директории плагина с валидацией безопасности |
**Проверки безопасности:**
- Абсолютные пути (начинающиеся с `/` или `\`) — отклоняются
- Path traversal (`..`) — отклоняется
- Выход за пределы plugin root — отклоняется через `filepath.Abs` + `strings.HasPrefix`
### 3. FrontendPluginHost
**PluginBundleHost.svelte** — загружает и рендерит плагин бандлы:
1. Получает plugin frontend info через `GetPluginFrontendInfo()`
2. Если у плагина есть frontend entry — загружает JS контент через `GetPluginAssetContent()`
3. Выполняет bundle через `new Function(content)` (безопасно: нет доступа к внешней области видимости)
4. Ждёт вызов `VerstakPluginRegister` и находит компонент по componentId
5. Создаёт `VerstakPluginAPI` и вызывает `component.mount(container, props, api)`
6. При смене view — вызывает `component.unmount(container)` и очищает
**Error boundary:**
- Если bundle не загружается — fallback с pluginID, componentId, error text
- Если компонент не найден — показывает доступные components
- Если mount выбрасывает исключение — fallback без падения shell
- Все состояния: idle, loading, error, loaded
### 4. ViewContainer.svelte
- Проверяет наличие frontend bundle у плагина
- Если есть — рендерит PluginBundleHost
- Если нет — показывает "frontend bundle not available" placeholder
- Badge в заголовке: "frontend bundle" (зелёный) или "no frontend bundle" (красный)
### 5. PluginManager — Settings Panel
- Убран hardcoded platform-test settings form
- Settings panel рендерится через PluginBundleHost, если у плагина есть frontend entry
- Если нет — показывает "Settings panel frontend bundle not available"
### 6. platform-test plugin — Real Frontend Bundle
**`frontend/dist/index.js`** (14.6 KB):
- Регистрирует компоненты через `VerstakPluginRegister`
- Диагностическая панель:
- Plugin name, version, ID
- "✅ Frontend Bundle Loaded" badge
- Test results summary
- Capabilities status section
- API methods info
- Settings panel:
- Plugin name + ID
- Interactive counter (increment/decrement/reset)
- Demo settings list
- Темная тема (совпадает с shell)
**`frontend/style.css`** (4.9 KB):
- Shared dark-theme styles
- Используется обоими компонентами
### 7. Тесты
**Backend (11 новых тестов в `internal/api/app_test.go`):**
| Тест | Проверяет |
|---|---|
| GetPluginFrontendInfo (known) | Полные данные для плагина с frontend |
| GetPluginFrontendInfo (no frontend) | Статус "no-frontend" |
| GetPluginFrontendInfo (unknown) | Статус "not-found" |
| GetPluginAssetContent (existing) | Чтение существующего файла |
| GetPluginAssetContent (style) | Чтение style.css |
| GetPluginAssetContent (absolute path) | Отклонение `/` и `\` |
| GetPluginAssetContent (path traversal) | Отклонение `..` |
| GetPluginAssetContent (path escape) | Отклонение выхода за root |
| GetPluginAssetContent (not found) | Ошибка для неизвестного pluginID |
| GetPluginAssetContent (no frontend) | Ошибка если нет frontend |
| GetPluginAssetContent (missing file) | Ошибка если файл не существует |
**Smoke test (frontend bundle checks):**
- Manifest объявляет `frontend.entry = "frontend/dist/index.js"`
- Файл бандла существует на диске
- Бандл содержит `"VerstakPluginRegister"`
- Компоненты `DiagnosticsPanel` и `PlatformTestSettings` зарегистрированы
### 8. Security Constraints
| Сценарий | Результат |
|---|---|
| frontend entry `../etc/passwd` | Отклоняется (path traversal) |
| frontend entry `/etc/passwd` | Отклоняется (absolute path) |
| Плагин без frontend | Не ломает UI, показывает placeholder |
| Плагин с missing entry | Error fallback с понятным сообщением |
| Bundle execution error | Error fallback, shell не падает |
| Компонент не найден в bundle | Error fallback со списком доступных components |
## Изменённые файлы
### verstak-desktop
| Файл | Изменение |
|---|---|
| `internal/api/app.go` | + `GetPluginFrontendInfo()`, `GetPluginAssetContent()` с path validation |
| `internal/api/app_test.go` | **NEW**: 11 тестов |
| `frontend/src/lib/plugin-host/VerstakPluginAPI.js` | **NEW**: Bundle contract + API stub |
| `frontend/src/lib/plugin-host/PluginBundleHost.svelte` | **NEW**: Загрузка/рендер бандлов, error boundary |
| `frontend/src/lib/shell/ViewContainer.svelte` | Обновлён: PluginBundleHost вместо placeholder |
| `frontend/src/lib/plugin-manager/PluginManager.svelte` | Обновлён: Settings через PluginBundleHost |
| `cmd/smoke-platform/main.go` | Обновлён: frontend bundle checks |
| `docs/PLUGIN_RUNTIME.md` | Обновлён: bundle contract, security |
### verstak-official-plugins
| Файл | Изменение |
|---|---|
| `plugins/platform-test/frontend/src/index.js` | **NEW**: DiagnosticsPanel + SettingsPanel |
| `plugins/platform-test/frontend/style.css` | **NEW**: Dark theme styles |
| `plugins/platform-test/frontend/dist/index.js` | **REPLACED**: VerstakPluginRegister contract |
### verstak-docs
| `docs/MILESTONE_PLATFORM_RUNTIME_5b.md` | **NEW**: этот документ |
## Проверки
```
go test ./internal/... -count=1 → ✅ 56 PASS (all packages)
go vet ./... → ✅ clean
cd frontend && npm run build → ✅ built (72.98 KB gzip:21.64 KB)
bash scripts/smoke-platform.sh → ✅ 4 теста (plugin + enable/disable + workspace + contributions + frontend bundle)
bash scripts/build.sh → ✅ wails build
```
## Non-goals (не реализовано)
- Official notes/files/editor extraction
- Backend sidecar runtime
- Secrets
- Полноценный command palette
- Remote plugin registry
- Прямой доступ плагина к Wails backend methods (кроме VerstakPluginAPI)