verstak-docs/docs/MILESTONE_PLATFORM_RUNTIME_...

188 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)