Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
590f8a9a6d | ||
|
|
3f71689c84 | ||
|
|
5e7ef495b5 | ||
|
|
78cfc367b3 | ||
|
|
cf26f7c3c1 | ||
|
|
f18fc2f727 | ||
|
|
6f62765e27 | ||
|
|
edecb8e674 | ||
|
|
55a1da4fb6 | ||
|
|
8366815cf2 | ||
|
|
a7563b6016 | ||
|
|
139b2a67b7 | ||
|
|
f59af601d6 | ||
|
|
a6d349f188 | ||
|
|
acabe340e9 | ||
|
|
9b3f3b6afc | ||
|
|
85a947bc73 |
@@ -1,12 +1,12 @@
|
|||||||
# Verstak Platform Docs
|
# Verstak Platform Docs
|
||||||
|
|
||||||
Этот комплект фиксирует новый архитектурный курс Верстака: не монолитное приложение с набором встроенных экранов, а local-first платформа рабочего vault, где пользовательские функции подключаются динамическими плагинами.
|
Этот комплект фиксирует архитектурный курс Верстака: не монолитное приложение с набором встроенных экранов, а local-first платформа рабочего vault, где пользовательские функции подключаются динамическими плагинами.
|
||||||
|
|
||||||
Исходная идея Верстака сохраняется:
|
Исходная идея Верстака сохраняется:
|
||||||
|
|
||||||
> Верстак - локальная рабочая среда, где по каждому клиенту, проекту или делу собраны файлы, заметки, документы, ссылки, действия, журнал и история работы.
|
> Верстак - локальная рабочая среда, где по каждому клиенту, проекту или делу собраны файлы, заметки, документы, ссылки, действия, журнал и история работы.
|
||||||
|
|
||||||
Меняется внутреннее устройство. Заметки, файловый менеджер, редакторы, предпросмотр, журнал, активность, браузерный inbox и подобные части больше не считаются внутренними разделами одного приложения. Они становятся плагинами, которые подключаются к платформе через capability registry, contribution points и permissions.
|
Заметки, файловый менеджер, редакторы, предпросмотр, журнал, активность, браузерный inbox и подобные части — это плагины, которые подключаются к платформе через capability registry, contribution points и permissions.
|
||||||
|
|
||||||
## Документы
|
## Документы
|
||||||
|
|
||||||
@@ -14,14 +14,17 @@
|
|||||||
- [02_Platform_Architecture.md](02_Platform_Architecture.md) - архитектура ядра, runtime, vault, UI shell и plugin host.
|
- [02_Platform_Architecture.md](02_Platform_Architecture.md) - архитектура ядра, runtime, vault, UI shell и plugin host.
|
||||||
- [03_Repositories.md](03_Repositories.md) - разбиение на репозитории и назначение каждого.
|
- [03_Repositories.md](03_Repositories.md) - разбиение на репозитории и назначение каждого.
|
||||||
- [04_Plugin_System.md](04_Plugin_System.md) - динамические плагины, manifest, lifecycle, capabilities, settings, permissions.
|
- [04_Plugin_System.md](04_Plugin_System.md) - динамические плагины, manifest, lifecycle, capabilities, settings, permissions.
|
||||||
- [05_Official_Plugins.md](05_Official_Plugins.md) - состав официальных плагинов и их зависимости через capabilities.
|
- [05_Official_Plugins.md](05_Official_Plugins.md) - состав официальных плагинов, их возможности и текущий статус.
|
||||||
- [06_Migration_Strategy.md](06_Migration_Strategy.md) - как развивать Verstak v2 как отдельную платформу без временных мостов к первой версии.
|
- [06_Migration_Strategy.md](06_Migration_Strategy.md) - стратегия развития платформы и definition of done.
|
||||||
- [07_Full_Implementation_Roadmap.md](07_Full_Implementation_Roadmap.md) - порядок доведения Verstak2 до полной standalone-реализации.
|
- [07_Full_Implementation_Roadmap.md](07_Full_Implementation_Roadmap.md) - полная дорожная карта реализации с текущими статусами фаз.
|
||||||
- [AGENTS.md](AGENTS.md) - инструкция для coding agents, чтобы они не возвращали проект к монолиту.
|
|
||||||
|
Детальная документация по runtime:
|
||||||
|
|
||||||
|
- [Plugin Runtime](../verstak-desktop/docs/PLUGIN_RUNTIME.md) — подробный reference по plugin lifecycle, API, contribution points.
|
||||||
|
|
||||||
## Главный архитектурный инвариант
|
## Главный архитектурный инвариант
|
||||||
|
|
||||||
Core не знает о конкретных функциях вроде "заметки", "файловый менеджер" или "markdown editor". Core знает о:
|
Core не знает о конкретных функциях вроде «заметки», «файловый менеджер» или «markdown editor». Core знает о:
|
||||||
|
|
||||||
- vault;
|
- vault;
|
||||||
- plugin runtime;
|
- plugin runtime;
|
||||||
@@ -33,4 +36,4 @@ Core не знает о конкретных функциях вроде "зам
|
|||||||
- storage API;
|
- storage API;
|
||||||
- UI shell.
|
- UI shell.
|
||||||
|
|
||||||
Все рабочие инструменты поставляются плагинами. Даже официальные плагины должны жить по тем же правилам, что и будущие сторонние.
|
Все рабочие инструменты поставляются плагинами. Даже официальные плагины живут по тем же правилам, что и будущие сторонние.
|
||||||
|
|||||||
@@ -2,11 +2,28 @@
|
|||||||
|
|
||||||
## 1. Организация
|
## 1. Организация
|
||||||
|
|
||||||
Для GitHub/Gitea лучше использовать organization `verstak` или аналогичную общую группу. Внутри нее живут отдельные репозитории одной продуктовой идеи.
|
Проект состоит из шести репозиториев под [github.com/mirivlad](https://github.com/mirivlad):
|
||||||
|
|
||||||
Project board можно вести на уровне organization: roadmap, issues, milestones, cross-repo tasks.
|
- `verstak` — основное desktop-приложение (Core Platform + UI Shell)
|
||||||
|
- `verstak-official-plugins` — монорепозиторий официальных плагинов
|
||||||
|
- `verstak-sdk` — TypeScript SDK, схемы и контракты плагинов
|
||||||
|
- `verstak-browser-extension` — расширение браузера
|
||||||
|
- `verstak-sync-server` — сервер синхронизации
|
||||||
|
- `verstak-docs` — платформенная и архитектурная документация
|
||||||
|
|
||||||
## 2. Минимальный набор репозиториев
|
Локально репозитории клонируются как sibling-директории:
|
||||||
|
|
||||||
|
```text
|
||||||
|
verstak2/
|
||||||
|
├── verstak-desktop/
|
||||||
|
├── verstak-official-plugins/
|
||||||
|
├── verstak-sdk/
|
||||||
|
├── verstak-browser-extension/
|
||||||
|
├── verstak-sync-server/
|
||||||
|
└── verstak-docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Назначение репозиториев
|
||||||
|
|
||||||
### `verstak-desktop`
|
### `verstak-desktop`
|
||||||
|
|
||||||
@@ -24,7 +41,11 @@ Project board можно вести на уровне organization: roadmap, iss
|
|||||||
- storage API;
|
- storage API;
|
||||||
- event bus;
|
- event bus;
|
||||||
- sync client boundary;
|
- sync client boundary;
|
||||||
- dev harness для локальных плагинов.
|
- workspace manager;
|
||||||
|
- file watcher;
|
||||||
|
- notifications;
|
||||||
|
- browser receiver (local HTTP endpoint);
|
||||||
|
- secret store (AES-GCM).
|
||||||
|
|
||||||
Не содержит:
|
Не содержит:
|
||||||
|
|
||||||
@@ -36,124 +57,71 @@ Project board можно вести на уровне organization: roadmap, iss
|
|||||||
|
|
||||||
### `verstak-official-plugins`
|
### `verstak-official-plugins`
|
||||||
|
|
||||||
Монорепозиторий официальных плагинов.
|
Монорепозиторий официальных плагинов:
|
||||||
|
|
||||||
Содержит:
|
```
|
||||||
|
|
||||||
```text
|
|
||||||
plugins/
|
plugins/
|
||||||
files/
|
platform-test/ — тестовый плагин для проверки runtime
|
||||||
notes/
|
files/ — файловый менеджер
|
||||||
markdown-editor/
|
notes/ — заметки
|
||||||
file-preview/
|
default-editor/ — редактор текста/markdown
|
||||||
activity/
|
file-preview/ — предпросмотр изображений
|
||||||
journal/
|
activity/ — активность
|
||||||
browser-inbox/
|
journal/ — журнал работ
|
||||||
search/
|
browser-inbox/ — браузерный inbox
|
||||||
secrets/
|
search/ — поиск
|
||||||
templates/
|
secrets/ — хранилище секретов
|
||||||
packages/
|
todo/ — задачи
|
||||||
plugin-sdk/
|
trash/ — корзина
|
||||||
shared-ui/
|
sync/ — синхронизация
|
||||||
test-harness/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Официальные плагины должны устанавливаться и загружаться так же, как сторонние. Нельзя делать для них скрытый privileged path, кроме явно описанных platform permissions.
|
Все плагины используют префикс `verstak.*` в id. Официальные плагины загружаются через тот же plugin runtime, что и сторонние.
|
||||||
|
|
||||||
### `verstak-sdk`
|
### `verstak-sdk`
|
||||||
|
|
||||||
Общие контракты для разработки плагинов.
|
Общие контракты для разработки плагинов:
|
||||||
|
|
||||||
Содержит:
|
- JSON Schema для manifest, capabilities, contributions, permissions, sync;
|
||||||
|
- TypeScript типы для plugin API;
|
||||||
- manifest schema;
|
- тестовые утилиты.
|
||||||
- TypeScript SDK;
|
|
||||||
- RPC protocol definitions;
|
|
||||||
- capability contracts;
|
|
||||||
- event schemas;
|
|
||||||
- test helpers;
|
|
||||||
- plugin packaging tools;
|
|
||||||
- examples.
|
|
||||||
|
|
||||||
На раннем этапе SDK может жить в `verstak-official-plugins/packages/plugin-sdk`, но должен быть выделен в отдельный репозиторий, когда API начнет стабилизироваться.
|
|
||||||
|
|
||||||
### `verstak-sync-server`
|
### `verstak-sync-server`
|
||||||
|
|
||||||
Отдельный сервер синхронизации.
|
Отдельный сервер синхронизации:
|
||||||
|
|
||||||
Содержит:
|
|
||||||
|
|
||||||
- HTTP API;
|
- HTTP API;
|
||||||
- auth/pairing;
|
- auth/pairing;
|
||||||
- device registry;
|
- device registry;
|
||||||
- vault operation log;
|
- vault operation log;
|
||||||
- blob upload/download;
|
- blob upload/download;
|
||||||
- conflict handling;
|
- embedded web console;
|
||||||
- retention/deleted file policy;
|
|
||||||
- server migrations;
|
|
||||||
- deployment docs.
|
- deployment docs.
|
||||||
|
|
||||||
Sync server не должен импортировать desktop UI или official plugins.
|
|
||||||
|
|
||||||
### `verstak-browser-extension`
|
### `verstak-browser-extension`
|
||||||
|
|
||||||
Расширение браузера.
|
Расширение браузера (Firefox/Chromium):
|
||||||
|
|
||||||
Содержит:
|
|
||||||
|
|
||||||
- Firefox/Chromium extension;
|
|
||||||
- local pairing with Verstak;
|
- local pairing with Verstak;
|
||||||
- page capture;
|
- page capture, selection, link, file;
|
||||||
- selected text capture;
|
- pending queue;
|
||||||
- link sending;
|
- domain bindings;
|
||||||
- pending queue if desktop is offline;
|
- passive domain activity (opt-in).
|
||||||
- domain bindings support;
|
|
||||||
- protocol docs for browser inbox plugin.
|
|
||||||
|
|
||||||
Расширение не должно напрямую знать внутреннюю структуру notes/files/activity. Оно отправляет события в local receiver, а обработка идет через плагин `official.browser-inbox`.
|
|
||||||
|
|
||||||
## 3. Репозитории позже
|
|
||||||
|
|
||||||
### `verstak-plugin-registry`
|
|
||||||
|
|
||||||
Каталог доступных плагинов:
|
|
||||||
|
|
||||||
- official plugin index;
|
|
||||||
- third-party plugin metadata;
|
|
||||||
- signatures/checksums;
|
|
||||||
- compatibility matrix;
|
|
||||||
- install URLs.
|
|
||||||
|
|
||||||
Не нужен в первый этап, если плагины ставятся вручную из локальной папки или из `verstak-official-plugins`.
|
|
||||||
|
|
||||||
### `verstak-docs`
|
### `verstak-docs`
|
||||||
|
|
||||||
Публичная документация:
|
Платформенная и архитектурная документация:
|
||||||
|
|
||||||
- user guide;
|
- product vision;
|
||||||
- developer guide;
|
- platform architecture;
|
||||||
- plugin authoring guide;
|
- plugin system overview;
|
||||||
- sync setup;
|
- official plugins reference;
|
||||||
- security model;
|
- development strategy;
|
||||||
- migration guides.
|
- implementation roadmap.
|
||||||
|
|
||||||
Может быть отдельным репозиторием позже. Сейчас допустимо держать архитектурные документы рядом с `verstak-desktop`.
|
## 3. Версионирование
|
||||||
|
|
||||||
## 4. Что не дробить слишком рано
|
Версии существуют на трёх уровнях:
|
||||||
|
|
||||||
Не стоит сразу создавать отдельный репозиторий на каждый официальный плагин. Это увеличит накладные расходы и усложнит синхронные изменения SDK/API.
|
|
||||||
|
|
||||||
Лучше:
|
|
||||||
|
|
||||||
```text
|
|
||||||
verstak-official-plugins - один repo для официальных плагинов
|
|
||||||
```
|
|
||||||
|
|
||||||
А отдельные repo оставить для сторонних плагинов или крупных независимых модулей.
|
|
||||||
|
|
||||||
## 5. Версионирование
|
|
||||||
|
|
||||||
Версии должны существовать на трех уровнях:
|
|
||||||
|
|
||||||
- app version: версия `verstak-desktop`;
|
- app version: версия `verstak-desktop`;
|
||||||
- platform apiVersion: версия API плагинов;
|
- platform apiVersion: версия API плагинов;
|
||||||
@@ -164,8 +132,9 @@ verstak-official-plugins - один repo для официальных плаг
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"apiVersion": "1"
|
"apiVersion": "0.1.0"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Core может загрузить плагин только при совместимости `apiVersion`.
|
Core может загрузить плагин только при совместимости `apiVersion`.
|
||||||
|
Совместимые компоненты следует собирать из одной релизной линии.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Verstak Plugin System
|
# Verstak Plugin System
|
||||||
|
|
||||||
|
> Подробный reference по реализации: [Plugin Runtime](../verstak-desktop/docs/PLUGIN_RUNTIME.md).
|
||||||
|
|
||||||
## 1. Цель
|
## 1. Цель
|
||||||
|
|
||||||
Плагины превращают Верстак из монолитного приложения в платформу. Любая пользовательская функция должна быть реализуема как плагин:
|
Плагины превращают Верстак из монолитного приложения в платформу. Любая пользовательская функция должна быть реализуема как плагин:
|
||||||
@@ -19,11 +21,12 @@
|
|||||||
## 2. Структура плагина
|
## 2. Структура плагина
|
||||||
|
|
||||||
```text
|
```text
|
||||||
official.notes/
|
verstak.notes/
|
||||||
plugin.json
|
plugin.json
|
||||||
frontend/
|
frontend/
|
||||||
index.js
|
dist/
|
||||||
style.css
|
index.js
|
||||||
|
style.css
|
||||||
backend/
|
backend/
|
||||||
plugin-linux-amd64
|
plugin-linux-amd64
|
||||||
plugin-windows-amd64.exe
|
plugin-windows-amd64.exe
|
||||||
@@ -37,15 +40,24 @@ official.notes/
|
|||||||
|
|
||||||
## 3. Manifest
|
## 3. Manifest
|
||||||
|
|
||||||
|
Manifest — файл `plugin.json` в корне директории плагина.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "official.notes",
|
"id": "verstak.notes",
|
||||||
"name": "Notes",
|
"name": "Notes",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"apiVersion": "1",
|
"apiVersion": "0.1.0",
|
||||||
"description": "Markdown notes inside Verstak cases.",
|
"description": "Markdown notes inside Verstak workspaces.",
|
||||||
"source": "official",
|
"source": "official",
|
||||||
|
"localization": {
|
||||||
|
"defaultLocale": "en",
|
||||||
|
"locales": {
|
||||||
|
"en": "locales/en.json",
|
||||||
|
"ru": "locales/ru.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
"provides": [
|
"provides": [
|
||||||
"workspace.notes",
|
"workspace.notes",
|
||||||
"entity.note"
|
"entity.note"
|
||||||
@@ -59,16 +71,17 @@ official.notes/
|
|||||||
"search.provider"
|
"search.provider"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"vault.read",
|
"files.read",
|
||||||
"vault.write",
|
"files.write",
|
||||||
"storage.namespace",
|
"storage.namespace",
|
||||||
"ui.register",
|
"ui.register",
|
||||||
"events.publish",
|
"events.publish",
|
||||||
"events.subscribe"
|
"events.subscribe",
|
||||||
|
"workbench.open"
|
||||||
],
|
],
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"entry": "frontend/index.js",
|
"entry": "frontend/dist/index.js",
|
||||||
"style": "frontend/style.css"
|
"style": "frontend/dist/style.css"
|
||||||
},
|
},
|
||||||
"backend": {
|
"backend": {
|
||||||
"type": "sidecar",
|
"type": "sidecar",
|
||||||
@@ -85,6 +98,35 @@ official.notes/
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3.1. Локализация
|
||||||
|
|
||||||
|
Локализуемый плагин объявляет собственные JSON-каталоги в `localization`.
|
||||||
|
Пути должны быть относительными, использовать `/` и оставаться внутри каталога
|
||||||
|
плагина. Все значения каталога — строки. Литеральные английские значения в
|
||||||
|
manifest остаются fallback, а переводы metadata используют стабильные ключи:
|
||||||
|
|
||||||
|
```text
|
||||||
|
manifest.name
|
||||||
|
manifest.description
|
||||||
|
contributions.views.<id>.title
|
||||||
|
contributions.commands.<id>.title
|
||||||
|
contributions.statusBarItems.<id>.label
|
||||||
|
```
|
||||||
|
|
||||||
|
Внутренний UI плагина получает текущий язык только через публичный API:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const locale = api.i18n.getLocale();
|
||||||
|
const title = api.i18n.t('ui.title', undefined, 'Notes');
|
||||||
|
const unsubscribe = api.i18n.onDidChangeLocale(nextLocale => {
|
||||||
|
// Обновить текст без перемонтирования компонента и потери состояния.
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Desktop поддерживает `system`, `en` и `ru`. В режиме `system` локали `ru-*`
|
||||||
|
выбирают русский язык, остальные — английский. Плагин отвечает за свои
|
||||||
|
переводы; desktop core не содержит тексты официальных плагинов.
|
||||||
|
|
||||||
## 4. Capabilities Instead Of Plugin Names
|
## 4. Capabilities Instead Of Plugin Names
|
||||||
|
|
||||||
Плагины не должны требовать конкретный плагин, если им нужна способность.
|
Плагины не должны требовать конкретный плагин, если им нужна способность.
|
||||||
@@ -93,7 +135,7 @@ official.notes/
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"requires": ["official.markdown-editor"]
|
"requires": ["verstak.default-editor"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -116,12 +158,11 @@ official.notes/
|
|||||||
Пример:
|
Пример:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
official.notes
|
verstak.notes
|
||||||
requires:
|
requires:
|
||||||
vault.files
|
verstak/core/files/v1
|
||||||
optionalRequires:
|
optionalRequires:
|
||||||
editor.text.markdown
|
editor.text.markdown
|
||||||
preview.markdown
|
|
||||||
search.provider
|
search.provider
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -204,8 +245,8 @@ Contribution должен быть декларативным, где возмо
|
|||||||
"contributes": {
|
"contributes": {
|
||||||
"settingsPanels": [
|
"settingsPanels": [
|
||||||
{
|
{
|
||||||
"id": "official.markdown-editor.general",
|
"id": "verstak.default-editor.general",
|
||||||
"title": "Markdown Editor",
|
"title": "Default Editor",
|
||||||
"component": "settings.general"
|
"component": "settings.general"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -218,7 +259,7 @@ Plugin Manager показывает кнопку настроек только
|
|||||||
Настройки хранятся в namespace плагина:
|
Настройки хранятся в namespace плагина:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
plugin_settings.official.markdown-editor
|
plugin_settings/<plugin_id>/
|
||||||
```
|
```
|
||||||
|
|
||||||
Плагин не должен читать/писать настройки другого плагина без разрешения.
|
Плагин не должен читать/писать настройки другого плагина без разрешения.
|
||||||
@@ -290,20 +331,19 @@ Uninstall не должен автоматически удалять польз
|
|||||||
|
|
||||||
## 13. Packaging
|
## 13. Packaging
|
||||||
|
|
||||||
Плагин распространяется как zip/tar package:
|
Плагин распространяется как директория внутри `plugins/` или как архив:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
official.notes-0.1.0.vpkg
|
verstak.notes-0.1.0.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
Пакет содержит:
|
Пакет содержит:
|
||||||
|
|
||||||
- `plugin.json`;
|
- `plugin.json`;
|
||||||
- frontend bundle;
|
- frontend bundle (`frontend/dist/`);
|
||||||
- backend binaries if any;
|
- backend binaries if any;
|
||||||
- migrations;
|
- migrations;
|
||||||
- README;
|
- README;
|
||||||
- checksums/signature later.
|
- checksums/signature later.
|
||||||
|
|
||||||
На первом этапе допустима ручная установка папкой в plugin directory.
|
Подробнее о сборке: [PACKAGING.md](../verstak-official-plugins/docs/PACKAGING.md).
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
# Official Plugins
|
# Official Plugins
|
||||||
|
|
||||||
Официальные плагины - это базовый набор инструментов Верстака. Они не должны быть скрытыми частями core. Их задача - показать, что платформа действительно работает через capabilities и contribution points.
|
Официальные плагины — базовый набор инструментов Верстака. Они используют тот же
|
||||||
|
plugin runtime, что и сторонние. Их задача — показать, что платформа работает
|
||||||
|
через capabilities и contribution points.
|
||||||
|
|
||||||
## 1. `official.files`
|
Все официальные плагины используют префикс `verstak.*` в id.
|
||||||
|
|
||||||
|
## 1. `verstak.files`
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- дерево/список файлов внутри дела;
|
- дерево/список файлов внутри дела;
|
||||||
- добавление файлов;
|
- добавление файлов и папок;
|
||||||
- перемещение/копирование в vault;
|
- перемещение/переименование;
|
||||||
- открытие системным приложением;
|
- открытие через Workbench (openProvider routing);
|
||||||
- file metadata;
|
- trash/restore (через `verstak.trash`);
|
||||||
- file actions registry consumer.
|
- file metadata.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
workspace.files
|
workspace.files
|
||||||
vault.files
|
vault.files
|
||||||
entity.file
|
entity.file
|
||||||
@@ -24,72 +28,64 @@ file.browser
|
|||||||
|
|
||||||
Optional requires:
|
Optional requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
editor.text
|
editor.text
|
||||||
viewer.file
|
viewer.file
|
||||||
preview.file
|
preview.file
|
||||||
search.provider
|
search.provider
|
||||||
```
|
```
|
||||||
|
|
||||||
Поведение:
|
Текущий статус: реализован. Открывает файлы через `api.workbench.openResource()`,
|
||||||
|
не импортирует редактор напрямую. Корзина выделена в отдельный плагин `verstak.trash`.
|
||||||
|
|
||||||
- если есть подходящий editor capability, показывает "Edit";
|
## 2. `verstak.notes`
|
||||||
- если есть viewer/preview capability, показывает "Preview";
|
|
||||||
- если подходящего provider/capability нет, показывает понятное no-provider
|
|
||||||
состояние; открытие внешним приложением остается отдельной отложенной
|
|
||||||
возможностью.
|
|
||||||
|
|
||||||
## 2. `official.notes`
|
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- markdown notes as a UI-level context over ordinary Markdown files;
|
- markdown notes как UI-слой над обычными `.md` файлами;
|
||||||
- canonical `Notes/` folder inside case/project;
|
- каноническая папка `Notes/` внутри дела;
|
||||||
- `Overview.md` is allowed only as an ordinary Markdown filename, not as a
|
- `Overview.md` — обычное markdown-имя файла, не специальная UI-сущность;
|
||||||
special UI entity;
|
|
||||||
- note metadata;
|
- note metadata;
|
||||||
- note links.
|
- rename с синхронизацией title/filename;
|
||||||
|
- conflict dialog при конфликте имён.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
workspace.notes
|
workspace.notes
|
||||||
note.registry
|
note.registry
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
vault.files
|
verstak/core/files/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional requires:
|
Optional requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
editor.text.markdown
|
editor.text.markdown
|
||||||
preview.markdown
|
|
||||||
search.provider
|
search.provider
|
||||||
```
|
```
|
||||||
|
|
||||||
Важное правило:
|
Текущий статус: реализован. Title и filename синхронизированы. При конфликте
|
||||||
|
имени показывается conflict dialog.
|
||||||
|
|
||||||
- title и filename должны оставаться синхронизированными;
|
## 3. `verstak.default-editor`
|
||||||
- filename - человекочитаемая проекция title;
|
|
||||||
- при конфликте имени не добавлять `_2` молча, а показывать понятный conflict dialog.
|
|
||||||
|
|
||||||
## 3. `official.markdown-editor`
|
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- редактирование markdown/text;
|
- редактирование текста и markdown;
|
||||||
- toolbar;
|
- toolbar;
|
||||||
- save flow;
|
- save flow;
|
||||||
- dirty state;
|
- dirty state;
|
||||||
- keyboard shortcuts.
|
- markdown preview (встроен, не отдельный плагин);
|
||||||
|
- keyboard shortcuts (Ctrl+S).
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
editor.text
|
editor.text
|
||||||
editor.text.markdown
|
editor.text.markdown
|
||||||
editor.note.markdown
|
editor.note.markdown
|
||||||
@@ -97,52 +93,47 @@ editor.note.markdown
|
|||||||
|
|
||||||
Optional requires:
|
Optional requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
link.resolver
|
link.resolver
|
||||||
```
|
```
|
||||||
|
|
||||||
Не должен:
|
Текущий статус: реализован как единый плагин с тремя openProviders
|
||||||
|
(`text`, `generic-markdown`, `notes-markdown`). Использует `api.files.readText` /
|
||||||
|
`api.files.writeText`. Markdown preview — часть редактора, без отдельного
|
||||||
|
плагина предпросмотра.
|
||||||
|
|
||||||
- сам решать, где хранятся notes;
|
## 4. `verstak.file-preview`
|
||||||
- напрямую зависеть от `official.notes`;
|
|
||||||
- тащить file manager внутрь себя.
|
|
||||||
|
|
||||||
Markdown preview is part of the Markdown editor surface. There is no separate
|
|
||||||
official Markdown preview plugin, because a standalone view provider would
|
|
||||||
compete with the editor routing for `.md` / `.markdown` files.
|
|
||||||
|
|
||||||
## 4. `official.file-preview`
|
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- inline preview for image files through bounded `api.files.readBytes`;
|
- inline preview для изображений через `api.files.readBytes`;
|
||||||
- metadata view for previewed files;
|
- metadata view;
|
||||||
- `Open External` action through `api.files.openExternal`.
|
- `Open External` через `api.files.openExternal`.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
viewer.file
|
viewer.file
|
||||||
viewer.image
|
viewer.image
|
||||||
preview.file
|
preview.file
|
||||||
```
|
```
|
||||||
|
|
||||||
Текущий статус: базовый `verstak.file-preview` renders image files inline via
|
Текущий статус: базовый рендеринг изображений через публичный Files API,
|
||||||
the public Files API and shows file metadata. Text-like files, code, and
|
плюс metadata файла. Текстовые файлы, код и markdown остаются за
|
||||||
Markdown stay with editor plugins.
|
editor-плагинами.
|
||||||
|
|
||||||
## 5. `official.activity`
|
## 5. `verstak.activity`
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- сбор activity events;
|
- сбор activity events;
|
||||||
- отображение истории;
|
- отображение истории активности;
|
||||||
- реконструкция работы;
|
- реконструкция работы;
|
||||||
- подсказки для worklog.
|
- worklog suggestions для Journal.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
activity.log
|
activity.log
|
||||||
activity.provider
|
activity.provider
|
||||||
activity.reconstruction
|
activity.reconstruction
|
||||||
@@ -150,52 +141,33 @@ activity.reconstruction
|
|||||||
|
|
||||||
Subscribes:
|
Subscribes:
|
||||||
|
|
||||||
```text
|
```
|
||||||
file.opened
|
file.opened
|
||||||
file.changed
|
file.changed
|
||||||
note.saved
|
note.saved
|
||||||
action.started
|
action.started
|
||||||
browser.capture.received
|
browser.capture.received
|
||||||
case.selected
|
case.selected
|
||||||
browser.capture.page
|
|
||||||
browser.capture.selection
|
|
||||||
browser.capture.link
|
|
||||||
browser.capture.file
|
|
||||||
browser.capture.converted
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Текущий статус: базовый `verstak.activity` implemented as both a global sidebar
|
Текущий статус: реализован как глобальный sidebar view и workspace item.
|
||||||
view and a workspace item. Workspace tabs store and display only their own
|
Workspace tabs хранят свою activity stream; глобальный view агрегирует все
|
||||||
activity stream; the global sidebar view aggregates activity from all workspace
|
workspace streams. Предоставляет `activityProviders`; desktop runtime записывает
|
||||||
streams plus unscoped global activity. It contributes `activityProviders`; the
|
события в plugin storage даже когда Activity view не смонтирован. Реконструирует
|
||||||
desktop runtime hosts those providers and records subscribed public events into
|
worklog suggestions через контракт `verstak.activity.suggestWorklog`.
|
||||||
the plugin storage even when the Activity view is not mounted. The Activity UI
|
|
||||||
is a read/clear surface, not a manual recording toggle. It now reconstructs
|
|
||||||
compact worklog suggestions from scoped activity streams and exposes them
|
|
||||||
through the command-backed `verstak.activity.suggestWorklog` runtime contract
|
|
||||||
for the Journal plugin.
|
|
||||||
|
|
||||||
## Sync Conflict UX Contract
|
## 6. `verstak.journal`
|
||||||
|
|
||||||
`api.sync.now()` returns warning details through `conflicts` and `applyErrors`.
|
|
||||||
The official Sync plugin must display both as warnings after manual sync. A
|
|
||||||
conflict warning must include at least the affected entity type/path when the
|
|
||||||
server provides those fields. The plugin must not auto-resolve conflicts, rename
|
|
||||||
local files, overwrite local files, or hide conflict details behind a plain
|
|
||||||
count.
|
|
||||||
|
|
||||||
## 6. `official.journal`
|
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- ручной журнал работ;
|
- ручной журнал работ;
|
||||||
- billable/non-billable;
|
- billable/non-billable;
|
||||||
- отчеты по делу/клиенту;
|
- приём suggested time из activity;
|
||||||
- принятие suggested time из activity.
|
- создание записи из завершённого todo.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
worklog
|
worklog
|
||||||
journal
|
journal
|
||||||
report.worklog
|
report.worklog
|
||||||
@@ -203,31 +175,27 @@ report.worklog
|
|||||||
|
|
||||||
Optional requires:
|
Optional requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
activity.reconstruction
|
activity.reconstruction
|
||||||
```
|
```
|
||||||
|
|
||||||
Текущий статус: базовый `verstak.journal` implemented as both a global sidebar
|
Текущий статус: реализован как глобальный sidebar view и workspace item.
|
||||||
view and a workspace item. Workspace views store manual worklog entries in the
|
Хранит записи в plugin settings namespace, импортирует не-billable записи
|
||||||
plugin settings namespace, import non-billable entries from
|
из `verstak.activity.suggestWorklog`, дедуплицирует по Activity suggestion id.
|
||||||
`verstak.activity.suggestWorklog`, and deduplicate repeated imports by Activity
|
|
||||||
suggestion id. The global view aggregates stored workspace worklogs. Billing
|
|
||||||
reports, invoice export, timers, and richer report filters are still future
|
|
||||||
work.
|
|
||||||
|
|
||||||
## 7. `official.browser-inbox`
|
## 7. `verstak.browser-inbox`
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- прием ссылок, выделенного текста, страниц и snippets из browser extension;
|
- приём страниц, ссылок, выделенного текста и файлов из browser extension;
|
||||||
- pending queue;
|
- pending queue с scoping по workspace;
|
||||||
- привязка доменов к делам;
|
- domain bindings;
|
||||||
- создание inbox entries;
|
- конвертация inbox entry в note/link/file;
|
||||||
- превращение inbox entry в note/link/file/activity.
|
- запись конвертаций в Activity.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
capture.browser
|
capture.browser
|
||||||
browser.inbox
|
browser.inbox
|
||||||
domain.binding
|
domain.binding
|
||||||
@@ -235,133 +203,150 @@ domain.binding
|
|||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
network.local
|
network.local
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional requires:
|
Optional requires:
|
||||||
|
|
||||||
```text
|
```
|
||||||
workspace.notes
|
workspace.notes
|
||||||
activity.log
|
activity.log
|
||||||
search.provider
|
search.provider
|
||||||
```
|
```
|
||||||
|
|
||||||
Текущий статус: базовый `verstak.browser-inbox` implemented as both a global
|
Текущий статус: реализован как глобальный sidebar view и workspace item.
|
||||||
sidebar view and a workspace item. Workspace tabs keep their own pending queue;
|
Local receiver в paired mode с токеном. Поддерживает конвертацию в note
|
||||||
the global sidebar view aggregates queues from all workspaces plus unscoped
|
(через публичный Files API), link (`.url` файлы), text file attachments
|
||||||
global captures. The local receiver starts in paired mode: it generates an
|
и bounded binary attachments. Конвертации записываются в Activity.
|
||||||
installation-local token and requires `X-Verstak-Receiver-Token` before
|
|
||||||
publishing browser capture events. The Browser Inbox settings panel exposes the
|
|
||||||
receiver URL and token, and rotates the token through the dangerous
|
|
||||||
`browser.receiver.manage` permission. Browser Inbox stores plugin-owned
|
|
||||||
`domainBindings` and routes unscoped captures with an
|
|
||||||
exact domain match into the bound workspace queue. Its first conversion workflow
|
|
||||||
creates ordinary Markdown notes through the public Files API and publishes a
|
|
||||||
`browser.capture.converted` event, which Activity records through its public
|
|
||||||
provider subscription. Browser Inbox also creates human-readable `.url` link
|
|
||||||
files through the public Files API. It now accepts selected files from the
|
|
||||||
browser extension and creates ordinary workspace files through `api.files.writeText`
|
|
||||||
or bounded `api.files.writeBytes`. Chunked large-file attachment capture remains
|
|
||||||
future work.
|
|
||||||
|
|
||||||
## 9. `official.search`
|
## 8. `verstak.search`
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- workspace-scoped search UI;
|
- workspace-scoped search;
|
||||||
- baseline recursive text search through public Files API;
|
- поиск по мере ввода;
|
||||||
- search provider contribution for platform discovery;
|
- поиск по именам файлов/папок и содержимому текстовых файлов;
|
||||||
- index notes/files/activity/worklog later;
|
- persistent search index;
|
||||||
- typo/layout tolerant search later.
|
- cross-provider runtime hosting.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
search
|
search
|
||||||
search.provider
|
search.provider
|
||||||
search.indexer
|
search.indexer
|
||||||
```
|
```
|
||||||
|
|
||||||
Текущий статус: базовый `verstak.search` implemented as a workspace item and
|
Текущий статус: реализован как workspace item и `searchProviders` contribution.
|
||||||
`searchProviders` contribution. It searches as the user types, matches vault
|
Ищет по мере ввода, индексирует vault, обновляет индекс по файловым событиям,
|
||||||
file/folder names and paths, scans text-like file contents through
|
поддерживает других search providers через runtime.
|
||||||
`api.files.list` / `api.files.readText`, and opens file results through
|
|
||||||
Workbench. It persists a workspace-scoped JSON search index in the plugin data
|
|
||||||
namespace, refreshes it from public file events, registers its own vault-text
|
|
||||||
provider as a command-backed `searchProviders` handler, and fans out to other
|
|
||||||
registered provider commands at runtime. Full-text ranking, typo/layout
|
|
||||||
tolerant search, and sidecar indexing remain later work.
|
|
||||||
|
|
||||||
Target UX: search should be available from the workspace header next to the
|
## 9. `verstak.secrets`
|
||||||
workspace title. The standalone Search workspace item may remain only as an
|
|
||||||
expanded results surface; it should not be the primary search entry point.
|
|
||||||
|
|
||||||
## 10. `official.secrets`
|
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- защищенное хранилище клиентских доступов;
|
- защищённое хранилище доступов;
|
||||||
- SSH/CMS/VPS/database/API secrets;
|
- workspace-scoped secrets;
|
||||||
- bridge secret, sync token, device private key, pairing token;
|
- master password (AES-GCM).
|
||||||
- permissions for secret access.
|
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
secret-store
|
secret-store
|
||||||
secrets.read-ui
|
secrets.read-ui
|
||||||
secrets.write-ui
|
secrets.write-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
Важное правило:
|
Текущий статус: реализован. Desktop core имеет AES-GCM secret store,
|
||||||
|
разблокируемый один раз за сессию. Плагин показывает global и workspace-scoped
|
||||||
|
секреты, поддерживает редактирование, удаление, копирование markdown secret links.
|
||||||
|
Рендерит `verstak-secret://...` ссылки.
|
||||||
|
|
||||||
- секреты не должны храниться как обычный markdown/plain text;
|
## 10. `verstak.todo`
|
||||||
- доступ к secret-store должен идти через permissions;
|
|
||||||
- плагины не получают `secrets.read` автоматически.
|
|
||||||
|
|
||||||
Текущий статус: platform contract defines `secret-store`, `secrets.read-ui`,
|
|
||||||
and `secrets.write-ui` capabilities plus dangerous `secrets.read` /
|
|
||||||
`secrets.write` permissions in the SDK manifest contract. Desktop core has a
|
|
||||||
local AES-GCM secret store unlocked once per app session by master password.
|
|
||||||
The official `verstak.secrets` plugin shows global and workspace-scoped
|
|
||||||
secrets grouped by workspace, separates first-run master-password setup from
|
|
||||||
later unlock, supports editing and deleting secrets, shows selected secret
|
|
||||||
fields as a table, copies markdown secret links, and handles `secret`
|
|
||||||
workbench resources. The default editor renders
|
|
||||||
`verstak-secret://...` markdown links only when a secrets open-provider is
|
|
||||||
enabled, and opens the Secrets plugin without inserting raw values into notes.
|
|
||||||
|
|
||||||
## 11. `official.templates`
|
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
- шаблоны дел;
|
- списки задач внутри дел и глобально;
|
||||||
- client/project/server/device structures;
|
- статусы: open, done, cancelled;
|
||||||
- initial folder/note/action layout.
|
- приоритеты, due date, reminders;
|
||||||
|
- создание Journal записи из завершённого todo.
|
||||||
|
|
||||||
Provides:
|
Provides:
|
||||||
|
|
||||||
```text
|
```
|
||||||
|
workspace.todo
|
||||||
|
```
|
||||||
|
|
||||||
|
Текущий статус: реализован. Reminders с нативными desktop-уведомлениями
|
||||||
|
(при наличии capability `verstak/core/notifications/v1`). Поддерживает
|
||||||
|
создание Journal записи из todo с копированием фактических данных.
|
||||||
|
|
||||||
|
## 11. `verstak.trash`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- глобальный просмотр удалённых файлов;
|
||||||
|
- восстановление из корзины;
|
||||||
|
- перманентное удаление.
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
|
||||||
|
```
|
||||||
|
trash.management
|
||||||
|
```
|
||||||
|
|
||||||
|
Текущий статус: реализован как глобальный sidebar view. Работает
|
||||||
|
с internal trash storage (`.verstak/trash/files/`).
|
||||||
|
|
||||||
|
## 12. `verstak.sync`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- ручная синхронизация vault;
|
||||||
|
- отображение статуса и конфликтов;
|
||||||
|
- настройки подключения к sync server.
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
|
||||||
|
```
|
||||||
|
sync.ui
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
```
|
||||||
|
network.remote
|
||||||
|
```
|
||||||
|
|
||||||
|
Текущий статус: реализован как глобальный sidebar view и settings panel.
|
||||||
|
Показывает vaultId, статус, unpushed count, ошибки и конфликты.
|
||||||
|
Не делает auto-resolve конфликтов.
|
||||||
|
|
||||||
|
## 13. `verstak.templates`
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- шаблоны дел при создании;
|
||||||
|
- предопределённые наборы workspace tools;
|
||||||
|
- one-time применение (шаблон не привязан к делу после создания).
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
|
||||||
|
```
|
||||||
case.templates
|
case.templates
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional requires:
|
Текущий статус: реализован через built-in templates в desktop core
|
||||||
|
(General, Project, Writing, Admin, Minimal). Модальное окно создания
|
||||||
|
дела показывает описание шаблона и его вкладки.
|
||||||
|
|
||||||
```text
|
## 14. `verstak.platform-test`
|
||||||
workspace.notes
|
|
||||||
workspace.files
|
|
||||||
```
|
|
||||||
|
|
||||||
## 12. Первый минимальный набор
|
Назначение:
|
||||||
|
|
||||||
Для первого платформенного этапа достаточно:
|
- тестовый плагин для отладки runtime;
|
||||||
|
- диагностические панели для проверки API;
|
||||||
|
- не для конечных пользователей.
|
||||||
|
|
||||||
- `official.files`;
|
Текущий статус: используется только при разработке.
|
||||||
- `official.notes`;
|
|
||||||
- `official.markdown-editor`;
|
|
||||||
- `official.file-preview`;
|
|
||||||
- `official.activity`;
|
|
||||||
- `official.browser-inbox`;
|
|
||||||
|
|
||||||
Но все они должны быть настоящими динамическими плагинами, даже если поставляются вместе с приложением.
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
## 1. Цель
|
## 1. Цель
|
||||||
|
|
||||||
Verstak v2 разрабатывается как отдельное приложение и платформа. Цель -
|
Verstak развивается как local-first платформа с динамическими плагинами:
|
||||||
собрать рабочий local-first vault вокруг дел без временных мостов к первой
|
|
||||||
версии:
|
|
||||||
|
|
||||||
- local-first vault;
|
- local-first vault;
|
||||||
- дела как центр контекста;
|
- дела как центр контекста;
|
||||||
@@ -12,23 +10,19 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||||||
- заметки, файлы, журнал, активность, браузерные материалы вокруг дела;
|
- заметки, файлы, журнал, активность, браузерные материалы вокруг дела;
|
||||||
- синхронизация и расширение как отдельные части, а не ядро смысла.
|
- синхронизация и расширение как отдельные части, а не ядро смысла.
|
||||||
|
|
||||||
Это не миграция v1 -> v2 и не перенос старого монолита частями. Это развитие
|
|
||||||
самостоятельной платформенной модели.
|
|
||||||
|
|
||||||
## 2. Нельзя делать
|
## 2. Нельзя делать
|
||||||
|
|
||||||
- Нельзя добавлять новые функции в монолитный `App.svelte`.
|
- Нельзя добавлять новые функции в монолитный `App.svelte`.
|
||||||
- Нельзя делать official plugins скрытыми compile-time modules.
|
- Нельзя делать official plugins скрытыми compile-time modules.
|
||||||
- Нельзя связывать плагины по именам, если нужна capability.
|
- Нельзя связывать плагины по именам, если нужна capability.
|
||||||
- Нельзя делать notes/files/editor обязательными частями core.
|
- Нельзя делать notes/files/editor обязательными частями core.
|
||||||
- Нельзя добавлять временные compatibility bridges к первой версии.
|
- Нельзя менять vault layout без явного плана изменения формата.
|
||||||
- Нельзя менять v2 vault layout без явного плана изменения формата.
|
|
||||||
- Нельзя хранить секреты как обычные заметки или plain text.
|
- Нельзя хранить секреты как обычные заметки или plain text.
|
||||||
- Нельзя молча менять title/filename note при конфликте.
|
- Нельзя молча менять title/filename note при конфликте.
|
||||||
|
|
||||||
## 3. Этап 1 - Platform Skeleton
|
## 3. Этап 1 - Platform Skeleton
|
||||||
|
|
||||||
Сделать в `verstak-desktop`:
|
Сделано в `verstak-desktop`:
|
||||||
|
|
||||||
- plugin manifest schema;
|
- plugin manifest schema;
|
||||||
- plugin discovery from plugin directories;
|
- plugin discovery from plugin directories;
|
||||||
@@ -41,17 +35,9 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||||||
- basic event bus;
|
- basic event bus;
|
||||||
- diagnostics panel.
|
- diagnostics panel.
|
||||||
|
|
||||||
Проверки:
|
|
||||||
|
|
||||||
- приложение запускается без плагинов;
|
|
||||||
- приложение показывает пустой Plugin Manager;
|
|
||||||
- тестовый плагин появляется в списке;
|
|
||||||
- enable/disable работает;
|
|
||||||
- failed plugin не роняет приложение.
|
|
||||||
|
|
||||||
## 4. Этап 2 - Frontend Plugin Host
|
## 4. Этап 2 - Frontend Plugin Host
|
||||||
|
|
||||||
Сделать:
|
Сделано:
|
||||||
|
|
||||||
- загрузку frontend bundle;
|
- загрузку frontend bundle;
|
||||||
- `VerstakPluginAPI`;
|
- `VerstakPluginAPI`;
|
||||||
@@ -59,16 +45,11 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||||||
- plugin UI error boundary;
|
- plugin UI error boundary;
|
||||||
- settings panel invocation from Plugin Manager.
|
- settings panel invocation from Plugin Manager.
|
||||||
|
|
||||||
Проверки:
|
|
||||||
|
|
||||||
- тестовый плагин регистрирует view;
|
|
||||||
- тестовый плагин регистрирует settings panel;
|
|
||||||
- выключение плагина убирает view/settings;
|
|
||||||
- ошибка в plugin UI не роняет shell.
|
|
||||||
|
|
||||||
## 5. Этап 3 - Backend Sidecar Host
|
## 5. Этап 3 - Backend Sidecar Host
|
||||||
|
|
||||||
Сделать:
|
Отложено до отдельного milestone.
|
||||||
|
|
||||||
|
План:
|
||||||
|
|
||||||
- sidecar launch protocol;
|
- sidecar launch protocol;
|
||||||
- local RPC;
|
- local RPC;
|
||||||
@@ -76,57 +57,63 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||||||
- sidecar shutdown/restart;
|
- sidecar shutdown/restart;
|
||||||
- logs/diagnostics.
|
- logs/diagnostics.
|
||||||
|
|
||||||
Проверки:
|
|
||||||
|
|
||||||
- sidecar отвечает health check;
|
|
||||||
- sidecar не получает API без permissions;
|
|
||||||
- падение sidecar переводит плагин в failed;
|
|
||||||
- disable останавливает sidecar.
|
|
||||||
|
|
||||||
## 6. Этап 4 - Official Plugin Development
|
## 6. Этап 4 - Official Plugin Development
|
||||||
|
|
||||||
Развивать official plugins по одному, через общий plugin runtime:
|
Развиваются по одному, через общий plugin runtime:
|
||||||
|
|
||||||
1. `official.markdown-editor`;
|
1. `verstak.default-editor` ✅
|
||||||
2. `official.files`;
|
2. `verstak.files` ✅
|
||||||
3. `official.notes`;
|
3. `verstak.notes` ✅
|
||||||
4. `official.file-preview`;
|
4. `verstak.file-preview` ✅ (базовый)
|
||||||
5. `official.activity`;
|
5. `verstak.activity` ✅
|
||||||
6. `official.browser-inbox`.
|
6. `verstak.journal` ✅ (базовый)
|
||||||
|
7. `verstak.browser-inbox` ✅
|
||||||
|
8. `verstak.search` ✅ (базовый)
|
||||||
|
9. `verstak.secrets` ✅
|
||||||
|
10. `verstak.todo` ✅
|
||||||
|
11. `verstak.trash` ✅
|
||||||
|
12. `verstak.sync` ✅
|
||||||
|
13. `verstak.templates` ✅
|
||||||
|
|
||||||
После каждого выноса:
|
После каждого изменения:
|
||||||
|
|
||||||
- проверить build;
|
- проверить build;
|
||||||
- проверить запуск;
|
- проверить запуск;
|
||||||
- проверить Plugin Manager;
|
- проверить Plugin Manager;
|
||||||
- проверить enable/disable;
|
- проверить enable/disable;
|
||||||
- проверить degraded mode при отключении optional plugin;
|
- проверить degraded mode при отключении optional plugin;
|
||||||
- проверить, что v2 vault остается читаемым и не получает скрытых
|
- проверить, что vault остается читаемым;
|
||||||
plugin-specific truth-слоев для пользовательских документов;
|
|
||||||
- проверить, что UI contributions исчезают при disable.
|
- проверить, что UI contributions исчезают при disable.
|
||||||
|
|
||||||
## 7. Этап 5 - Repository Split
|
## 7. Этап 5 - Repository Split
|
||||||
|
|
||||||
Когда plugin runtime работает:
|
Выполнено:
|
||||||
|
|
||||||
- оставить core и shell в `verstak-desktop`;
|
- core и shell в `verstak-desktop`;
|
||||||
- вынести official plugins в `verstak-official-plugins`;
|
- official plugins в `verstak-official-plugins`;
|
||||||
- вынести sync server в `verstak-sync-server`;
|
- sync server в `verstak-sync-server`;
|
||||||
- вынести browser extension в `verstak-browser-extension`;
|
- browser extension в `verstak-browser-extension`;
|
||||||
- выделить `verstak-sdk` после стабилизации API.
|
- SDK в `verstak-sdk`;
|
||||||
|
- документация в `verstak-docs`.
|
||||||
|
|
||||||
Не начинать с физического split repo, пока runtime не умеет грузить плагины локально.
|
## 8. Definition Of Done (текущий статус)
|
||||||
|
|
||||||
## 8. Definition Of Done
|
Платформенный переход состоялся:
|
||||||
|
|
||||||
Платформенный переход можно считать состоявшимся, когда:
|
- [x] core запускается без official plugins;
|
||||||
|
- [x] official plugins лежат вне core modules;
|
||||||
|
- [x] notes/files/editor/preview/activity работают как плагины;
|
||||||
|
- [x] Plugin Manager умеет включать/выключать плагины;
|
||||||
|
- [x] плагин может иметь свое settings окно;
|
||||||
|
- [x] capability registry управляет видимостью actions;
|
||||||
|
- [x] отсутствие optional capability не считается ошибкой;
|
||||||
|
- [x] vault layout стабилен и читаем;
|
||||||
|
- [x] документация соответствует реализации (в процессе).
|
||||||
|
|
||||||
- core запускается без official plugins;
|
Оставшиеся крупные задачи:
|
||||||
- official plugins лежат вне core modules;
|
|
||||||
- notes/files/editor/preview/activity работают как плагины;
|
- [ ] Sidecar/sandbox изоляция;
|
||||||
- Plugin Manager умеет включать/выключать плагины;
|
- [ ] Production-grade packaging и автообновление;
|
||||||
- плагин может иметь свое settings окно;
|
- [ ] Operation-log retention (sync server);
|
||||||
- capability registry управляет видимостью actions;
|
- [ ] Синхронизация Secrets, plugin settings, Todo, Journal, Activity, Browser Inbox;
|
||||||
- отсутствие optional capability не считается ошибкой;
|
- [ ] UX-полировка (Today flow, mobile layout, search в workspace header).
|
||||||
- v2 vault layout остается стабильным и читаемым без compatibility bridges к v1;
|
|
||||||
- документация соответствует реализации.
|
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
# Verstak2 Full Implementation Roadmap
|
# Verstak Implementation Roadmap
|
||||||
|
|
||||||
## 1. Goal
|
## 1. Goal
|
||||||
|
|
||||||
Bring Verstak2 to a complete standalone local-first desktop product:
|
Bring Verstak to a complete standalone local-first desktop product:
|
||||||
|
|
||||||
- core platform and UI shell can run without official plugins;
|
- core platform and UI shell can run without official plugins;
|
||||||
- official plugins provide the user-facing tools;
|
- official plugins provide the user-facing tools;
|
||||||
- vault data stays human-readable and local-first;
|
- vault data stays human-readable and local-first;
|
||||||
- sync, browser capture, activity, journal, preview, search, and secrets are
|
- sync UI/settings, browser capture, activity, journal, preview, search, and
|
||||||
plugin/runtime extensions, not mandatory core modules;
|
secrets are plugin/runtime extensions; sync correctness (scanner, operation
|
||||||
- no compatibility bridge to the first Verstak version is introduced.
|
log, reconciliation, and workspace identity) stays in Desktop core;
|
||||||
|
- workspaces can be nested inside plain folders; identity through UUID markers
|
||||||
|
(`.verstak/workspace.json`) at any depth; folder metadata (icon, color, order)
|
||||||
|
lives in `.verstak/folder-metadata/`.
|
||||||
|
|
||||||
## 2. Non-negotiable Constraints
|
## 2. Non-negotiable Constraints
|
||||||
|
|
||||||
- No v1 migration bridge or temporary compatibility layer.
|
|
||||||
- No notes/files/editor/activity/journal/browser inbox feature may become a
|
- No notes/files/editor/activity/journal/browser inbox feature may become a
|
||||||
required core module.
|
required core module.
|
||||||
- User documents remain ordinary vault files unless a feature explicitly needs
|
- User documents remain ordinary vault files unless a feature explicitly needs
|
||||||
protected storage, such as secrets.
|
protected storage, such as secrets.
|
||||||
- `Overview.md` is an ordinary Markdown filename, not a special UI entity.
|
- `Overview.md` is an ordinary Markdown filename, not a special UI entity.
|
||||||
- New plugin-facing behavior must go through public Verstak2 API contracts and
|
- New plugin-facing behavior must go through public API contracts and
|
||||||
SDK/schema updates.
|
SDK/schema updates.
|
||||||
- Every significant step must be verified, committed, and pushed separately.
|
- Every significant step must be verified, committed, and pushed separately.
|
||||||
|
|
||||||
@@ -32,41 +34,41 @@ Implemented:
|
|||||||
- bundled frontend plugin host and `VerstakPluginAPI`;
|
- bundled frontend plugin host and `VerstakPluginAPI`;
|
||||||
- Command Palette UI host for `commands` contributions;
|
- Command Palette UI host for `commands` contributions;
|
||||||
- Status Bar UI host for `statusBarItems`, vault status, and settings menu;
|
- Status Bar UI host for `statusBarItems`, vault status, and settings menu;
|
||||||
- workspace top-level folder model and workspace item host;
|
- workspace top-level folder model, workspace item host, workspace templates;
|
||||||
- Files Core text API with safe path policy and sync operation recording;
|
- Files Core API with safe path policy, durable snapshot scanning, atomic
|
||||||
- public `files.openExternal` / `files.showInFolder` API and Files plugin usage;
|
text/binary writes, and core sync operation recording;
|
||||||
|
- public `files.openExternal` / `files.showInFolder` API;
|
||||||
- mode-aware Workbench open/edit provider routing and default editor plugin;
|
- mode-aware Workbench open/edit provider routing and default editor plugin;
|
||||||
- official Files plugin, Notes plugin, Markdown Editor plugin, Search plugin,
|
- all official plugins: Files, Notes, Default Editor, File Preview, Activity,
|
||||||
Sync plugin, and platform-test plugin;
|
Journal, Browser Inbox, Search, Secrets, Todo, Trash, Sync, Templates;
|
||||||
- browser inbox local receiver and minimal official Browser Inbox plugin;
|
- browser inbox local receiver with paired mode, domain bindings, and
|
||||||
- sync server with device/user auth and operation push/pull;
|
create-note/link/file conversion;
|
||||||
- SDK manifest/types/schema coverage for current plugin APIs;
|
- sync server with device/user auth, operation push/pull, Blob transport,
|
||||||
- automated Go, frontend, official plugin, SDK, and real-sync smoke checks.
|
embedded web console, and real two-vault smoke scenarios;
|
||||||
|
- SDK manifest/types/schema coverage for plugin APIs;
|
||||||
|
- persisted System/English/Russian application language selection, localized
|
||||||
|
desktop shell, public `api.i18n` plugin contract, and bilingual catalogs for
|
||||||
|
all official plugins;
|
||||||
|
- automated Go, frontend, official plugin, SDK, and real-sync smoke checks;
|
||||||
|
- tray icon with native menu and native desktop notifications;
|
||||||
|
- AES-GCM secret store with master password, UI plugin;
|
||||||
|
- nested workspace model: identity markers at any depth, folder metadata,
|
||||||
|
recursive tree in sidebar, workspace creation with parent folder selector.
|
||||||
|
|
||||||
Known remaining gaps:
|
Known remaining gaps:
|
||||||
|
|
||||||
- `fileActions`, `noteActions`, and `contextMenuEntries` are exposed through the
|
- Sidecar host is not implemented (bundled plugins run in shared JS context).
|
||||||
desktop contribution summary and hosted by the official Files/Notes surfaces.
|
- Chunked streaming/large-file import is deferred; bounded text (2 MB) and
|
||||||
- Sidecar host is not implemented.
|
byte (8 MB) APIs are the current limits.
|
||||||
- Files/Notes are usable but not complete: chunked streaming/large-file import,
|
- UX polish is ongoing: Today flow as work-resume surface, Activity-to-Journal
|
||||||
richer conflict UX, and remaining Notes polish are still incomplete.
|
review, mobile/responsive layout, search in workspace header.
|
||||||
- Activity, Journal, Browser Inbox conversion workflows, indexed Search, and
|
- Production-grade packaging, auto-update, and release workflow is partial:
|
||||||
Secrets now have baseline plugin implementations and public API contracts.
|
build scripts exist, but not a polished update channel.
|
||||||
Their remaining work is product UX depth: richer Today aggregation,
|
- Sync operation-log retention/compaction is intentionally deferred.
|
||||||
actionable Activity to Journal review flows, capture-from-clipboard/manual
|
- Secrets, plugin settings, Todo, Journal, Activity, and Browser Inbox are not
|
||||||
capture, production-grade reporting, and final polish.
|
synchronized yet.
|
||||||
- Templates plugin is not implemented yet.
|
- Browser extension chunked large-file capture is future work.
|
||||||
- File/image preview exists as a basic provider with bounded inline image
|
- Automatic conflict resolution is intentionally absent.
|
||||||
rendering through the public Files API.
|
|
||||||
- Browser extension repository has protocol, queue, and Chromium/Firefox build
|
|
||||||
scaffold; desktop has a bounded, token-paired local receiver and mounted-view
|
|
||||||
inbox plugin; receiver pairing settings, basic Browser Inbox domain binding,
|
|
||||||
create-note conversion, and
|
|
||||||
create-link conversion are implemented, text file attachment conversion is
|
|
||||||
implemented, bounded binary attachment conversion is implemented, and Activity
|
|
||||||
records conversions. Chunked large-file attachment capture remains future
|
|
||||||
work.
|
|
||||||
- Packaging/update/release workflow is not product-grade yet.
|
|
||||||
|
|
||||||
## 4. Implementation Phases
|
## 4. Implementation Phases
|
||||||
|
|
||||||
@@ -86,11 +88,7 @@ Tasks:
|
|||||||
practical;
|
practical;
|
||||||
- [x] document each public API in SDK schemas and desktop runtime docs.
|
- [x] document each public API in SDK schemas and desktop runtime docs.
|
||||||
|
|
||||||
Verification:
|
Status: done.
|
||||||
|
|
||||||
- focused e2e/smoke tests for command palette, status bar, action surfaces, and
|
|
||||||
contribution lifecycle;
|
|
||||||
- `go test ./...`, frontend build/e2e, official plugin checks, SDK tests.
|
|
||||||
|
|
||||||
### Phase 2 - Files And Notes Product Surface
|
### Phase 2 - Files And Notes Product Surface
|
||||||
|
|
||||||
@@ -104,34 +102,49 @@ Tasks:
|
|||||||
- [x] add Notes delete/trash through Files API with confirmation;
|
- [x] add Notes delete/trash through Files API with confirmation;
|
||||||
- [x] add Files restore metadata view;
|
- [x] add Files restore metadata view;
|
||||||
- [x] add Files restore command;
|
- [x] add Files restore command;
|
||||||
- [x] define external open/show-in-folder as a public v2 API, replacing fallback;
|
- [x] define external open/show-in-folder as a public v2 API;
|
||||||
- [x] add watcher-based refresh for Files/Notes after external changes;
|
- [x] add watcher-based refresh for Files/Notes after external changes;
|
||||||
- [x] add safe binary read/streaming contract only after text workflows are stable.
|
- [x] add safe binary read/streaming contract.
|
||||||
|
|
||||||
Verification:
|
Status: done.
|
||||||
|
|
||||||
- plugin smoke tests and desktop e2e for create/open/rename/trash/reload;
|
|
||||||
- no `.verstak/notes`, no special `Overview.md`, no direct backend bypasses.
|
|
||||||
|
|
||||||
### Phase 3 - Sync Hardening
|
### Phase 3 - Sync Hardening
|
||||||
|
|
||||||
Goal: make cross-device sync reliable enough for real vaults.
|
Goal: make local-first cross-device file/workspace sync reliable and harden the
|
||||||
|
optional self-hosted relay without making it the source of vault truth.
|
||||||
|
|
||||||
Tasks:
|
Verified in the current implementation:
|
||||||
|
|
||||||
- [x] define conflict UX contract for desktop and sync plugin;
|
- [x] define conflict UX contract for desktop and sync plugin;
|
||||||
- [x] add server/device revocation checks for sync auth paths;
|
- [x] add server/device revocation checks for sync auth paths;
|
||||||
- [x] persist and display sync errors in Sync plugin;
|
- [x] persist and display sync errors in Sync plugin;
|
||||||
- [x] add retry/backoff for sync client operations;
|
- [x] add retry/backoff for sync client operations;
|
||||||
- [x] add real two-vault sync smoke scenarios for create/update/move/trash
|
- [x] persist an atomic core snapshot under `.verstak/sync/`;
|
||||||
folders and files;
|
- [x] reconcile initial vaults safely;
|
||||||
- [x] document deployment and backup procedures for `verstak-sync-server`.
|
- [x] apply pulled operations strictly by `server_sequence`;
|
||||||
|
- [x] sync workspace create, rename, trash, and restore through core-owned
|
||||||
|
entity with durable `workspaceId`;
|
||||||
|
- [x] add real two-vault smoke scenarios;
|
||||||
|
- [x] document deployment and backup procedures for sync-server;
|
||||||
|
- [x] bind sync-server to loopback by default, set HTTP timeouts/graceful
|
||||||
|
shutdown, publish health/readiness/build data;
|
||||||
|
- [x] bounded JSON/push fields and pull pages;
|
||||||
|
- [x] add streamed Blob transport with SHA-256/size verification;
|
||||||
|
- [x] hash tokens, persist sessions, require CSRF for browser mutations;
|
||||||
|
- [x] add embedded sync-server web console with shared responsive templates;
|
||||||
|
- [x] remove hardcoded server HTML, split into shared layout/sidebar plus
|
||||||
|
per-section templates.
|
||||||
|
|
||||||
Verification:
|
Known limits:
|
||||||
|
|
||||||
- sync server `go test ./...`;
|
- [x] Files plugin API remains bounded to 2 MB text / 8 MB byte reads.
|
||||||
- desktop real sync smoke;
|
- [x] External rename is represented as delete + create.
|
||||||
- official Sync plugin settings/status tests.
|
- [ ] Operation-log retention/compaction is intentionally deferred.
|
||||||
|
- [ ] Secrets, plugin settings, Todo, Journal, Activity, and Browser Inbox are
|
||||||
|
not synchronized.
|
||||||
|
|
||||||
|
Status: core sync foundations complete. Retention and additional data domains
|
||||||
|
are future milestones.
|
||||||
|
|
||||||
### Phase 4 - Preview, Search, Activity, Journal
|
### Phase 4 - Preview, Search, Activity, Journal
|
||||||
|
|
||||||
@@ -139,30 +152,18 @@ Goal: add the next visible product layer as replaceable plugins.
|
|||||||
|
|
||||||
Tasks:
|
Tasks:
|
||||||
|
|
||||||
- [x] keep Markdown preview inside `verstak.markdown-editor`, with no separate
|
- [x] keep Markdown preview inside `verstak.default-editor`, with no separate
|
||||||
provider competing for `.md` files;
|
provider competing for `.md` files;
|
||||||
- [x] implement basic image metadata preview plugin;
|
- [x] implement basic image metadata preview plugin;
|
||||||
- [x] implement baseline `verstak.search` workspace plugin and expose
|
- [x] implement baseline `verstak.search` workspace plugin;
|
||||||
`searchProviders` in contribution summaries;
|
- [x] add type-as-you-search behavior and vault path/name matches;
|
||||||
- [x] add type-as-you-search behavior and vault path/name matches to
|
- [x] implement baseline `verstak.activity` event log plugin;
|
||||||
`verstak.search`;
|
- [x] expose Activity and Browser Inbox as global sidebar views;
|
||||||
- [x] implement baseline `verstak.activity` event log plugin with
|
|
||||||
workspace-scoped storage, global aggregation, and public event subscriptions;
|
|
||||||
- [x] expose `verstak.activity` and `verstak.browser-inbox` as global sidebar
|
|
||||||
views while keeping their workspace items;
|
|
||||||
- [x] implement persistent search index and cross-provider runtime hosting;
|
- [x] implement persistent search index and cross-provider runtime hosting;
|
||||||
- [x] implement activity reconstruction and worklog suggestions;
|
- [x] implement activity reconstruction and worklog suggestions;
|
||||||
- [x] implement journal/worklog plugin that can consume activity suggestions.
|
- [x] implement journal/worklog plugin that can consume activity suggestions.
|
||||||
|
|
||||||
Verification:
|
Status: done. Remaining work is UX depth (Today flow, reporting, timers).
|
||||||
|
|
||||||
- provider selection tests;
|
|
||||||
- plugin lifecycle tests proving optional dependencies degrade cleanly.
|
|
||||||
|
|
||||||
Status: baseline platform/product contracts are implemented. Remaining work is
|
|
||||||
tracked as UX follow-up rather than missing runtime foundation: Today should
|
|
||||||
make Activity suggestions and Journal imports actionable from the first
|
|
||||||
workspace screen, and Journal still needs reporting/timer/export depth.
|
|
||||||
|
|
||||||
### Phase 5 - Browser Inbox
|
### Phase 5 - Browser Inbox
|
||||||
|
|
||||||
@@ -172,31 +173,19 @@ receiver and an official inbox plugin.
|
|||||||
Tasks:
|
Tasks:
|
||||||
|
|
||||||
- [x] define browser capture payload protocol;
|
- [x] define browser capture payload protocol;
|
||||||
- [x] implement minimal `verstak.browser-inbox` plugin with workspace-scoped
|
- [x] implement minimal `verstak.browser-inbox` plugin;
|
||||||
pending queues and a global aggregate view;
|
- [x] implement browser extension capture scaffold;
|
||||||
- [x] implement browser extension capture scaffold for URL, selected text,
|
|
||||||
page title, and link captures;
|
|
||||||
- [x] define local receiver permission/pairing model;
|
- [x] define local receiver permission/pairing model;
|
||||||
- [x] require an installation-local pairing token and expose its rotation through
|
- [x] require an installation-local pairing token;
|
||||||
Browser Inbox settings;
|
|
||||||
- [x] bound browser receiver payloads and file content before publishing events;
|
|
||||||
- [x] add domain-to-workspace binding;
|
- [x] add domain-to-workspace binding;
|
||||||
- [x] convert inbox entries into notes through public plugin APIs;
|
- [x] convert inbox entries into notes through public plugin APIs;
|
||||||
- [x] record converted inbox entries in Activity through public plugin events;
|
- [x] record converted inbox entries in Activity;
|
||||||
- [x] convert inbox entries into link files through public plugin APIs;
|
- [x] convert inbox entries into link files;
|
||||||
- [x] convert captured text file attachments through public plugin APIs;
|
- [x] convert captured text file attachments;
|
||||||
- [x] convert captured bounded binary attachments through public plugin APIs.
|
- [x] convert captured bounded binary attachments.
|
||||||
|
|
||||||
Verification:
|
Status: done. Remaining work: capture-from-clipboard/manual capture,
|
||||||
|
chunked large-file capture.
|
||||||
- extension build checks;
|
|
||||||
- local receiver API tests;
|
|
||||||
- inbox plugin smoke/e2e tests.
|
|
||||||
|
|
||||||
Status: baseline capture, pairing, routing, conversion, and Activity recording
|
|
||||||
workflows are implemented. Remaining work is UX follow-up:
|
|
||||||
capture-from-clipboard/manual capture, domain binding state, and conversion
|
|
||||||
outcomes in the visible app flow.
|
|
||||||
|
|
||||||
### Phase 6 - Secrets
|
### Phase 6 - Secrets
|
||||||
|
|
||||||
@@ -208,13 +197,9 @@ Tasks:
|
|||||||
- [x] define secret-store capability and permissions;
|
- [x] define secret-store capability and permissions;
|
||||||
- [x] implement encrypted local secret storage;
|
- [x] implement encrypted local secret storage;
|
||||||
- [x] add UI-only official secrets plugin;
|
- [x] add UI-only official secrets plugin;
|
||||||
- [x] integrate secret references with workspaces without exposing raw values to
|
- [x] integrate secret references with workspaces.
|
||||||
unrelated plugins.
|
|
||||||
|
|
||||||
Verification:
|
Status: done.
|
||||||
|
|
||||||
- permission denial tests;
|
|
||||||
- storage encryption and no-plaintext regression checks.
|
|
||||||
|
|
||||||
### Phase 7 - Sidecar/Sandbox Boundary
|
### Phase 7 - Sidecar/Sandbox Boundary
|
||||||
|
|
||||||
@@ -222,16 +207,13 @@ Goal: move from trusted bundled plugin JavaScript toward safer plugin execution.
|
|||||||
|
|
||||||
Tasks:
|
Tasks:
|
||||||
|
|
||||||
- implement sidecar launch protocol and local RPC;
|
- [ ] implement sidecar launch protocol and local RPC;
|
||||||
- permission-scope sidecar APIs;
|
- [ ] permission-scope sidecar APIs;
|
||||||
- stop sidecars on plugin disable;
|
- [ ] stop sidecars on plugin disable;
|
||||||
- surface sidecar logs and health in Plugin Manager;
|
- [ ] surface sidecar logs and health in Plugin Manager;
|
||||||
- document supported sidecar languages and packaging rules.
|
- [ ] document supported sidecar languages and packaging rules.
|
||||||
|
|
||||||
Verification:
|
Status: not started.
|
||||||
|
|
||||||
- sidecar health and permission tests;
|
|
||||||
- disable/failure lifecycle tests.
|
|
||||||
|
|
||||||
### Phase 8 - Packaging, Update, Release
|
### Phase 8 - Packaging, Update, Release
|
||||||
|
|
||||||
@@ -239,37 +221,50 @@ Goal: produce installable, recoverable releases.
|
|||||||
|
|
||||||
Tasks:
|
Tasks:
|
||||||
|
|
||||||
- define release artifact matrix for desktop, official plugins, SDK, sync server,
|
- [x] define release artifact matrix (desktop, plugins, SDK, sync server,
|
||||||
and browser extension;
|
browser extension);
|
||||||
- add plugin package signing/verification or equivalent integrity checks;
|
- [x] add build scripts for .deb, AppImage, Windows portable, and plugin
|
||||||
- add backup/restore docs for vault and sync server;
|
packages;
|
||||||
- add crash/log collection for local diagnostics;
|
- [ ] add plugin package signing/verification or equivalent integrity checks;
|
||||||
- build release smoke checklist.
|
- [x] add backup/restore docs for vault and sync server;
|
||||||
|
- [ ] add crash/log collection for local diagnostics;
|
||||||
|
- [ ] build release smoke checklist.
|
||||||
|
|
||||||
Verification:
|
Status: packaging scripts exist and produce release artifacts. Signing,
|
||||||
|
auto-update, and release smoke checklist are future work.
|
||||||
|
|
||||||
- clean build from clone;
|
### Phase 9 - Nested Workspaces and Folder Tree
|
||||||
- packaged desktop starts with bundled official plugins;
|
|
||||||
- documented recovery path works on a sample vault.
|
Goal: workspaces can live at any depth inside plain vault folders.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
|
||||||
|
- [x] workspace identity markers at any depth (recursive discovery)
|
||||||
|
- [x] folder metadata API (icon, color, order)
|
||||||
|
- [x] recursive tree rendering in sidebar with collapsible folders
|
||||||
|
- [x] workspace creation with parent folder selector
|
||||||
|
- [x] move workspaces between plain folders (MoveNode)
|
||||||
|
- [x] `workspaceTree` contribution point for plugin replacement
|
||||||
|
- [ ] `verstak.workspace-folders` plugin: drag-and-drop, IconPicker, color picker
|
||||||
|
- [ ] folder creation from sidebar ("Create Folder" modal)
|
||||||
|
- [ ] workspace rename from sidebar in tree mode
|
||||||
|
|
||||||
|
Status: core infrastructure complete. Plugin with rich UX (drag-and-drop,
|
||||||
|
icon/color picker) is next.
|
||||||
|
|
||||||
## 5. Immediate Execution Order
|
## 5. Immediate Execution Order
|
||||||
|
|
||||||
1. [x] Command Palette UI host in `verstak-desktop`.
|
1. [x] Command Palette UI host.
|
||||||
2. [x] Status bar item host in `verstak-desktop`.
|
2. [x] Status bar item host.
|
||||||
3. [x] External open public v2 API to replace Files fallback.
|
3. [x] External open public API.
|
||||||
4. [x] Notes trash/delete UX in `verstak-official-plugins`.
|
4. [x] Notes trash/delete UX.
|
||||||
5. [x] Sync hardening pass with expanded real two-vault smoke.
|
5. [x] Sync hardening pass.
|
||||||
6. [x] Browser inbox protocol design, extension scaffold, local receiver,
|
6. [x] Browser inbox protocol, extension scaffold, local receiver, inbox plugin,
|
||||||
minimal inbox plugin, and note/link/text-file/binary-file conversions are
|
and conversions.
|
||||||
implemented.
|
7. [ ] Product UX follow-up: make the shell-level Today flow the command center
|
||||||
7. [ ] Product UX follow-up in `verstak-desktop`: make the shell-level Today
|
for captures, recent activity, Activity worklog suggestions, and Journal
|
||||||
flow the command center for captures, recent activity, Activity worklog
|
import/review. This should reuse existing official plugin contracts instead
|
||||||
suggestions, and Journal import/review. This should reuse existing official
|
of moving Activity, Browser Inbox, or Journal into desktop core.
|
||||||
plugin contracts instead of moving Activity, Browser Inbox, or Journal into
|
|
||||||
desktop core.
|
|
||||||
|
|
||||||
This order finishes generic platform surfaces before building product features
|
|
||||||
that depend on them.
|
|
||||||
|
|
||||||
## 6. Stop Conditions
|
## 6. Stop Conditions
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,11 @@
|
|||||||
|
|
||||||
Перед работой прочитать:
|
Перед работой прочитать:
|
||||||
|
|
||||||
- `verstak-platform-docs/01_Product_Vision.md`
|
- `verstak-docs/01_Product_Vision.md`
|
||||||
- `verstak-platform-docs/02_Platform_Architecture.md`
|
- `verstak-docs/02_Platform_Architecture.md`
|
||||||
- `verstak-platform-docs/04_Plugin_System.md`
|
- `verstak-docs/04_Plugin_System.md`
|
||||||
- `verstak-platform-docs/05_Official_Plugins.md`
|
- `verstak-docs/05_Official_Plugins.md`
|
||||||
- `verstak-platform-docs/06_Migration_Strategy.md`
|
- `verstak-docs/06_Migration_Strategy.md`
|
||||||
|
|
||||||
Если код противоречит документам, не молча подгонять документы под код. Сначала понять, это старый монолитный долг или осознанное новое решение.
|
Если код противоречит документам, не молча подгонять документы под код. Сначала понять, это старый монолитный долг или осознанное новое решение.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
Verstak Documentation
|
||||||
|
Copyright (C) 2026 Verstak contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
The complete license text is available at:
|
||||||
|
https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
@@ -1,35 +1,55 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
# Verstak Platform Docs
|
||||||
|
|
||||||
|
### Architecture and platform documentation for Verstak.
|
||||||
|
|
||||||
|
**English** · [Русский](README.ru.md)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
# Platform Architecture Reference
|
# Platform Architecture Reference
|
||||||
|
|
||||||
Сводные архитектурные артефакты платформы Верстак.
|
Architecture artifacts for the Verstak platform.
|
||||||
|
|
||||||
## Содержание
|
> Each executable component's build instructions live in its own README:
|
||||||
|
> Desktop, official plugins, browser extension and SDK must be built from
|
||||||
|
> the same release line.
|
||||||
|
|
||||||
- [Product Vision](01_Product_Vision.md) — что остаётся неизменным
|
## Contents
|
||||||
- [Platform Architecture](02_Platform_Architecture.md) — устройство ядра
|
|
||||||
- [Repositories](03_Repositories.md) — разбиение на репозитории
|
|
||||||
- [Plugin System](04_Plugin_System.md) — динамические плагины
|
|
||||||
- [Official Plugins](05_Official_Plugins.md) — состав официальных плагинов
|
|
||||||
- [Platform Development Strategy](06_Migration_Strategy.md) — план развития v2
|
|
||||||
- [Full Implementation Roadmap](07_Full_Implementation_Roadmap.md) — путь до полной реализации Verstak2
|
|
||||||
|
|
||||||
## Схемы SDK
|
- [Product Vision](01_Product_Vision.md) — what stays unchanged
|
||||||
|
- [Platform Architecture](02_Platform_Architecture.md) — core design
|
||||||
|
- [Repositories](03_Repositories.md) — repository layout
|
||||||
|
- [Plugin System](04_Plugin_System.md) — dynamic plugins
|
||||||
|
- [Official Plugins](05_Official_Plugins.md) — plugin set and current status
|
||||||
|
- [Development Strategy](06_Migration_Strategy.md) — platform evolution
|
||||||
|
- [Implementation Roadmap](07_Full_Implementation_Roadmap.md) — full roadmap with phase status
|
||||||
|
|
||||||
Схемы данных и контракты SDK находятся в репозитории [`verstak-sdk`](https://git.mirv.top/verstak/verstak-sdk).
|
## SDK Schemas
|
||||||
|
|
||||||
- [Manifest Schema](../verstak-sdk/schemas/manifest.json)
|
Data schemas and SDK contracts live in the
|
||||||
- [Capabilities](../verstak-sdk/schemas/capabilities.json)
|
[`verstak-sdk`](https://github.com/mirivlad/verstak-sdk) repository.
|
||||||
- [Contributions](../verstak-sdk/schemas/contributions.json)
|
|
||||||
- [Permissions](../verstak-sdk/schemas/permissions.json)
|
|
||||||
- [Event Schemas](../verstak-sdk/schemas/events/)
|
|
||||||
- [Sync Operations](../verstak-sdk/schemas/sync.json)
|
|
||||||
|
|
||||||
## Репозитории
|
- [Manifest Schema](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/manifest.json)
|
||||||
|
- [Capabilities](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/capabilities.json)
|
||||||
|
- [Contributions](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/contributions.json)
|
||||||
|
- [Permissions](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/permissions.json)
|
||||||
|
- [Event Schemas](https://github.com/mirivlad/verstak-sdk/tree/main/schemas/events)
|
||||||
|
- [Sync Operations](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/sync.json)
|
||||||
|
|
||||||
| Репозиторий | Назначение |
|
## Repositories
|
||||||
|
|
||||||
|
| Repository | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `verstak-desktop` | Core Platform + UI Shell |
|
| [`verstak`](https://github.com/mirivlad/verstak) | Core Platform + UI Shell |
|
||||||
| `verstak-official-plugins` | Официальные плагины |
|
| [`verstak-official-plugins`](https://github.com/mirivlad/verstak-official-plugins) | Official plugins |
|
||||||
| `verstak-sdk` | Plugin SDK и схемы |
|
| [`verstak-sdk`](https://github.com/mirivlad/verstak-sdk) | Plugin SDK and schemas |
|
||||||
| `verstak-sync-server` | Сервер синхронизации |
|
| [`verstak-sync-server`](https://github.com/mirivlad/verstak-sync-server) | Sync server |
|
||||||
| `verstak-browser-extension` | Расширение браузера |
|
| [`verstak-browser-extension`](https://github.com/mirivlad/verstak-browser-extension) | Browser extension |
|
||||||
| `verstak-docs` | Документация |
|
| [`verstak-docs`](https://github.com/mirivlad/verstak-docs) | Documentation |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2026 Verstak contributors. Documentation is licensed under
|
||||||
|
[GNU AGPLv3 or later](LICENSE).
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
# Verstak Platform Docs
|
||||||
|
|
||||||
|
### Architecture and platform documentation for Verstak.
|
||||||
|
|
||||||
|
[English](README.md) · **Русский**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
# Platform Architecture Reference
|
||||||
|
|
||||||
|
Сводные архитектурные артефакты платформы Верстак.
|
||||||
|
|
||||||
|
> Сборка каждого исполняемого компонента описана в его README: Desktop,
|
||||||
|
> official plugins, browser extension и SDK должны собираться из одной
|
||||||
|
> release-линейки.
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
- [Product Vision](01_Product_Vision.md) — что остаётся неизменным
|
||||||
|
- [Platform Architecture](02_Platform_Architecture.md) — устройство ядра
|
||||||
|
- [Repositories](03_Repositories.md) — разбиение на репозитории
|
||||||
|
- [Plugin System](04_Plugin_System.md) — динамические плагины
|
||||||
|
- [Official Plugins](05_Official_Plugins.md) — состав официальных плагинов
|
||||||
|
- [Platform Development Strategy](06_Migration_Strategy.md) — стратегия развития
|
||||||
|
- [Full Implementation Roadmap](07_Full_Implementation_Roadmap.md) — дорожная карта
|
||||||
|
|
||||||
|
## Схемы SDK
|
||||||
|
|
||||||
|
Схемы данных и контракты SDK находятся в репозитории
|
||||||
|
[`verstak-sdk`](https://github.com/mirivlad/verstak-sdk).
|
||||||
|
|
||||||
|
- [Manifest Schema](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/manifest.json)
|
||||||
|
- [Capabilities](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/capabilities.json)
|
||||||
|
- [Contributions](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/contributions.json)
|
||||||
|
- [Permissions](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/permissions.json)
|
||||||
|
- [Event Schemas](https://github.com/mirivlad/verstak-sdk/tree/main/schemas/events)
|
||||||
|
- [Sync Operations](https://github.com/mirivlad/verstak-sdk/blob/main/schemas/sync.json)
|
||||||
|
|
||||||
|
## Репозитории
|
||||||
|
|
||||||
|
| Репозиторий | Назначение |
|
||||||
|
|---|---|
|
||||||
|
| [`verstak`](https://github.com/mirivlad/verstak) | Core Platform + UI Shell |
|
||||||
|
| [`verstak-official-plugins`](https://github.com/mirivlad/verstak-official-plugins) | Официальные плагины |
|
||||||
|
| [`verstak-sdk`](https://github.com/mirivlad/verstak-sdk) | Plugin SDK и схемы |
|
||||||
|
| [`verstak-sync-server`](https://github.com/mirivlad/verstak-sync-server) | Сервер синхронизации |
|
||||||
|
| [`verstak-browser-extension`](https://github.com/mirivlad/verstak-browser-extension) | Расширение браузера |
|
||||||
|
| [`verstak-docs`](https://github.com/mirivlad/verstak-docs) | Документация |
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
Copyright © 2026 Verstak contributors. Документация распространяется на
|
||||||
|
условиях [GNU AGPLv3 или новее](LICENSE).
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
# Milestone: Platform Runtime 1
|
|
||||||
|
|
||||||
Фиксирует текущее состояние plugin runtime в Milestone 1.
|
|
||||||
|
|
||||||
## Что уже работает
|
|
||||||
|
|
||||||
- **Plugin Discovery** — сканирование директорий, чтение plugin.json, валидация манифеста.
|
|
||||||
- **Core Capabilities** — 5 capabilities регистрируются в `main.go` ДО plugin discovery.
|
|
||||||
- **Plugin Lifecycle** — полный цикл: discovered → loaded / degraded / missing-required-capability / failed.
|
|
||||||
- **Dev Install Flow** — `./scripts/build.sh` + `./scripts/install-dev-plugins.sh`.
|
|
||||||
- **Smoke Platform** — `./scripts/smoke-platform.sh` проверяет discovery, capabilities, lifecycle без GUI.
|
|
||||||
- **Wails v2 + Svelte 4** — core запускается, Plugin Manager UI работает.
|
|
||||||
- **platform-test plugin** — собирается, устанавливается, проходит lifecycle, регистрирует contributions.
|
|
||||||
|
|
||||||
## Репозитории
|
|
||||||
|
|
||||||
| Репозиторий | За что отвечает |
|
|
||||||
|---|---|
|
|
||||||
| `verstak-desktop` | Core platform: plugin runtime, capability/contribution/permission registries, event bus, Wails shell, Plugin Manager UI |
|
|
||||||
| `verstak-official-plugins` | Монорепо официальных плагинов (platform-test и будущие) |
|
|
||||||
| `verstak-docs` | Документация платформы (этот репозиторий) |
|
|
||||||
| `verstak-sdk` | Manifest schema + TypeScript SDK |
|
|
||||||
| `verstak-sync-server` | Сервер синхронизации |
|
|
||||||
| `verstak-browser-extension` | Расширение Firefox |
|
|
||||||
|
|
||||||
## Core Capabilities (зарегистрированы)
|
|
||||||
|
|
||||||
Следующие capabilities регистрируются в `main.go` до plugin discovery, чтобы плагины могли разрешить `requires` при загрузке:
|
|
||||||
|
|
||||||
| Capability | Описание |
|
|
||||||
|---|---|
|
|
||||||
| `verstak/core/plugin-manager/v1` | Управление плагинами: discovery, enable/disable, reload |
|
|
||||||
| `verstak/core/capability-registry/v1` | Реестр возможностей: регистрация, запрос, проверка зависимостей |
|
|
||||||
| `verstak/core/contribution-registry/v1` | Реестр контрибуций: views, commands, sidebar items, actions и т.д. |
|
|
||||||
| `verstak/core/permissions/v1` | Реестр разрешений: проверка dangerous, запрос пользователю |
|
|
||||||
| `verstak/core/events/v1` | In-process event bus: publish/subscribe |
|
|
||||||
|
|
||||||
### Capabilities НЕ зарегистрированы (намеренно)
|
|
||||||
|
|
||||||
| Capability | Почему отсутствует |
|
|
||||||
|---|---|
|
|
||||||
| `verstak/core/vault/v1` | Реализация vault api (`internal/core/vault/api.go`) ещё не создана |
|
|
||||||
| `verstak/core/sync/v1` | Реализация sync boundary (`internal/core/sync/boundary.go`) ещё не создана |
|
|
||||||
|
|
||||||
Плагины, объявляющие эти capabilities в `optionalRequires`, переходят в статус `degraded`, но продолжают работать. Плагины с `requires` получат `missing-required-capability`.
|
|
||||||
|
|
||||||
## DEGRADED: что означает
|
|
||||||
|
|
||||||
Плагин получает статус `degraded`, когда:
|
|
||||||
|
|
||||||
1. Все `requires` capabilities разрешены (есть в registry).
|
|
||||||
2. Хотя бы одна из `optionalRequires` capabilities отсутствует.
|
|
||||||
|
|
||||||
В этом состоянии:
|
|
||||||
- Плагин загружается и регистрирует свои `provides` capabilities.
|
|
||||||
- Contributions (views, commands, sidebar items) попадают в registry.
|
|
||||||
- Plugin Manager UI показывает статус `degraded` с указанием отсутствующих optional capabilities.
|
|
||||||
- UI-функции, зависящие от отсутствующих capabilities, должны скрываться или gracefully degrade.
|
|
||||||
|
|
||||||
## Команды для полной проверки
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Собрать официальные плагины (соберёт frontend, backend, упакует dist/)
|
|
||||||
cd ~/git/verstak2/verstak-official-plugins
|
|
||||||
./scripts/build.sh
|
|
||||||
|
|
||||||
# 2. Установить platform-test как dev plugin в verstak-desktop
|
|
||||||
cd ~/git/verstak2/verstak-desktop
|
|
||||||
./scripts/install-dev-plugins.sh
|
|
||||||
|
|
||||||
# 3. Запустить smoke-проверку (headless, без GUI)
|
|
||||||
./scripts/smoke-platform.sh
|
|
||||||
|
|
||||||
# 4. Запустить приложение (откроет Wails GUI)
|
|
||||||
go run -mod=mod .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Проверка через Plugin Manager UI
|
|
||||||
|
|
||||||
1. Запустить приложение: `go run -mod=mod .`
|
|
||||||
2. Открыть Plugin Manager.
|
|
||||||
3. Убедиться, что `verstak.platform-test` отображается со статусом `degraded`.
|
|
||||||
4. Убедиться, что указаны отсутствующие optional capabilities (`verstak/core/vault/v1`, `verstak/core/sync/v1`).
|
|
||||||
5. Contributions (view, commands, sidebar item) должны быть видны.
|
|
||||||
|
|
||||||
## Файлы реализации
|
|
||||||
|
|
||||||
| Файл | Назначение |
|
|
||||||
|---|---|
|
|
||||||
| `verstak-desktop/main.go` | Инициализация core, регистрация capabilities, plugin lifecycle, Wails run |
|
|
||||||
| `verstak-desktop/internal/api/app.go` | Wails-bound API, ReloadPlugins, GetPlugins, GetCapabilities |
|
|
||||||
| `verstak-desktop/internal/core/plugin/plugin.go` | Manifest struct, ValidateManifest, DiscoverPlugins, Status constants |
|
|
||||||
| `verstak-desktop/internal/core/capability/registry.go` | CapabilityRegistry: Register, CheckRequired, List |
|
|
||||||
| `verstak-desktop/internal/core/contribution/registry.go` | ContributionRegistry: views, commands, sidebar items и т.д. |
|
|
||||||
| `verstak-desktop/internal/core/permissions/registry.go` | PermissionsRegistry: defaults, IsDangerous |
|
|
||||||
| `verstak-desktop/internal/core/events/bus.go` | EventBus: Subscribe, Publish |
|
|
||||||
| `verstak-desktop/cmd/smoke-platform/main.go` | Headless smoke test |
|
|
||||||
| `verstak-official-plugins/scripts/build.sh` | Build + package всех плагинов |
|
|
||||||
| `verstak-desktop/scripts/install-dev-plugins.sh` | Копирование dist/ в ./plugins/ |
|
|
||||||
| `verstak-desktop/scripts/smoke-platform.sh` | Полная smoke-проверка |
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Milestone: Platform Runtime 2 — Vault Core Capability
|
|
||||||
|
|
||||||
Фиксирует состояние plugin runtime после добавления vault layer как core capability.
|
|
||||||
|
|
||||||
## Что реализовано
|
|
||||||
|
|
||||||
- **Vault Layer** — полноценный vault service (`internal/core/vault/vault.go`) с созданием, открытием, закрытием и валидацией.
|
|
||||||
- **Vault Capability** — `verstak/core/vault/v1` регистрируется в `main.go` и доступен плагинам через capability registry.
|
|
||||||
- **Vault Events** — `vault.created`, `vault.opened`, `vault.closed` публикуются в event bus.
|
|
||||||
- **Vault Tests** — `internal/core/vault/vault_test.go` покрывает layout creation, open/close cycle, corrupt JSON, path traversal, plugin namespace paths, status transitions, event publishing.
|
|
||||||
- **platform-test plugin** — статус обновлён: был `degraded` из-за отсутствия vault, теперь `degraded` только из-за отсутствия `verstak/core/sync/v1`.
|
|
||||||
|
|
||||||
## Vault Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
<base>/
|
|
||||||
VerstakVault/ ← vault root (создаётся CreateVault)
|
|
||||||
.verstak/
|
|
||||||
vault.json ← VaultMeta: schemaVersion=1, vaultId (UUID), createdAt, updatedAt, app="verstak"
|
|
||||||
plugin-data/ ← per-plugin data namespaces
|
|
||||||
<plugin-id>/
|
|
||||||
plugin-settings/ ← per-plugin settings namespaces
|
|
||||||
<plugin-id>/
|
|
||||||
plugin-cache/ ← per-plugin cache namespaces
|
|
||||||
<plugin-id>/
|
|
||||||
trash/ ← soft-deleted items
|
|
||||||
logs/ ← vault-scoped logs
|
|
||||||
```
|
|
||||||
|
|
||||||
`EnsureVaultLayout()` создаёт `.verstak/` и все стандартные поддиректории. Идемпотентен — безопасно вызывать повторно.
|
|
||||||
|
|
||||||
## Vault API
|
|
||||||
|
|
||||||
| Метод | Описание |
|
|
||||||
|---|---|
|
|
||||||
| `NewVault(bus)` | Создаёт Vault instance. Начальный статус: `not-created`. |
|
|
||||||
| `CreateVault(path)` | Создаёт `VerstakVault/` на указанном пути. Генерирует `vault.json` с UUID. Публикует `vault.created`. Статус → `open`. |
|
|
||||||
| `OpenVault(path)` | Открывает существующий vault. Валидирует `vault.json` (schemaVersion, vaultId). Публикует `vault.opened`. Статус → `open`. |
|
|
||||||
| `CloseVault()` | Закрывает vault. Сбрасывает path и meta. Публикует `vault.closed`. Статус → `closed`. |
|
|
||||||
| `GetVaultStatus()` | Текущий статус: `not-created`, `closed`, `open`, `error`. |
|
|
||||||
| `GetVaultPath()` | Путь к vault root. |
|
|
||||||
| `GetVaultMeta()` | Указатель на `VaultMeta` (vaultId, timestamps, app). |
|
|
||||||
| `ResolveSafePath(rel)` | Резолвит относительный путь внутри vault. Блокирует `../` traversal. Требует `open` vault. |
|
|
||||||
| `GetPluginDataPath(id)` | Возвращает (и создаёт) `.verstak/plugin-data/<id>/`. |
|
|
||||||
| `GetPluginSettingsPath(id)` | Возвращает (и создаёт) `.verstak/plugin-settings/<id>/`. |
|
|
||||||
| `GetPluginCachePath(id)` | Возвращает (и создаёт) `.verstak/plugin-cache/<id>/`. |
|
|
||||||
|
|
||||||
## Vault Events
|
|
||||||
|
|
||||||
| Event | Когда | Payload |
|
|
||||||
|---|---|---|
|
|
||||||
| `vault.created` | Успешный `CreateVault` | `path`, `vaultId` |
|
|
||||||
| `vault.opened` | Успешный `OpenVault` | `path`, `vaultId` |
|
|
||||||
| `vault.closed` | `CloseVault` | `vaultId` |
|
|
||||||
| `vault.error` | Ошибки операций | `error` |
|
|
||||||
|
|
||||||
## Vault Status Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
not-created ──CreateVault──▶ open ──CloseVault──▶ closed
|
|
||||||
│ │
|
|
||||||
└──OpenVault─────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Capabilities (зарегистрированы)
|
|
||||||
|
|
||||||
| Capability | Описание |
|
|
||||||
|---|---|
|
|
||||||
| `verstak/core/plugin-manager/v1` | Управление плагинами: discovery, enable/disable, reload |
|
|
||||||
| `verstak/core/capability-registry/v1` | Реестр возможностей: регистрация, запрос, проверка зависимостей |
|
|
||||||
| `verstak/core/contribution-registry/v1` | Реестр контрибуций: views, commands, sidebar items, actions |
|
|
||||||
| `verstak/core/permissions/v1` | Реестр разрешений: проверка dangerous, запрос пользователю |
|
|
||||||
| `verstak/core/events/v1` | In-process event bus: publish/subscribe |
|
|
||||||
| `verstak/core/vault/v1` | Vault service: создание/открытие/закрытие vault, plugin namespace paths, safe path resolution |
|
|
||||||
|
|
||||||
Все 6 capabilities регистрируются в `main.go` до plugin discovery.
|
|
||||||
|
|
||||||
## platform-test: текущий статус
|
|
||||||
|
|
||||||
| Аспект | Статус |
|
|
||||||
|---|---|
|
|
||||||
| Discovery | ✅ |
|
|
||||||
| Capability resolution (required) | ✅ |
|
|
||||||
| Capability resolution (optional: vault) | ✅ |
|
|
||||||
| Capability resolution (optional: sync) | ❌ `verstak/core/sync/v1` отсутствует |
|
|
||||||
| **Итоговый статус** | **degraded** (только из-за missing sync optional) |
|
|
||||||
|
|
||||||
## Команды для проверки
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Собрать официальные плагины
|
|
||||||
cd ~/git/verstak2/verstak-official-plugins
|
|
||||||
./scripts/build.sh
|
|
||||||
|
|
||||||
# 2. Установить platform-test как dev plugin
|
|
||||||
cd ~/git/verstak2/verstak-desktop
|
|
||||||
./scripts/install-dev-plugins.sh
|
|
||||||
|
|
||||||
# 3. Smoke-проверка (headless)
|
|
||||||
./scripts/smoke-platform.sh
|
|
||||||
|
|
||||||
# 4. Запуск приложения
|
|
||||||
cd ~/git/verstak2/verstak-desktop
|
|
||||||
go run -mod=mod .
|
|
||||||
|
|
||||||
# 5. Vault unit tests
|
|
||||||
go test ./internal/core/vault/ -v
|
|
||||||
```
|
|
||||||
|
|
||||||
## Что НЕ сделано (намеренно)
|
|
||||||
|
|
||||||
- **Sync** (`verstak/core/sync/v1`) — sync boundary существует как заглушка, полная реализация позже.
|
|
||||||
- **Notes** — плагин для заметок не входит в core.
|
|
||||||
- **Files** — плагин для файлового менеджера не входит в core.
|
|
||||||
- **Editor** — markdown editor не входит в core.
|
|
||||||
|
|
||||||
Vault — фундамент для всех этих будущих плагинов, но сам по себе не зависит от них.
|
|
||||||
|
|
||||||
## Файлы реализации
|
|
||||||
|
|
||||||
| Файл | Назначение |
|
|
||||||
|---|---|
|
|
||||||
| `verstak-desktop/main.go` | Инициализация core, регистрация 6 capabilities (включая vault), plugin lifecycle |
|
|
||||||
| `verstak-desktop/internal/core/vault/vault.go` | Vault service: CreateVault, OpenVault, CloseVault, ResolveSafePath, plugin namespace paths |
|
|
||||||
| `verstak-desktop/internal/core/vault/vault_test.go` | Vault tests: layout, lifecycle, path traversal, events |
|
|
||||||
| `verstak-desktop/internal/core/capability/registry.go` | CapabilityRegistry |
|
|
||||||
| `verstak-desktop/internal/core/events/bus.go` | EventBus |
|
|
||||||
| `verstak-desktop/internal/api/app.go` | Wails API, ReloadPlugins |
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# Milestone 4 — App Settings + Vault Plugin State + First Run UI
|
|
||||||
|
|
||||||
**Дата:** 2026-06-17
|
|
||||||
**Статус:** ✅ Завершён
|
|
||||||
|
|
||||||
## Цель
|
|
||||||
|
|
||||||
Сделать нормальную модель настроек приложения, выбор vault при первом запуске, и enable/disable плагинов через vault plugin state.
|
|
||||||
|
|
||||||
## Что сделано
|
|
||||||
|
|
||||||
### 1. App Settings Core (`internal/core/appsettings/`)
|
|
||||||
|
|
||||||
- `manager.go` — Load/Save/Update, recent vaults, defaults, corrupt config recovery
|
|
||||||
- `manager_test.go` — 6 тестов
|
|
||||||
- Хранение: `~/.config/verstak/config.json`
|
|
||||||
- Поля: currentVaultPath, recentVaults, theme, devMode, userPluginsDir, windowState, lastOpenedAt
|
|
||||||
- Правила: defaults при отсутствии, backup+recovery при битом config, без secrets
|
|
||||||
|
|
||||||
### 2. Vault Plugin State (`internal/core/pluginstate/`)
|
|
||||||
|
|
||||||
- `manager.go` — enable/disable, desired plugins, missing-installed tracking
|
|
||||||
- `manager_test.go` — 7 тестов
|
|
||||||
- Хранение: `<vault>/.verstak/plugins.json`
|
|
||||||
- Поля: enabledPlugins, disabledPlugins, desiredPlugins, updatedAt
|
|
||||||
- Installed ≠ Enabled ≠ Desired
|
|
||||||
|
|
||||||
### 3. Wails API (app.go)
|
|
||||||
|
|
||||||
- `GetAppSettings()` / `UpdateAppSettings(patch)` / `SetCurrentVault(path)`
|
|
||||||
- `GetVaultPluginState()` / `EnablePlugin(id)` / `DisablePlugin(id)`
|
|
||||||
- `SetCurrentVault` вызывает `OpenVault` + сохраняет в app settings + загружает plugin state
|
|
||||||
|
|
||||||
### 4. First Run / Vault Selection UI (VaultSelection.svelte)
|
|
||||||
|
|
||||||
- Показывается когда currentVaultPath пустой или vault не открывается
|
|
||||||
- Create New Vault → CreateVault → OpenVault → SetCurrentVault
|
|
||||||
- Open Existing Vault → OpenVault → SetCurrentVault
|
|
||||||
- Recent Vaults → OpenVault → SetCurrentVault
|
|
||||||
- Понятные ошибки при неудаче
|
|
||||||
|
|
||||||
### 5. Sidebar Navigation (Sidebar.svelte)
|
|
||||||
|
|
||||||
- Ширина 220px, фиксированная
|
|
||||||
- Навигация: Plugin Manager
|
|
||||||
- Plugin sidebar items (из contributions)
|
|
||||||
- Vault status indicator
|
|
||||||
- Отступы и hover-эффекты
|
|
||||||
|
|
||||||
### 6. Plugin Manager Integration
|
|
||||||
|
|
||||||
- Enable/Disable toggle в PluginCard
|
|
||||||
- Disabled plugin не регистрирует capabilities/contributions
|
|
||||||
- Missing installed plugins — отдельная секция
|
|
||||||
- Vault state загружается при открытии
|
|
||||||
|
|
||||||
### 7. Layout Fixes
|
|
||||||
|
|
||||||
- App.svelte: global reset (margin, padding, box-sizing)
|
|
||||||
- PluginManager: отступы header, border-bottom separator
|
|
||||||
- Content area: padding 1.5rem
|
|
||||||
|
|
||||||
## Тесты
|
|
||||||
|
|
||||||
- `go test ./...` — 52 PASS (6 appsettings + 7 pluginstate + 39 previous)
|
|
||||||
- `./scripts/check.sh` — ✅
|
|
||||||
- `./scripts/smoke-platform.sh` — ✅ (enable/disable/plugins.json verification)
|
|
||||||
- `./scripts/build.sh` — ✅
|
|
||||||
|
|
||||||
## Структура файлов
|
|
||||||
|
|
||||||
```
|
|
||||||
~/.config/verstak/config.json ← app settings (local)
|
|
||||||
<vault>/.verstak/plugins.json ← vault plugin state
|
|
||||||
<vault>/.verstak/plugin-settings/<id>/ ← per-plugin settings
|
|
||||||
<vault>/.verstak/plugin-data/<id>/ ← per-plugin data
|
|
||||||
<vault>/.verstak/plugin-cache/<id>/ ← per-plugin cache
|
|
||||||
```
|
|
||||||
|
|
||||||
## Пример plugins.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"enabledPlugins": ["verstak.platform-test"],
|
|
||||||
"disabledPlugins": [],
|
|
||||||
"desiredPlugins": [
|
|
||||||
{
|
|
||||||
"id": "verstak.platform-test",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"source": "official"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updatedAt": "2026-06-17T..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Исправление отчёта Milestone 3
|
|
||||||
|
|
||||||
В отчёте Milestone 3 была арифметическая ошибка: написано "24/24 PASS", реально — **39 PASS** (16 plugin + 8 storage + 7 vault + 8 other). Исправлено в документации.
|
|
||||||
|
|
||||||
## Что НЕ сделано (будет в следующих milestone)
|
|
||||||
|
|
||||||
- Notes/files/editor/sync plugins
|
|
||||||
- Plugin marketplace/distribution
|
|
||||||
- Auto-install plugins
|
|
||||||
- Advanced window state management
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# Milestone 4b — App Settings + Vault Plugin State (UI Completion)
|
|
||||||
|
|
||||||
## Цель
|
|
||||||
|
|
||||||
Довести Milestone 4 до пользовательски завершённого состояния:
|
|
||||||
- При первом запуске можно выбрать/создать vault
|
|
||||||
- После выбора путь сохраняется в app settings
|
|
||||||
- При следующем запуске vault открывается автоматически
|
|
||||||
- Plugin Manager умеет enable/disable plugins через vault plugin state
|
|
||||||
|
|
||||||
## Что сделано
|
|
||||||
|
|
||||||
### 1. First Run / Vault Selection UI
|
|
||||||
|
|
||||||
Новый компонент `VaultSelection.svelte`:
|
|
||||||
- Экран показывается если `currentVaultPath` пустой или vault не открывается
|
|
||||||
- Create new vault → `CreateVault(path)` → `SetCurrentVault(path)`
|
|
||||||
- Open existing vault → `SetCurrentVault(path)`
|
|
||||||
- Recent vaults из app settings
|
|
||||||
- Понятная ошибка если vault не открылся
|
|
||||||
- После успеха → событие `verstak:vault-opened` → переход в основной UI
|
|
||||||
|
|
||||||
### 2. App Startup Flow
|
|
||||||
|
|
||||||
Обновлён `App.svelte`:
|
|
||||||
- При монтировании: `GetAppSettings()` + `GetVaultStatus()`
|
|
||||||
- Если `currentVaultPath` пустой или vault не open → `needsVaultSelection = true`
|
|
||||||
- Показывает `VaultSelection` или основной UI
|
|
||||||
- Слушает `verstak:vault-opened` для перехода
|
|
||||||
- Добавлены отступы: `padding: 1.5rem` на `.content`
|
|
||||||
|
|
||||||
### 3. Plugin Manager Enable/Disable
|
|
||||||
|
|
||||||
Обновлён `PluginCard.svelte`:
|
|
||||||
- Кнопка "▶ Enable" если plugin disabled
|
|
||||||
- Кнопка "⏸ Disable" если plugin enabled
|
|
||||||
- Скрыты если vault не открыт (показывается hint)
|
|
||||||
- Скрыты если plugin failed/incompatible
|
|
||||||
|
|
||||||
Обновлён `PluginManager.svelte`:
|
|
||||||
- `enablePlugin(id)` → `EnablePlugin(id)` → `reload()`
|
|
||||||
- `disablePlugin(id)` → `DisablePlugin(id)` → `reload()`
|
|
||||||
- Загрузка `vaultPluginState` при vault open
|
|
||||||
- Вычисление `missingInstalled` из desired plugins
|
|
||||||
|
|
||||||
### 4. Missing Installed Plugins UI
|
|
||||||
|
|
||||||
Новый блок в `PluginManager.svelte`:
|
|
||||||
- Показывает desired plugins которых нет локально
|
|
||||||
- Карточка с красной рамкой и статусом "missing"
|
|
||||||
- Показывает source если известен
|
|
||||||
- Auto-install НЕ делается
|
|
||||||
|
|
||||||
### 5. Backend Changes
|
|
||||||
|
|
||||||
`internal/api/app.go`:
|
|
||||||
- `SetCurrentVault(path)` — открывает vault, сохраняет в app settings, загружает plugin state, регистрирует vault capability
|
|
||||||
- `RecordDesiredPlugin(id, version, source)` — записывает desired plugin
|
|
||||||
- `ReloadPlugins()` — записывает desired plugins при discovery (только если vault open)
|
|
||||||
|
|
||||||
`main.go`:
|
|
||||||
- Запись desired plugins при первичной загрузке (только если vault open)
|
|
||||||
|
|
||||||
`internal/core/appsettings/manager.go`:
|
|
||||||
- Исправлен `addRecent()` — убран дублирующий sort
|
|
||||||
|
|
||||||
### 6. Smoke Test
|
|
||||||
|
|
||||||
Обновлён `scripts/smoke-platform.sh`:
|
|
||||||
- Добавлен `-test-enable-disable` флаг
|
|
||||||
- Создаёт temp vault, открывает, загружает plugin state
|
|
||||||
- Disable → проверяет IsDisabled/IsEnabled
|
|
||||||
- Enable → проверяет IsEnabled/IsDisabled
|
|
||||||
- Проверяет `plugins.json` на диске
|
|
||||||
|
|
||||||
## Верификация
|
|
||||||
|
|
||||||
| Команда | Результат |
|
|
||||||
|---------|-----------|
|
|
||||||
| `go test ./...` | ✅ все тесты проходят |
|
|
||||||
| `./scripts/check.sh` | ✅ go vet + gofmt + go mod tidy |
|
|
||||||
| `./scripts/smoke-platform.sh` | ✅ discovery + enable/disable |
|
|
||||||
| `./scripts/build.sh` | ✅ wails build OK |
|
|
||||||
|
|
||||||
## Хранение данных
|
|
||||||
|
|
||||||
| Что | Где | Назначение |
|
|
||||||
|-----|-----|-----------|
|
|
||||||
| App settings | `~/.config/verstak/config.json` | Локальные настройки установки |
|
|
||||||
| Vault plugin state | `<vault>/.verstak/plugins.json` | Enabled/disabled/desired plugins |
|
|
||||||
| Plugin settings | `<vault>/.verstak/plugin-settings/<id>/settings.json` | Настройки конкретного плагина |
|
|
||||||
|
|
||||||
## Пример plugins.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"enabledPlugins": ["verstak.platform-test"],
|
|
||||||
"disabledPlugins": [],
|
|
||||||
"desiredPlugins": [
|
|
||||||
{
|
|
||||||
"id": "verstak.platform-test",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"source": "official"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updatedAt": "2026-06-17T04:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Статус platform-test
|
|
||||||
|
|
||||||
- После enable: DEGRADED (optional sync missing)
|
|
||||||
- После disable: status = disabled, capabilities/contributions исчезают
|
|
||||||
- После enable обратно: DEGRADED снова
|
|
||||||
|
|
||||||
## Что НЕ сделано
|
|
||||||
|
|
||||||
- Notes/files/editor/sync
|
|
||||||
- Auto-install plugins
|
|
||||||
- Plugin marketplace/distribution
|
|
||||||
- Sync capability
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# Milestone 5a — Frontend Plugin Host: Declarative UI Contributions + Plugin Settings
|
|
||||||
|
|
||||||
## Цель
|
|
||||||
|
|
||||||
Создать первый UI-host слой, чтобы shell отображал UI contributions плагинов и корректно убирал их при disable/reload. Ошибка plugin UI не должна ронять shell.
|
|
||||||
|
|
||||||
## Contribution Points
|
|
||||||
|
|
||||||
### Реализованы в этом milestone
|
|
||||||
|
|
||||||
| Contribution Point | Backend Registry | Frontend Host | Статус |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `sidebarItems` | ✅ Registry.Register/Unregister/ListByPoint | ✅ Sidebar.svelte | Работает |
|
|
||||||
| `views` | ✅ Registry | ✅ ViewContainer.svelte (placeholder) | Работает (declarative placeholder) |
|
|
||||||
| `settingsPanels` | ✅ Registry | ✅ PluginManager.svelte | Работает |
|
|
||||||
| `commands` | ✅ Registry | ContributionRegistry (UI command palette not implemented) | Registry готов, UI planned |
|
|
||||||
|
|
||||||
### Планируемые (не реализованы)
|
|
||||||
|
|
||||||
- `fileActions`, `noteActions`, `contextMenuEntries`, `searchProviders`, `activityProviders`, `statusBarItems`
|
|
||||||
|
|
||||||
## Что сделано
|
|
||||||
|
|
||||||
### 1. Contribution Registry Lifecycle
|
|
||||||
|
|
||||||
**`internal/core/contribution/registry.go`:**
|
|
||||||
- Добавлен `ListByPoint(pointType)` — запрос contributions по типу
|
|
||||||
- `Register()` теперь idempotent: удаляет старые записи plugin перед добавлением
|
|
||||||
- Добавлены `ContributionPointType` константы для всех 10 типов
|
|
||||||
|
|
||||||
**`internal/api/app.go` — ReloadPlugins:**
|
|
||||||
- Перед регистрацией contributions вызывается `Unregister(pluginID)` → предотвращает дубли при повторном reload
|
|
||||||
- Disabled/failed plugins не регистрируют contributions
|
|
||||||
- Flattened ContributionSummary для фронтенда (FlatSidebarItem, FlatView, FlatSettingsPanel, FlatCommand)
|
|
||||||
|
|
||||||
### 2. Manifest Contributions Schema
|
|
||||||
|
|
||||||
`plugin.json` может объявлять (без изменений — схема существовала):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"contributes": {
|
|
||||||
"sidebarItems": [{ "id": "...", "title": "...", "icon": "🧪", "view": "...", "position": 100 }],
|
|
||||||
"views": [{ "id": "...", "title": "...", "component": "..." }],
|
|
||||||
"settingsPanels": [{ "id": "...", "title": "...", "component": "..." }],
|
|
||||||
"commands": [{ "id": "...", "title": "...", "icon": "⚡", "handler": "..." }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. UI Shell Rendering
|
|
||||||
|
|
||||||
**Sidebar.svelte:**
|
|
||||||
- Строит plugin sidebar items из ContributionRegistry (поле `sidebarItems`)
|
|
||||||
- Сортировка по `position` (default 100)
|
|
||||||
- Фильтрация: скрыты items от disabled/failed/incompatible плагинов
|
|
||||||
- Клик → `verstak:open-view` событие
|
|
||||||
- Error boundary: перехват ошибок API, показ "⚠️ Plugin UI error"
|
|
||||||
|
|
||||||
**ViewContainer.svelte:**
|
|
||||||
- Declarative placeholder host для plugin views
|
|
||||||
- Показывает plugin name, view id, component id, статус "frontend bundle host not implemented yet"
|
|
||||||
- Error boundary: `{#key}` + catch rendering errors → "⚠️ Plugin UI failed" fallback
|
|
||||||
- Empty state: "Select a plugin view from the sidebar"
|
|
||||||
|
|
||||||
### 4. Plugin Settings
|
|
||||||
|
|
||||||
**PluginManager.svelte:**
|
|
||||||
- Загружает `settingsPanels` из ContributionRegistry
|
|
||||||
- PluginCard принимает `settingsPanels` prop
|
|
||||||
- "⚙️ Settings" кнопка показывается только если у plugin есть settingsPanel
|
|
||||||
- Клик → `verstak:open-settings` → открывает settings panel в modal
|
|
||||||
- Disable plugin → кнопка Settings исчезает
|
|
||||||
- Error boundary: `{#key}` + error state вокруг settings panel
|
|
||||||
|
|
||||||
### 5. Error Boundary
|
|
||||||
|
|
||||||
- ViewContainer: `{#key activeView}` + try/catch в reactive declarations → "⚠️ Plugin UI failed"
|
|
||||||
- PluginManager: `{#key}` вокруг settings modal + `settingsError` state
|
|
||||||
- Ошибки логируются в `console.error`
|
|
||||||
|
|
||||||
### 6. Platform-test Plugin
|
|
||||||
|
|
||||||
`verstak-official-plugins/plugins/platform-test/plugin.json` уже содержит все contribution points. Без изменений.
|
|
||||||
|
|
||||||
### 7. Enable/Disable Verification
|
|
||||||
|
|
||||||
| Сценарий | Результат |
|
|
||||||
|---|---|
|
|
||||||
| Plugin enabled → sidebar item visible | ✅ Sidebar items из ContributionRegistry |
|
|
||||||
| Plugin enabled → view opens | ✅ ViewContainer placeholder |
|
|
||||||
| Plugin enabled → settings button visible | ✅ Если есть settingsPanel |
|
|
||||||
| Plugin disabled → sidebar item disappears | ✅ `Unregister` → contributions удалены |
|
|
||||||
| Plugin disabled → view/settings unavailable | ✅ Не показываются |
|
|
||||||
| Plugin re-enabled → contributions return | ✅ При Reload — Register |
|
|
||||||
| ReloadPlugins no duplicates | ✅ Unregister перед Register + Register idempotent |
|
|
||||||
| Failed plugin → shell stable | ✅ Error boundary в ViewContainer + PluginManager |
|
|
||||||
|
|
||||||
### 8. Build Script
|
|
||||||
|
|
||||||
`scripts/build.sh` — добавлена функция `global_update()`:
|
|
||||||
- `git pull --ff-only` для всех 6 репозиториев
|
|
||||||
- Сборка official plugins (npm install + build для каждого plugin с frontend, go build для backend)
|
|
||||||
- Копирование собранных плагинов в `verstak-desktop/plugins/`
|
|
||||||
- Ошибки не фатальны — собираются и показываются в конце
|
|
||||||
|
|
||||||
## Изменённые файлы
|
|
||||||
|
|
||||||
### verstak-desktop
|
|
||||||
|
|
||||||
| Файл | Изменение |
|
|
||||||
|---|---|
|
|
||||||
| `internal/core/contribution/registry.go` | Добавлен `ListByPoint`, `ContributionPointType`, `Register` idempotent |
|
|
||||||
| `internal/core/contribution/registry_test.go` | **НОВЫЙ**: 5 тестов (register, unregister, ListByPoint, duplicate prevention, no-side-effects) |
|
|
||||||
| `internal/api/app.go` | Flat типы для фронтенда, `buildContributionSummary`, `ReloadPlugins` — Unregister перед Register |
|
|
||||||
| `frontend/src/lib/shell/Sidebar.svelte` | Sidebar items из ContributionRegistry, фильтрация, сортировка, error boundary |
|
|
||||||
| `frontend/src/lib/shell/ViewContainer.svelte` | **НОВЫЙ**: declarative placeholder host + error boundary |
|
|
||||||
| `frontend/src/lib/plugin-manager/PluginCard.svelte` | Settings button по settingsPanels prop, disabled state |
|
|
||||||
| `frontend/src/lib/plugin-manager/PluginManager.svelte` | Загрузка settingsPanels, settings modal, error boundary |
|
|
||||||
| `frontend/src/App.svelte` | Обработка `verstak:open-view`, `verstak:open-settings`, `verstak:close-settings` |
|
|
||||||
| `cmd/smoke-platform/main.go` | Добавлен `-test-contributions` флаг с тестом lifecycle |
|
|
||||||
| `scripts/smoke-platform.sh` | Добавлен вызов `-test-contributions` |
|
|
||||||
| `scripts/build.sh` | Добавлена `global_update()` — pull всех репозиториев, сборка official plugins |
|
|
||||||
| `docs/PLUGIN_RUNTIME.md` | Обновлён раздел Contribution Points + Reload |
|
|
||||||
|
|
||||||
### verstak-docs
|
|
||||||
| Файл | Изменение |
|
|
||||||
|---|---|
|
|
||||||
| `docs/MILESTONE_PLATFORM_RUNTIME_5a.md` | **НОВЫЙ**: этот документ |
|
|
||||||
|
|
||||||
## Результаты проверок
|
|
||||||
|
|
||||||
```
|
|
||||||
go test ./internal/... -count=1
|
|
||||||
→ all packages PASS (включая 5 новых contribution tests)
|
|
||||||
|
|
||||||
go vet ./...
|
|
||||||
→ clean
|
|
||||||
|
|
||||||
cd frontend && npm run build
|
|
||||||
→ ✓ built in 1.43s
|
|
||||||
|
|
||||||
bash scripts/smoke-platform.sh
|
|
||||||
→ smoke-platform passed (plugin + workspace + contributions lifecycle)
|
|
||||||
→ enable/disable test passed
|
|
||||||
→ workspace test passed
|
|
||||||
→ contributions lifecycle test passed
|
|
||||||
|
|
||||||
git status --short
|
|
||||||
→ clean (все 6 репозиториев)
|
|
||||||
|
|
||||||
git log HEAD --not --remotes
|
|
||||||
→ empty (все запушено)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Non-goals (не реализовано)
|
|
||||||
|
|
||||||
- Frontend bundle loader (plugin JS bundle host). ViewContainer — declarative placeholder
|
|
||||||
- Official plugin extraction (notes/files/editor/activity)
|
|
||||||
- Backend sidecar runtime
|
|
||||||
- Secrets
|
|
||||||
- Remote plugin registry
|
|
||||||
- Sync
|
|
||||||
- Command palette UI
|
|
||||||
- Пользовательские функции в core
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -1,496 +0,0 @@
|
|||||||
# Verstak2 UI/UX Audit - 2026-06-30
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This audit compares current Verstak2 against the old Verstak repository at
|
|
||||||
`/home/mirivlad/git/verstak`. The old repository is used only as UI/UX,
|
|
||||||
feature, and workflow reference. Code and architecture are not copied.
|
|
||||||
|
|
||||||
No previous complete UI/UX comparison report was found in the Verstak2
|
|
||||||
repositories. Existing documents cover implementation plans and roadmap items,
|
|
||||||
but not an end-to-end scenario audit.
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
- Date: 2026-06-30
|
|
||||||
- Verstak2 desktop repo: `/home/mirivlad/git/verstak2/verstak-desktop`
|
|
||||||
- Old Verstak repo: `/home/mirivlad/git/verstak`
|
|
||||||
- Tooling:
|
|
||||||
- Node.js v24.18.0 installed locally under `/tmp/verstak2-tools`
|
|
||||||
- Playwright Chromium used through local CLI wrapper
|
|
||||||
- Wails CLI v2.12.0 installed locally
|
|
||||||
- Debian 13 WebKitGTK package present as `libwebkit2gtk-4.1-dev`
|
|
||||||
- Wails note:
|
|
||||||
- `wails doctor` still reports missing `libwebkit2gtk-4.0-dev`.
|
|
||||||
- Wails v2.12.0 supports WebKitGTK 4.1 with Go build tag `webkit2_41`.
|
|
||||||
- `go test -tags webkit2_41 ./...` passes for current `verstak-desktop`.
|
|
||||||
|
|
||||||
## Artifacts
|
|
||||||
|
|
||||||
Screenshots are stored in:
|
|
||||||
|
|
||||||
`docs/ui-ux-audit-assets/2026-06-30/`
|
|
||||||
|
|
||||||
- `v1-start.png`
|
|
||||||
- `v1-today.png`
|
|
||||||
- `v1-inbox.png`
|
|
||||||
- `v1-activity.png`
|
|
||||||
- `v2-plugin-manager.png`
|
|
||||||
- `v2-workspace-files.png`
|
|
||||||
- `v2-command-palette.png`
|
|
||||||
- `v2-workspace-activity.png`
|
|
||||||
- `v2-workspace-browser-inbox.png`
|
|
||||||
|
|
||||||
## Verification Summary
|
|
||||||
|
|
||||||
### Repository Update
|
|
||||||
|
|
||||||
All Verstak2 subrepositories were updated. `verstak-desktop` initially could
|
|
||||||
not fast-forward because of local tracked changes in generated Wails typings
|
|
||||||
and `scripts/build.sh`. Those changes were saved in:
|
|
||||||
|
|
||||||
`stash@{0}: codex-pre-ui-audit-desktop-update`
|
|
||||||
|
|
||||||
After that, `verstak-desktop` fast-forwarded to `origin/main`.
|
|
||||||
|
|
||||||
### Automated Checks
|
|
||||||
|
|
||||||
Current `verstak-desktop`:
|
|
||||||
|
|
||||||
- `go test -tags webkit2_41 ./...`: pass
|
|
||||||
- `npm --prefix frontend run build`: pass
|
|
||||||
- `frontend npm run test:e2e`: 42 passed, 11 failed
|
|
||||||
- `scripts/check.sh` with `GOFLAGS=-tags=webkit2_41`: fails on gofmt only:
|
|
||||||
- `plugins/platform-test/backend/main.go`
|
|
||||||
- `build/bin/plugins/platform-test/backend/main.go`
|
|
||||||
|
|
||||||
### E2E Failures Worth Tracking
|
|
||||||
|
|
||||||
The failed E2E tests cluster around:
|
|
||||||
|
|
||||||
- Plugin Manager navigation selectors expecting `.sidebar .nav-item` while the
|
|
||||||
current sidebar uses different structure/classes.
|
|
||||||
- File/workbench editor height regressions:
|
|
||||||
- text editor height was about 266px where test expects over 300px;
|
|
||||||
- markdown preview height was about 217px where test expects over 300px.
|
|
||||||
- Platform Test status bar component failure:
|
|
||||||
- `openDiagnostics` component not found in bundle.
|
|
||||||
- Sync status bar failure:
|
|
||||||
- `SyncStatusBar` bundle content is empty.
|
|
||||||
|
|
||||||
Some selector failures may be stale test assumptions. The editor height and
|
|
||||||
status bar errors are visible workflow/UI issues.
|
|
||||||
|
|
||||||
### Post-Pull Status - 2026-06-30 Later Sync
|
|
||||||
|
|
||||||
After pulling `verstak-desktop` to `46f754c` (`feat: workspace routing,
|
|
||||||
GlobalSearch, and shell refinements`), several roadmap items from this audit
|
|
||||||
are now implemented and verified.
|
|
||||||
|
|
||||||
Current verification:
|
|
||||||
|
|
||||||
- `GOFLAGS=-tags=webkit2_41 go test ./...`: pass
|
|
||||||
- `npm --prefix frontend run test:e2e`: 71 passed
|
|
||||||
- Focused UX coverage now includes:
|
|
||||||
- app starts in the first workspace instead of Plugin Manager;
|
|
||||||
- workspace opens with a shell-level Today tab before plugin tools;
|
|
||||||
- workspace selection and main content stay in sync after Plugin Manager
|
|
||||||
round trips;
|
|
||||||
- workspace tool selection survives Workbench open/back/close flows;
|
|
||||||
- status bar plugin failures do not render large error panels;
|
|
||||||
- Plugin Manager remains reachable from the status/settings menu;
|
|
||||||
- Files uses readable dates and understandable labeled controls;
|
|
||||||
- Vault Selection is localized and has clear primary actions;
|
|
||||||
- global search stays visible after opening tool views;
|
|
||||||
- global search searches workspaces and file contents with RU/EN layout
|
|
||||||
fallback;
|
|
||||||
- plugin settings modal has enough space for complex panels;
|
|
||||||
- Browser Inbox desktop workflow renders an empty capture flow, stored
|
|
||||||
captures, detail metadata, and create/remove actions;
|
|
||||||
- Activity desktop workflow renders an empty event flow, stored events, and
|
|
||||||
worklog suggestions;
|
|
||||||
- Command Palette promotes shell workflow commands before diagnostics and can
|
|
||||||
open Today, Files, Activity, Browser Inbox, Plugin Manager, and start
|
|
||||||
markdown/text file creation;
|
|
||||||
- Command Palette can run Sync Now and open Sync settings.
|
|
||||||
|
|
||||||
The historical screenshots in `docs/ui-ux-audit-assets/2026-06-30/` remain
|
|
||||||
useful as "before" evidence, but the active roadmap below supersedes the
|
|
||||||
original P0 ordering.
|
|
||||||
|
|
||||||
## Scenario Findings
|
|
||||||
|
|
||||||
### 1. First Meaningful Screen
|
|
||||||
|
|
||||||
Old Verstak opens into a working shell:
|
|
||||||
|
|
||||||
- left navigation has clear system sections: Today, Inbox, Activity;
|
|
||||||
- workspace tree sits in the same navigation context;
|
|
||||||
- global search is immediately visible in the header;
|
|
||||||
- sync status and settings are discoverable but secondary.
|
|
||||||
|
|
||||||
Verstak2 opens into Plugin Manager:
|
|
||||||
|
|
||||||
- the first screen is technical/admin-oriented rather than user-task-oriented;
|
|
||||||
- plugin cards expose implementation details such as capabilities, roots,
|
|
||||||
permissions, API versions, and contribution counts;
|
|
||||||
- status bar plugin errors are visible immediately;
|
|
||||||
- workspace entry exists in the sidebar, but it is not the primary landing
|
|
||||||
experience.
|
|
||||||
|
|
||||||
Verdict: Verstak2 is feature-visible, but not user-workflow-first.
|
|
||||||
|
|
||||||
### 2. Navigation Model
|
|
||||||
|
|
||||||
Old Verstak has one coherent navigation model:
|
|
||||||
|
|
||||||
- System section for cross-workspace workflows;
|
|
||||||
- Workspace section for user cases/spaces;
|
|
||||||
- settings and sync anchored at the bottom;
|
|
||||||
- current page title and search are consistent.
|
|
||||||
|
|
||||||
Verstak2 separates:
|
|
||||||
|
|
||||||
- Tools;
|
|
||||||
- Workspaces;
|
|
||||||
- workspace tabs;
|
|
||||||
- Plugin Manager;
|
|
||||||
- status bar settings.
|
|
||||||
|
|
||||||
This is architecturally accurate for a platform, but it leaks platform concepts
|
|
||||||
too strongly into daily use. The user needs "work", "capture", "search",
|
|
||||||
"activity", and "settings"; they should not need to reason about plugin
|
|
||||||
contributions on the main path.
|
|
||||||
|
|
||||||
### 3. Workspace Files
|
|
||||||
|
|
||||||
Verstak2 workspace Files is one of the stronger current screens:
|
|
||||||
|
|
||||||
- workspace header exists;
|
|
||||||
- search slot exists;
|
|
||||||
- Files, Activity, Browser Inbox tabs are correctly scoped to workspace;
|
|
||||||
- file rows, folder rows, filtering, sorting, and action buttons are present.
|
|
||||||
|
|
||||||
Problems:
|
|
||||||
|
|
||||||
- Files toolbar buttons render visually as near-identical small squares in the
|
|
||||||
screenshot. Accessibility names exist, but visible affordance is poor.
|
|
||||||
- Editor/preview area appears too short in E2E, causing regressions for file
|
|
||||||
opening workflows.
|
|
||||||
- Status bar error area consumes a large part of the lower viewport and reduces
|
|
||||||
usable file space.
|
|
||||||
|
|
||||||
### 4. Activity
|
|
||||||
|
|
||||||
Old Verstak gives Activity a clear system-level place and empty state.
|
|
||||||
Today also exposes related workflow tabs: feed, suggestions, in-progress,
|
|
||||||
captures, browser.
|
|
||||||
|
|
||||||
Original Verstak2 desktop mock rendered only:
|
|
||||||
|
|
||||||
- heading `Activity`;
|
|
||||||
- text `Global activity feed`.
|
|
||||||
|
|
||||||
Current desktop shell/mock now mirrors the official Activity plugin's core
|
|
||||||
visible workflow: scoped empty state, event count, stored event rows, clear
|
|
||||||
action, and worklog suggestions. Remaining work is to expose real activity
|
|
||||||
ingestion and Journal import from the visible app flow, not just prove the
|
|
||||||
surface contract in mock E2E.
|
|
||||||
|
|
||||||
### 5. Browser Inbox / Capture Flow
|
|
||||||
|
|
||||||
Old Verstak "Неразобранное" has:
|
|
||||||
|
|
||||||
- a clear name for unprocessed captures;
|
|
||||||
- explanatory text;
|
|
||||||
- an immediate visible action: paste from clipboard.
|
|
||||||
|
|
||||||
Original Verstak2 desktop mock rendered only:
|
|
||||||
|
|
||||||
- heading `Browser Inbox`;
|
|
||||||
- text `Global browser inbox`.
|
|
||||||
|
|
||||||
Current desktop shell/mock now mirrors the official Browser Inbox plugin's core
|
|
||||||
visible workflow: scoped empty state, capture count, stored capture rows,
|
|
||||||
detail metadata, and visible conversion/remove actions. Remaining work is to
|
|
||||||
make the real capture ingestion path and extension pairing visible from the
|
|
||||||
app.
|
|
||||||
|
|
||||||
### 6. Command Palette
|
|
||||||
|
|
||||||
Verstak2 command palette opens and is keyboard-accessible. Current visible
|
|
||||||
commands are only Platform Test commands. It is useful infrastructure, but not
|
|
||||||
yet a meaningful user launcher.
|
|
||||||
|
|
||||||
Expected near-term commands:
|
|
||||||
|
|
||||||
- open/create workspace;
|
|
||||||
- search workspace;
|
|
||||||
- create note;
|
|
||||||
- create file;
|
|
||||||
- open Browser Inbox;
|
|
||||||
- open Activity;
|
|
||||||
- sync now / sync settings;
|
|
||||||
- plugin/settings only after user tasks.
|
|
||||||
|
|
||||||
### 7. Status Bar
|
|
||||||
|
|
||||||
Status bar is currently the largest immediate UX blocker.
|
|
||||||
|
|
||||||
Observed:
|
|
||||||
|
|
||||||
- permanent lower panel shows plugin errors;
|
|
||||||
- error cards overlap/consume workspace area;
|
|
||||||
- messages expose internal plugin/component/bundle details;
|
|
||||||
- normal user status is visually dominated by red plugin failure content.
|
|
||||||
|
|
||||||
The old version keeps sync/settings status quiet and secondary. Verstak2 should
|
|
||||||
follow that principle: status should be compact, actionable, and non-blocking.
|
|
||||||
Developer diagnostics belong behind an explicit diagnostics surface.
|
|
||||||
|
|
||||||
## Priority Plan
|
|
||||||
|
|
||||||
### DONE - Remove User-Facing Internal Errors From Main Workspace
|
|
||||||
|
|
||||||
Goal: no normal user screen should be dominated by plugin bundle errors.
|
|
||||||
|
|
||||||
Status: implemented in `verstak-desktop` and verified by
|
|
||||||
`frontend/e2e/ux-p0.spec.js` plus `frontend/e2e/status-bar.spec.js`.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Fix Platform Test status contribution:
|
|
||||||
- manifest/component mismatch around `openDiagnostics` vs available bundle
|
|
||||||
components.
|
|
||||||
- Fix Sync status contribution:
|
|
||||||
- empty frontend bundle content for `SyncStatusBar`.
|
|
||||||
- Change status bar error rendering:
|
|
||||||
- compact "Plugin issue" badge;
|
|
||||||
- details only on click;
|
|
||||||
- cap height so it cannot cover workspace content.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- `frontend/e2e/status-bar.spec.js` passes.
|
|
||||||
- First workspace screenshot has no large red error cards.
|
|
||||||
|
|
||||||
### DONE - Make Workspace The Default User Landing
|
|
||||||
|
|
||||||
Goal: after vault opens, user lands in a productive workspace or Today-style
|
|
||||||
overview, not Plugin Manager.
|
|
||||||
|
|
||||||
Status: implemented in `verstak-desktop`; current mock flow starts in the first
|
|
||||||
workspace and keeps Plugin Manager reachable through settings.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- If a vault has workspaces, open the last active workspace.
|
|
||||||
- If no workspace exists, show a first-run workspace creation screen.
|
|
||||||
- Move Plugin Manager out of the primary default route:
|
|
||||||
- keep accessible through Settings;
|
|
||||||
- optionally expose via command palette.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- Initial app screen shows workspace or useful overview.
|
|
||||||
- Plugin Manager remains reachable but is not the first user-facing surface.
|
|
||||||
|
|
||||||
### PARTIAL - Restore Old "Today / Inbox / Activity" Workflow Shape
|
|
||||||
|
|
||||||
Goal: preserve the old product's user workflow while keeping Verstak2 plugin
|
|
||||||
architecture.
|
|
||||||
|
|
||||||
Status: first shell-level Today surface is implemented in `verstak-desktop`.
|
|
||||||
Workspace now opens with `Today` before plugin tools, showing captures, recent
|
|
||||||
activity, worklog suggestions, and quick actions. The Today "Open Inbox" action
|
|
||||||
switches into the workspace Browser Inbox tool, and Files remains stable after
|
|
||||||
Workbench open/back/close flows.
|
|
||||||
|
|
||||||
Remaining gap: this is still a thin aggregation layer. Activity, Browser Inbox,
|
|
||||||
and Journal exist as plugin-backed surfaces, but the old product's richer
|
|
||||||
"what should I do now?" flow needs real capture/activity/journal data density
|
|
||||||
and stronger empty-state actions. Current architecture has the ingredients:
|
|
||||||
|
|
||||||
- `verstak.activity` stores scoped activity and exposes worklog suggestion
|
|
||||||
command `verstak.activity.suggestWorklog`;
|
|
||||||
- `verstak.browser-inbox` stores scoped captures and can convert captures into
|
|
||||||
notes, links, and files;
|
|
||||||
- `verstak.journal` stores worklog entries and can import Activity suggestions;
|
|
||||||
- `GlobalSearch` indexes workspaces, tools, files, Browser Inbox, Activity, and
|
|
||||||
Journal plugin settings.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Expand the user-level `Today` surface with richer real data.
|
|
||||||
- Surface and prioritize:
|
|
||||||
- recent activity;
|
|
||||||
- unprocessed captures;
|
|
||||||
- in-progress work;
|
|
||||||
- worklog suggestions;
|
|
||||||
- quick create actions.
|
|
||||||
- Keep implementation plugin-driven, but present as one coherent workflow.
|
|
||||||
- Add first-class commands for opening Today, Inbox, Activity, and creating
|
|
||||||
work items.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- User can answer "what should I do now?" from the first screen.
|
|
||||||
- Browser captures and activity are visible without opening Plugin Manager.
|
|
||||||
- `frontend/e2e/ux-today.spec.js` covers Today-first startup and the Today to
|
|
||||||
Browser Inbox quick-action path.
|
|
||||||
|
|
||||||
### PARTIAL - Make Activity A Real Visible Workflow
|
|
||||||
|
|
||||||
Goal: Activity should look useful even with no events and should connect events
|
|
||||||
to worklog reconstruction.
|
|
||||||
|
|
||||||
Status: partially implemented. The real Activity plugin has scoped storage,
|
|
||||||
event subscriptions, worklog suggestion generation, and a command contribution
|
|
||||||
for `verstak.activity.suggestWorklog`. The desktop E2E/mock surface now mirrors
|
|
||||||
the core visible workflow: scoped empty state, event count, stored event rows,
|
|
||||||
clear action, and worklog suggestions.
|
|
||||||
|
|
||||||
Remaining UX work is mostly around real activity ingestion from app actions,
|
|
||||||
Journal import/review, and making suggestions actionable from Today/Activity
|
|
||||||
rather than merely visible.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Keep empty-state copy explaining that file changes, browser captures, and
|
|
||||||
conversions will appear here.
|
|
||||||
- Add visible actions for:
|
|
||||||
- import worklog suggestion to Journal;
|
|
||||||
- filter by source/type;
|
|
||||||
- open related file/capture from an activity row.
|
|
||||||
- Connect Today worklog suggestions to Activity/Journal actions.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- Empty Activity explains what will appear.
|
|
||||||
- Stored events render with source/type/summary.
|
|
||||||
- Worklog suggestions are visible from activity events.
|
|
||||||
- `frontend/e2e/activity.spec.js` covers the desktop shell/mock contract.
|
|
||||||
|
|
||||||
### PARTIAL - Make Browser Inbox A Real Visible Workflow
|
|
||||||
|
|
||||||
Goal: Browser Inbox should look usable even when empty.
|
|
||||||
|
|
||||||
Status: partially implemented. The real Browser Inbox plugin has storage,
|
|
||||||
event subscriptions, scoped capture lists, domain binding, and Create Note /
|
|
||||||
Create Link / Create File actions for selected captures. The desktop E2E/mock
|
|
||||||
surface now mirrors the core visible workflow: scoped empty state, capture
|
|
||||||
count, stored capture list, detail metadata, and Create Note / Create Link /
|
|
||||||
Create File / Remove actions. Today also opens this workspace tool directly.
|
|
||||||
|
|
||||||
Remaining UX work is mostly around actual primary capture actions, extension
|
|
||||||
pairing/settings visibility, and making the real capture ingestion path easy to
|
|
||||||
exercise from the app.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Keep empty-state copy explaining capture flow.
|
|
||||||
- Add primary actions:
|
|
||||||
- paste from clipboard;
|
|
||||||
- open browser extension pairing/settings;
|
|
||||||
- create note/link/file from selected capture when data exists.
|
|
||||||
- Show domain/workspace binding state if configured.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- Empty Browser Inbox explains the capture flow.
|
|
||||||
- Stored captures render with visible metadata and conversion actions.
|
|
||||||
- Captures can be converted from the visible UI in the real plugin.
|
|
||||||
- `frontend/e2e/browser-inbox.spec.js` covers the desktop shell/mock contract.
|
|
||||||
|
|
||||||
### DONE - Fix Files Toolbar Visual Affordance
|
|
||||||
|
|
||||||
Goal: toolbar buttons must be visually recognizable, not identical dark boxes.
|
|
||||||
|
|
||||||
Status: implemented and verified by `frontend/e2e/ux-p0.spec.js` and the full
|
|
||||||
Files E2E suite. Files now uses readable dates and understandable action
|
|
||||||
controls; editor/preview height regressions are covered by passing E2E.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Ensure Lucide icons render with sufficient contrast and size.
|
|
||||||
- Add tooltips for icon-only buttons.
|
|
||||||
- Disable unavailable actions visibly instead of showing identical active
|
|
||||||
squares.
|
|
||||||
- Keep layout dense, but make grouping clear: navigation, create, selected item
|
|
||||||
actions, clipboard, filter/sort.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- Screenshot shows recognizable icons.
|
|
||||||
- Buttons have accessible names and hover tooltips.
|
|
||||||
- File open editor/preview height returns above E2E threshold.
|
|
||||||
|
|
||||||
### PARTIAL - Make Command Palette User-Oriented
|
|
||||||
|
|
||||||
Goal: command palette should be a power-user entry point for real tasks.
|
|
||||||
|
|
||||||
Status: partial. Command Palette works, executes active plugin commands, and
|
|
||||||
now promotes shell workflow commands above diagnostics: Open Today, Open Files,
|
|
||||||
Open Activity, Open Browser Inbox, Open Plugin Manager, Create Markdown File,
|
|
||||||
Create Text File, Sync Now, and Open Sync Settings. Remaining command coverage
|
|
||||||
gaps are capture actions, richer note templates, Journal import/review, and
|
|
||||||
hiding test diagnostics from production-like mode.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Register remaining user commands from Browser Inbox, Activity, Search, and
|
|
||||||
Journal.
|
|
||||||
- Keep user commands ranked above diagnostics/test commands.
|
|
||||||
- Hide test/plugin diagnostics from production-like mode.
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- `Ctrl+K` shows open Today/Files/Activity/Inbox commands before diagnostics.
|
|
||||||
- `Ctrl+K` can start markdown/text file creation through the visible Files
|
|
||||||
workflow.
|
|
||||||
- `Ctrl+K` can run Sync Now and open Sync settings.
|
|
||||||
- `frontend/e2e/command-palette.spec.js` covers shell command ranking and the
|
|
||||||
Open Activity, create-file, and sync command paths.
|
|
||||||
|
|
||||||
### DONE - Reduce Plugin Manager Density
|
|
||||||
|
|
||||||
Goal: Plugin Manager should remain useful but stop looking like the product's
|
|
||||||
main screen.
|
|
||||||
|
|
||||||
Status: implemented enough for the current roadmap. Plugin cards now show a
|
|
||||||
short default surface and hide API version, root, capabilities, optional
|
|
||||||
requires, and permissions under `Technical details`.
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
|
|
||||||
- Split default view into simple plugin list:
|
|
||||||
- name;
|
|
||||||
- enabled/disabled;
|
|
||||||
- short description;
|
|
||||||
- settings;
|
|
||||||
- disable/enable.
|
|
||||||
- Put capabilities, permissions, roots, and contribution details behind
|
|
||||||
expandable "Developer details".
|
|
||||||
|
|
||||||
Verification:
|
|
||||||
|
|
||||||
- Plugin Manager fits more plugins per viewport.
|
|
||||||
- Non-developer user can safely enable/disable/configure without reading
|
|
||||||
internal contracts.
|
|
||||||
|
|
||||||
## Next Review Steps
|
|
||||||
|
|
||||||
1. Design and implement the next Today follow-up: make captures, Activity
|
|
||||||
worklog suggestions, and Journal import/review actionable from the first
|
|
||||||
workspace screen.
|
|
||||||
2. Re-run:
|
|
||||||
- `GOFLAGS=-tags=webkit2_41 ./scripts/check.sh`
|
|
||||||
- `npm --prefix frontend run test:e2e`
|
|
||||||
3. Re-capture the same screenshots as "after" evidence.
|
|
||||||
4. Run real Wails GUI smoke with:
|
|
||||||
- `wails dev -tags webkit2_41`
|
|
||||||
5. Do a second audit focused on:
|
|
||||||
- first-run/open vault;
|
|
||||||
- real filesystem vault;
|
|
||||||
- Browser extension capture;
|
|
||||||
- native window/WebKitGTK rendering differences.
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
# Sync Server & Plugin Design
|
|
||||||
|
|
||||||
## [S1] Problem
|
|
||||||
|
|
||||||
Verstak2 needs synchronization between devices. The old Verstak (`~/git/verstak`) has a working sync server and client, but Verstak2 is a platform architecture with plugins. Sync must be implemented as:
|
|
||||||
1. A separate sync server repository
|
|
||||||
2. A sync plugin that provides settings UI via the plugin manager
|
|
||||||
|
|
||||||
## [S2] Sync Server
|
|
||||||
|
|
||||||
### Location
|
|
||||||
Separate repository: `verstak-sync-server`
|
|
||||||
|
|
||||||
### Source
|
|
||||||
Based on `~/git/verstak/cmd/verstak-server/` with minimal changes.
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
```
|
|
||||||
verstak-sync-server/
|
|
||||||
cmd/server/main.go
|
|
||||||
internal/
|
|
||||||
server/
|
|
||||||
server.go
|
|
||||||
routes.go
|
|
||||||
handlers_api.go
|
|
||||||
handlers_auth.go
|
|
||||||
handlers_admin.go
|
|
||||||
middleware.go
|
|
||||||
config.go
|
|
||||||
schema.go
|
|
||||||
tokens.go
|
|
||||||
smtp.go
|
|
||||||
go.mod
|
|
||||||
go.sum
|
|
||||||
README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Endpoints
|
|
||||||
- `POST /api/v1/sync/push` — push operations
|
|
||||||
- `POST /api/v1/sync/pull` — pull operations
|
|
||||||
- `POST /api/v1/blobs/` — upload blob (multipart)
|
|
||||||
- `GET /api/v1/blobs/:sha256` — download blob
|
|
||||||
- `POST /api/client/pair` — device pairing with login/password
|
|
||||||
- `POST /api/auth/test` — test credentials
|
|
||||||
- `GET /api/client/me` — device info
|
|
||||||
- `POST /api/client/revoke-current` — revoke current device
|
|
||||||
- `POST /api/client/revoke-device` — revoke specific device (web)
|
|
||||||
- `GET /api/v1/health` — health check
|
|
||||||
- Admin endpoints: `/admin/dashboard`, `/admin/users`, `/admin/api/stats`
|
|
||||||
|
|
||||||
### Config
|
|
||||||
YAML file at `data/config.yml`:
|
|
||||||
```yaml
|
|
||||||
port: 47732
|
|
||||||
admin:
|
|
||||||
- username: admin
|
|
||||||
password_hash: "$2a$10$..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database
|
|
||||||
SQLite at `data/server.db` with tables:
|
|
||||||
- `server_users` — user accounts
|
|
||||||
- `server_devices` — paired devices
|
|
||||||
- `server_ops` — sync operations
|
|
||||||
- `server_tombstones` — deleted entities
|
|
||||||
- `server_idempotency_keys` — idempotent push
|
|
||||||
- `server_user_devices` — user-device mapping
|
|
||||||
- `server_audit_log` — audit trail
|
|
||||||
|
|
||||||
## [S3] Sync Plugin
|
|
||||||
|
|
||||||
### Location
|
|
||||||
`verstak-official-plugins/plugins/sync/`
|
|
||||||
|
|
||||||
### Manifest
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"id": "verstak.sync",
|
|
||||||
"name": "Sync",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"apiVersion": "0.1.0",
|
|
||||||
"description": "Vault synchronization across devices via Verstak Sync Server.",
|
|
||||||
"source": "official",
|
|
||||||
"icon": "sync",
|
|
||||||
"provides": ["verstak/sync/v1", "verstak/sync.status/v1"],
|
|
||||||
"requires": ["verstak/core/files/v1"],
|
|
||||||
"permissions": [
|
|
||||||
"files.read",
|
|
||||||
"files.write",
|
|
||||||
"network.remote",
|
|
||||||
"settings.read",
|
|
||||||
"settings.write",
|
|
||||||
"ui.register"
|
|
||||||
],
|
|
||||||
"frontend": {
|
|
||||||
"entry": "frontend/dist/index.js"
|
|
||||||
},
|
|
||||||
"contributes": {
|
|
||||||
"settingsPanels": [{
|
|
||||||
"id": "verstak.sync.settings",
|
|
||||||
"title": "Sync",
|
|
||||||
"component": "SyncSettings"
|
|
||||||
}],
|
|
||||||
"statusBarItems": [{
|
|
||||||
"id": "verstak.sync.status",
|
|
||||||
"label": "Sync",
|
|
||||||
"position": "right"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Capabilities Provided
|
|
||||||
- `verstak/sync/v1` — sync provider capability
|
|
||||||
- `verstak/sync.status/v1` — sync status provider
|
|
||||||
|
|
||||||
### Settings (Extended)
|
|
||||||
The plugin stores settings via `settings.read/write` API:
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
|-----|------|---------|-------------|
|
|
||||||
| `serverUrl` | string | "" | Sync server URL |
|
|
||||||
| `username` | string | "" | User login |
|
|
||||||
| `password` | string | "" | User password (stored temporarily, not persisted) |
|
|
||||||
| `syncInterval` | number | 0 | Auto-sync interval in minutes (0 = disabled) |
|
|
||||||
| `autoSync` | boolean | false | Enable auto-sync |
|
|
||||||
| `deviceName` | string | hostname | Device identifier |
|
|
||||||
| `deviceId` | string | "" | Assigned device ID |
|
|
||||||
| `deviceToken` | string | "" | Device auth token |
|
|
||||||
| `lastStatus` | string | "disabled" | Connection status |
|
|
||||||
| `lastSyncAt` | string | "" | Last successful sync timestamp |
|
|
||||||
| `lastError` | string | "" | Last error message |
|
|
||||||
|
|
||||||
### Settings UI Component
|
|
||||||
|
|
||||||
`SyncSettings.svelte` — registered as settings panel via contributes.
|
|
||||||
|
|
||||||
**Two states:**
|
|
||||||
|
|
||||||
1. **Not configured** — setup form:
|
|
||||||
- Server URL input
|
|
||||||
- Username input
|
|
||||||
- Password input
|
|
||||||
- "Test Connection" button
|
|
||||||
- "Connect" button
|
|
||||||
|
|
||||||
2. **Configured** — status view:
|
|
||||||
- Status indicator (connected/disconnected/error/revoked)
|
|
||||||
- Server URL display
|
|
||||||
- Device Name display
|
|
||||||
- Device ID display
|
|
||||||
- Last Sync Time display
|
|
||||||
- Last Error display (if any)
|
|
||||||
- "Sync Now" button
|
|
||||||
- "Disconnect" button
|
|
||||||
- "Reset Key" button
|
|
||||||
- Sync Interval input + Save button
|
|
||||||
- Auto-sync toggle
|
|
||||||
|
|
||||||
### Backend Integration
|
|
||||||
|
|
||||||
The plugin uses VerstakPluginAPI to:
|
|
||||||
- Read/write settings via `settings.read/write`
|
|
||||||
- Call backend methods for sync operations
|
|
||||||
|
|
||||||
Backend methods needed in `verstak-desktop`:
|
|
||||||
- `SyncStatus()` — get current sync status
|
|
||||||
- `SyncConfigure(serverURL, username, password)` — pair device
|
|
||||||
- `SyncDisconnect()` — disconnect and revoke
|
|
||||||
- `SyncTestConnection(serverURL, username, password)` — test credentials
|
|
||||||
- `SyncSetInterval(minutes)` — set auto-sync interval
|
|
||||||
- `SyncNow()` — trigger immediate sync
|
|
||||||
- `ResetSyncKey()` — clear device token
|
|
||||||
|
|
||||||
### Status Bar Item
|
|
||||||
|
|
||||||
Shows sync status in the bottom-right status bar:
|
|
||||||
- Icon: sync icon
|
|
||||||
- Label: status text (Connected/Disconnected/Error)
|
|
||||||
- Click: opens plugin settings
|
|
||||||
|
|
||||||
## [S4] Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ Verstak Desktop │
|
|
||||||
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
|
|
||||||
│ │ Plugin Manager│───▶│ Sync Plugin │───▶│ Settings │ │
|
|
||||||
│ │ (Settings) │ │ (UI Panel) │ │ API │ │
|
|
||||||
│ └──────────────┘ └──────┬───────┘ └───────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌──────────────────────────▼──────────────────────────┐│
|
|
||||||
│ │ VerstakPluginAPI ││
|
|
||||||
│ │ settings.read/write → files.read/write ││
|
|
||||||
│ └──────────────────────────┬──────────────────────────┘│
|
|
||||||
│ │ │
|
|
||||||
│ ┌──────────────────────────▼──────────────────────────┐│
|
|
||||||
│ │ Core Platform (Go Backend) ││
|
|
||||||
│ │ SyncService: RecordOp, GetUnpushedOps, MarkPushed ││
|
|
||||||
│ │ SyncClient: Push, Pull, UploadBlob, DownloadBlob ││
|
|
||||||
│ └──────────────────────────┬──────────────────────────┘│
|
|
||||||
└─────────────────────────────┼───────────────────────────┘
|
|
||||||
│ HTTP
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ Verstak Sync Server │
|
|
||||||
│ SQLite: users, devices, ops, tombstones │
|
|
||||||
│ Blobs: SHA-256 content-addressed storage │
|
|
||||||
│ API: push/pull, pair, auth, admin │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## [S5] Implementation Order
|
|
||||||
|
|
||||||
1. **Sync Server** — copy and adapt from `~/git/verstak/cmd/verstak-server/`
|
|
||||||
2. **Backend API** — add sync methods to `verstak-desktop/internal/api/app.go`
|
|
||||||
3. **Sync Plugin** — create plugin structure with manifest and frontend
|
|
||||||
4. **Settings UI** — implement `SyncSettings.svelte` component
|
|
||||||
5. **Status Bar** — add sync status indicator
|
|
||||||
6. **Testing** — E2E tests for settings UI, integration tests for sync
|
|
||||||
|
|
||||||
## [S6] Testing Strategy
|
|
||||||
|
|
||||||
- **Unit tests**: Sync client (push/pull), settings read/write
|
|
||||||
- **E2E tests**: Settings UI (configure, disconnect, sync now)
|
|
||||||
- **Integration tests**: Full sync cycle (two devices, push/pull)
|
|
||||||
- **Manual verification**: Real sync server with two vault instances
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
# Activity Worklog Suggestions Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add a minimal Activity reconstruction layer that renders and exposes worklog suggestions from recorded activity events.
|
|
||||||
|
|
||||||
**Architecture:** Keep reconstruction inside `verstak.activity`. Generate suggestions from normalized stored events, render them in the Activity view, and expose them through a command-backed runtime contract for the future Journal plugin.
|
|
||||||
|
|
||||||
**Tech Stack:** Official plugin browserless JavaScript bundle, Node smoke tests, JSON plugin manifest, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not move activity or journal logic into desktop core.
|
|
||||||
- Do not build the Journal plugin in this slice.
|
|
||||||
- Suggestions are derived from existing Activity events and are informational.
|
|
||||||
- Use TDD: update the activity smoke test first, run it red, then implement.
|
|
||||||
- Commit and push each affected repository after meaningful changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Document The Slice
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-docs/docs/superpowers/specs/2026-06-29-activity-worklog-suggestions-design.md`
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-docs/docs/superpowers/plans/2026-06-29-activity-worklog-suggestions.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces: written contract for `verstak.activity.suggestWorklog`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write spec and plan**
|
|
||||||
|
|
||||||
Write the design and this implementation plan.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: exits 0.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit and push docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git add docs/superpowers/specs/2026-06-29-activity-worklog-suggestions-design.md docs/superpowers/plans/2026-06-29-activity-worklog-suggestions.md
|
|
||||||
git commit -m "docs: plan activity worklog suggestions"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: docs `main` is clean and pushed.
|
|
||||||
|
|
||||||
### Task 2: Activity Suggestions Runtime
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/plugin.json`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/frontend/src/index.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-activity-plugin.js`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces command: `verstak.activity.suggestWorklog(args?: { workspaceRootPath?: string }): Promise<{ suggestions: WorklogSuggestion[] }>`
|
|
||||||
- Produces DOM: `data-activity-section="worklog-suggestions"` and `data-worklog-suggestion="<suggestionId>"`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing smoke assertions**
|
|
||||||
|
|
||||||
Extend `scripts/smoke-activity-plugin.js` to read the Activity manifest, mock
|
|
||||||
`api.commands.register`, and assert that:
|
|
||||||
|
|
||||||
- `commands.register` permission exists;
|
|
||||||
- command contribution `verstak.activity.suggestWorklog` exists;
|
|
||||||
- the command is registered after mount;
|
|
||||||
- Project events render a suggestion;
|
|
||||||
- executing the command returns a suggestion with `minutes`, `summary`, and
|
|
||||||
source `eventIds`;
|
|
||||||
- global Activity renders separate Project and ClientA suggestions;
|
|
||||||
- clear removes the rendered suggestion.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
node scripts/smoke-activity-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because `verstak.activity.suggestWorklog` is not registered.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Update manifest**
|
|
||||||
|
|
||||||
Add `commands.register` to permissions and add:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "verstak.activity.suggestWorklog",
|
|
||||||
"title": "Suggest Worklog From Activity",
|
|
||||||
"handler": "verstak.activity.suggestWorklog"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
under `contributes.commands`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Implement suggestions**
|
|
||||||
|
|
||||||
In `plugins/activity/frontend/src/index.js`:
|
|
||||||
|
|
||||||
- define `WORKLOG_COMMAND_ID = 'verstak.activity.suggestWorklog'`;
|
|
||||||
- add `suggestions` state;
|
|
||||||
- derive suggestions from normalized events grouped by workspace/day;
|
|
||||||
- render a suggestions band above the activity list;
|
|
||||||
- register the command with `api.commands.register`;
|
|
||||||
- recompute suggestions after load, event refresh, and clear.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
node scripts/smoke-activity-plugin.js
|
|
||||||
./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
- [ ] **Step 6: Commit and push official plugin**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
git add plugins/activity/plugin.json plugins/activity/frontend/src/index.js scripts/smoke-activity-plugin.js
|
|
||||||
git commit -m "feat: suggest worklogs from activity"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: official plugins `main` is clean and pushed.
|
|
||||||
|
|
||||||
### Task 3: Mark Roadmap Progress
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified official plugin commit.
|
|
||||||
- Produces docs matching implemented behavior.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update Activity status**
|
|
||||||
|
|
||||||
Replace the sentence saying reconstruction/worklog suggestions are future work
|
|
||||||
with a sentence describing the implemented suggestion band and command.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update roadmap**
|
|
||||||
|
|
||||||
Change:
|
|
||||||
|
|
||||||
```md
|
|
||||||
- implement activity reconstruction and worklog suggestions;
|
|
||||||
```
|
|
||||||
|
|
||||||
to:
|
|
||||||
|
|
||||||
```md
|
|
||||||
- [x] implement activity reconstruction and worklog suggestions;
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "Reconstruction and worklog suggestions are still future work|implement activity reconstruction and worklog suggestions" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `git diff --check` exits 0. `rg` shows only the checked roadmap line.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit and push docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git add 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
git commit -m "docs: mark activity worklog suggestions complete"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: docs `main` is clean and pushed.
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
# Binary File Write And Browser Attachments Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add bounded binary file writes to the public Files API and use them for Browser Inbox binary attachment conversion.
|
|
||||||
|
|
||||||
**Architecture:** Desktop owns the safe byte-write primitive and sync payload compatibility. SDK and frontend bridge expose the method as public plugin API. Browser extension sends base64 for selected files, receiver republishes it, and Browser Inbox converts through `api.files.writeBytes`.
|
|
||||||
|
|
||||||
**Tech Stack:** Go Files service and Wails bridge, plain JavaScript plugin host and WebExtension code, TypeScript SDK types/tests, official plugin smoke harness, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not add chunked streaming in this slice.
|
|
||||||
- Do not allow plugins to write outside vault-relative path policy.
|
|
||||||
- Keep the write limit at 8 MB to match existing `readBytes`.
|
|
||||||
- Keep sync backward compatible with existing text `content` payloads.
|
|
||||||
- Use TDD for each behavior change.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Desktop Files API `writeBytes`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/files/service.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/files/service_test.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/api/app.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/api/app_test.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/src/lib/plugin-host/VerstakPluginAPI.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/tests/plugin-api-files-test.mjs`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/wailsjs/go/api/App.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/wailsjs/go/api/App.d.ts`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces `api.files.writeBytes(relativePath, dataBase64, options)`.
|
|
||||||
- Produces backend `WriteVaultFileBytes(pluginID, relativePath, dataBase64, options)`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED service/API/bridge tests**
|
|
||||||
|
|
||||||
Add tests for successful byte write, invalid base64 rejection, oversized payload
|
|
||||||
rejection, permission enforcement, sync `dataBase64` payload, remote binary
|
|
||||||
apply, and frontend bridge exposure.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
|
||||||
go test ./internal/core/files -run TestWriteVaultFileBytesAtomicAndConflictBehavior -count=1
|
|
||||||
go test ./internal/api -run 'TestFilesBridgeReadWriteListMoveTrash|TestFilesBridgePermissions|TestApplyRemoteFileOps|TestFileBridgeRecordsSyncOps' -count=1
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node frontend/tests/plugin-api-files-test.mjs
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failures because `writeBytes` does not exist.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement byte write**
|
|
||||||
|
|
||||||
Add atomic bounded base64 decode/write in the Files service, Wails bridge method,
|
|
||||||
plugin host method, generated Wails stubs, sync payload `DataBase64`, and remote
|
|
||||||
apply support.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/core/files
|
|
||||||
go test ./internal/api
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node frontend/tests/plugin-api-files-test.mjs
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/test.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: all commands exit 0.
|
|
||||||
|
|
||||||
### Task 2: SDK `writeBytes` Contract
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-sdk/src/plugin-api.ts`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-sdk/src/test-utils.ts`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-sdk/src/plugin-api.test.ts`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces SDK `files.writeBytes(relativePath, dataBase64, options?)`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED SDK test**
|
|
||||||
|
|
||||||
Assert `createMockPluginAPI().files.writeBytes` writes base64 content that can
|
|
||||||
be read back through `readBytes`.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-sdk
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failure because `writeBytes` is missing.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement SDK types and mock**
|
|
||||||
|
|
||||||
Add the method to `VerstakPluginAPI` and mock implementation.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: SDK tests pass.
|
|
||||||
|
|
||||||
### Task 3: Browser Extension Binary Capture
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/protocol.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/background.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-protocol.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/README.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces file captures with `file.dataBase64` for selected files up to 8 MB.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED protocol test**
|
|
||||||
|
|
||||||
Add a binary file capture assertion and validate that missing both `file.text`
|
|
||||||
and `file.dataBase64` is rejected.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failure because `dataBase64` is not preserved.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement base64 file capture**
|
|
||||||
|
|
||||||
Read selected files as `ArrayBuffer`, base64 encode them, keep text only when
|
|
||||||
`file.text()` succeeds for text-like files, and enforce the 8 MB limit.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: extension tests and build pass.
|
|
||||||
|
|
||||||
### Task 4: Receiver And Browser Inbox Binary Conversion
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/browserreceiver/receiver.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/browserreceiver/receiver_test.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes `file.dataBase64`.
|
|
||||||
- Produces Browser Inbox conversion through `api.files.writeBytes`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED tests**
|
|
||||||
|
|
||||||
Receiver test asserts `fileDataBase64`; Browser Inbox smoke asserts `Create File`
|
|
||||||
uses `writeBytes` for binary captures.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
|
||||||
go test ./internal/core/browserreceiver -run TestReceiverAcceptsFileCaptureAndPublishesEvent -count=1
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failures for missing `fileDataBase64` and missing `writeBytes` path.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement receiver and plugin conversion**
|
|
||||||
|
|
||||||
Add `DataBase64` to receiver file payload, preserve it in Browser Inbox storage,
|
|
||||||
and call `api.files.writeBytes` when present.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
|
||||||
go test ./internal/core/browserreceiver
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: receiver and official plugin checks pass.
|
|
||||||
|
|
||||||
### Task 5: Documentation And Final Verification
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/docs/PLUGIN_RUNTIME.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Documents public `files.writeBytes` and binary Browser Inbox completion.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update docs**
|
|
||||||
|
|
||||||
Document `writeBytes`, base64/8 MB limits, sync compatibility, and mark binary
|
|
||||||
attachment conversion complete.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run full verification**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop-current && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/test.sh
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-sdk && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm run build
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins && PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs && git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: all commands exit 0.
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# Browser Inbox Conversion Activity Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Record Browser Inbox note conversions in Activity through the public `browser.capture.converted` event.
|
|
||||||
|
|
||||||
**Architecture:** Keep plugins decoupled. Browser Inbox already publishes `browser.capture.converted`; Activity subscribes to that event through its normal frontend event list and manifest `activityProviders` contribution.
|
|
||||||
|
|
||||||
**Tech Stack:** Plain JavaScript official plugins, Node smoke test harness, plugin manifest, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not add a direct Browser Inbox to Activity API.
|
|
||||||
- Do not move conversion recording into desktop core.
|
|
||||||
- Use only public event subscription behavior.
|
|
||||||
- Use TDD: write the failing Activity smoke test first, run it red, then implement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Activity Conversion Event Recording
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-activity-plugin.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/frontend/src/index.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/plugin.json`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes: `browser.capture.converted` event payload from Browser Inbox.
|
|
||||||
- Produces: stored Activity entry using the existing activity event model.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing smoke test**
|
|
||||||
|
|
||||||
In `scripts/smoke-activity-plugin.js`, add `browser.capture.converted` to the
|
|
||||||
required subscription checks and dispatch a workspace-scoped conversion event.
|
|
||||||
Assert it is stored, rendered, and included in worklog suggestion event ids.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-activity-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because Activity does not subscribe to
|
|
||||||
`browser.capture.converted`.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement event subscription**
|
|
||||||
|
|
||||||
Add `browser.capture.converted` to `ACTIVITY_EVENTS` in
|
|
||||||
`plugins/activity/frontend/src/index.js` and to
|
|
||||||
`contributes.activityProviders[0].events` in `plugins/activity/plugin.json`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-activity-plugin.js
|
|
||||||
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
### Task 2: Roadmap Documentation
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified Activity recording behavior.
|
|
||||||
- Produces docs that mark Activity integration for capture conversion complete.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update docs**
|
|
||||||
|
|
||||||
Describe that `browser.capture.converted` is recorded by Activity and that
|
|
||||||
link/file-specific conversions remain.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "browser.capture.converted|link/file" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `git diff --check` exits 0 and `rg` shows updated status lines.
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
# Browser Inbox Create Link Conversion Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add a Browser Inbox conversion workflow that creates an ordinary `.url` link file from a captured URL.
|
|
||||||
|
|
||||||
**Architecture:** Keep conversion behavior in `verstak.browser-inbox`. The plugin writes a human-readable `.url` file through `api.files.writeText`, publishes `browser.capture.converted`, and leaves Activity/other consumers decoupled through the event bus.
|
|
||||||
|
|
||||||
**Tech Stack:** Plain JavaScript official plugin bundle, Node smoke test harness, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not add a core link entity model in this slice.
|
|
||||||
- Do not call private APIs from Browser Inbox.
|
|
||||||
- Use only public plugin APIs: `api.files.writeText` and `api.events.publish`.
|
|
||||||
- Do not silently rename on link path conflict; rely on `overwrite: false`.
|
|
||||||
- Use TDD: write the failing smoke test first, run it red, then implement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Browser Inbox Create Link Conversion
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes:
|
|
||||||
- `api.files.writeText(relativePath, content, { createIfMissing: true, overwrite: false })`
|
|
||||||
- `api.events.publish('browser.capture.converted', payload)`
|
|
||||||
- Produces:
|
|
||||||
- `Create Link` action for workspace-scoped URL captures.
|
|
||||||
- `browser.capture.converted` event with `conversionType: "link"`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing smoke test**
|
|
||||||
|
|
||||||
Add a scenario to `scripts/smoke-browser-inbox-plugin.js` that clicks
|
|
||||||
`data-browser-inbox-action="create-link"` and asserts the written path,
|
|
||||||
`.url` content, write options, queue removal, and published event.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because `Create Link` is not rendered.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement conversion**
|
|
||||||
|
|
||||||
Add helpers in `plugins/browser-inbox/frontend/src/index.js`:
|
|
||||||
|
|
||||||
- `safeLinkFilename(title)`;
|
|
||||||
- `captureToUrlShortcut(capture)`;
|
|
||||||
- `createLinkFromCapture(capture)`.
|
|
||||||
|
|
||||||
Render a `Create Link` button for captures with `workspaceRootPath` and `url`.
|
|
||||||
On success, remove the capture and publish `browser.capture.converted`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
### Task 2: Roadmap Documentation
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified create-link conversion behavior.
|
|
||||||
- Produces docs that mark link file conversion done while leaving file attachment capture for later.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update docs**
|
|
||||||
|
|
||||||
Describe Browser Inbox create-link conversion through the public Files API and
|
|
||||||
mark link conversion as complete.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "Create Link|link conversion|file attachment|browser.capture.converted" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `git diff --check` exits 0 and `rg` shows updated status lines.
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
# Browser Inbox Create Note Conversion Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add a first Browser Inbox conversion workflow that creates an ordinary Markdown note from a capture.
|
|
||||||
|
|
||||||
**Architecture:** Keep conversion behavior in `verstak.browser-inbox`. The plugin uses only public `api.files.writeText` and `api.events.publish`, removes the capture after a successful conversion, and leaves conversion consumers decoupled through the event bus.
|
|
||||||
|
|
||||||
**Tech Stack:** Plain JavaScript official plugin bundle, Node smoke test harness, plugin manifest permissions, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not move Browser Inbox conversion workflows into desktop core.
|
|
||||||
- Do not call Notes, Activity, Journal, or Search private APIs.
|
|
||||||
- Use only public plugin APIs: `api.files.writeText` and `api.events.publish`.
|
|
||||||
- Do not silently rename on note path conflict; rely on `overwrite: false`.
|
|
||||||
- Use TDD: write the failing smoke test first, run it red, then implement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Browser Inbox Create Note Conversion
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/plugin.json`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes:
|
|
||||||
- `api.files.writeText(relativePath, content, { createIfMissing: true, overwrite: false })`
|
|
||||||
- `api.events.publish('browser.capture.converted', payload)`
|
|
||||||
- Produces:
|
|
||||||
- `Create Note` action for workspace-scoped captures.
|
|
||||||
- `browser.capture.converted` event after successful conversion.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing smoke test**
|
|
||||||
|
|
||||||
Add fake API support for `files.writeText` and `events.publish` in
|
|
||||||
`scripts/smoke-browser-inbox-plugin.js`, then add a scenario that clicks
|
|
||||||
`data-browser-inbox-action="create-note"` and asserts the written path,
|
|
||||||
Markdown content, write options, queue removal, and published event.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because `Create Note` is not rendered.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement conversion**
|
|
||||||
|
|
||||||
Add helpers in `plugins/browser-inbox/frontend/src/index.js`:
|
|
||||||
|
|
||||||
- `noteTitle(capture)`;
|
|
||||||
- `safeNoteFilename(title)`;
|
|
||||||
- `captureToMarkdown(capture)`;
|
|
||||||
- `createNoteFromCapture(capture)`.
|
|
||||||
|
|
||||||
Render a `Create Note` button for captures with a workspace root. On success,
|
|
||||||
remove the capture and publish `browser.capture.converted`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Update manifest permissions**
|
|
||||||
|
|
||||||
Add `files.write` and `events.publish` to
|
|
||||||
`plugins/browser-inbox/plugin.json`.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
### Task 2: Roadmap Documentation
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified create-note conversion behavior.
|
|
||||||
- Produces docs that mark note conversion done while leaving link/file/activity conversion for later.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update docs**
|
|
||||||
|
|
||||||
Describe Browser Inbox create-note conversion through the public Files API and
|
|
||||||
mark only note conversion as complete.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "Create Note|note conversion|conversion workflow|notes/links/files/activity" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `git diff --check` exits 0 and `rg` shows updated status lines.
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
# Browser Inbox Domain Binding Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Route unscoped Browser Inbox captures into workspace queues through plugin-owned domain bindings.
|
|
||||||
|
|
||||||
**Architecture:** Keep domain binding in `verstak.browser-inbox`. The plugin reads `domainBindings` from its own settings namespace, annotates unscoped incoming captures before storage, and preserves desktop core as a capture-event publisher only.
|
|
||||||
|
|
||||||
**Tech Stack:** Plain JavaScript official plugin bundle, Node smoke test harness, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not move Browser Inbox queues or conversion workflows into desktop core.
|
|
||||||
- Do not import Notes, Files, Activity, or Journal from Browser Inbox.
|
|
||||||
- Route only captures that do not already include `workspaceRootPath`.
|
|
||||||
- Domain matching is exact and case-insensitive for this slice.
|
|
||||||
- Use TDD: write the failing smoke test first, run it red, then implement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Browser Inbox Domain Routing
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes: Browser Inbox plugin settings key `domainBindings`.
|
|
||||||
- Produces: unscoped captures annotated with `workspaceRootPath` and `workspaceName` when an exact domain binding exists.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing smoke test**
|
|
||||||
|
|
||||||
Add a scenario to `scripts/smoke-browser-inbox-plugin.js`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const bindingApi = makeApi({
|
|
||||||
domainBindings: {
|
|
||||||
'client.example.com': 'ClientA',
|
|
||||||
'project.example.com': 'Project'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const bindingGlobal = await mountWithApi(bindingApi, {});
|
|
||||||
await bindingApi.handlers['browser.capture.page']({
|
|
||||||
name: 'browser.capture.page',
|
|
||||||
timestamp: '2026-06-29T00:00:00Z',
|
|
||||||
payload: {
|
|
||||||
captureId: 'bound-client-capture',
|
|
||||||
capturedAt: '2026-06-29T00:00:00.000Z',
|
|
||||||
kind: 'page',
|
|
||||||
url: 'https://client.example.com/page',
|
|
||||||
title: 'Bound Client Page',
|
|
||||||
domain: 'client.example.com'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await flush();
|
|
||||||
if (bindingApi.getStoredCaptures('captures:workspace:ClientA').length !== 1) {
|
|
||||||
throw new Error('domain-bound capture was not stored under ClientA workspace key');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Also prove explicit `workspaceRootPath: "Project"` wins over a binding for
|
|
||||||
`client.example.com`.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because domain-bound captures are still stored in the receiving
|
|
||||||
view scope.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement minimal routing**
|
|
||||||
|
|
||||||
In `plugins/browser-inbox/frontend/src/index.js`, add helper functions to
|
|
||||||
normalize binding keys, derive a domain from capture fields, and annotate
|
|
||||||
captures without `workspaceRootPath` before storage.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
### Task 2: Roadmap Documentation
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified plugin routing behavior.
|
|
||||||
- Produces docs that distinguish implemented domain binding from future conversion workflows.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update docs**
|
|
||||||
|
|
||||||
Mark domain binding as implemented in the Browser Inbox status text and roadmap
|
|
||||||
while leaving conversion workflow as future work.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "domain-to-workspace binding|Domain binding|conversion" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `git diff --check` exits 0 and `rg` shows the updated status lines.
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
# Browser Inbox Text File Attachment Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add end-to-end text file attachment capture and conversion through Browser Inbox.
|
|
||||||
|
|
||||||
**Architecture:** The browser extension remains a protocol producer and does not know workspace internals. Desktop receiver validates and republishes `browser.capture.file`; Browser Inbox stores the capture and writes the final text file through the public Files API.
|
|
||||||
|
|
||||||
**Tech Stack:** Plain JavaScript WebExtension code, Go receiver tests, official plugin JavaScript smoke harness, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not add private `.verstak/inbox` staging in this slice.
|
|
||||||
- Do not add binary file writes in this slice.
|
|
||||||
- Use only existing public plugin APIs for conversion: `api.files.writeText` and `api.events.publish`.
|
|
||||||
- Keep the extension offline queue behavior unchanged.
|
|
||||||
- Use TDD for each behavior change.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Extension Text File Capture Protocol
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/protocol.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/background.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.html`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.css`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-protocol.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/README.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces `kind: "file"` capture payloads with `file.name`, `file.mime`,
|
|
||||||
`file.size`, and `file.text`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED protocol assertions**
|
|
||||||
|
|
||||||
Add a test that builds a file capture and asserts `validateCapture` accepts it,
|
|
||||||
then add a validation rejection for missing `file.text`.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failure mentioning unsupported `kind` or missing file validation.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement protocol and popup capture**
|
|
||||||
|
|
||||||
Add `file` payload support in `buildCapture` / `validateCapture`. Add a popup
|
|
||||||
file input and `Send File` button that reads one selected file as text and sends
|
|
||||||
`{ type: "verstak.capture", kind: "file", fileName, fileMime, fileSize, fileText }`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: protocol tests pass and extension dist builds.
|
|
||||||
|
|
||||||
### Task 2: Desktop Receiver File Event
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/browserreceiver/receiver.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/browserreceiver/receiver_test.go`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes extension `kind: "file"` payloads.
|
|
||||||
- Produces `browser.capture.file` events with flattened `fileName`,
|
|
||||||
`fileMime`, `fileSize`, and `fileText`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED receiver test**
|
|
||||||
|
|
||||||
Add a test that subscribes to `browser.capture.file`, posts a file capture, and
|
|
||||||
asserts the flattened event payload.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
|
||||||
go test ./internal/core/browserreceiver -run TestReceiverAcceptsFileCaptureAndPublishesEvent -count=1
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failure with unsupported kind.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement receiver support**
|
|
||||||
|
|
||||||
Add a `CaptureFile` struct, `File *CaptureFile` field, file validation, and
|
|
||||||
file payload flattening in `EventPayload`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/core/browserreceiver
|
|
||||||
./scripts/test.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: receiver package and desktop script pass.
|
|
||||||
|
|
||||||
### Task 3: Browser Inbox Create File Conversion
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes `browser.capture.file` events.
|
|
||||||
- Produces `Create File` action and `browser.capture.converted` with
|
|
||||||
`conversionType: "file"`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write RED smoke test**
|
|
||||||
|
|
||||||
Add success and failure scenarios for a workspace file capture. Assert the write
|
|
||||||
path `Project/Files/notes.txt`, file content, write options, queue removal, and
|
|
||||||
converted event.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: failure because `browser.capture.file` is not subscribed or
|
|
||||||
`Create File` is not rendered.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement conversion**
|
|
||||||
|
|
||||||
Preserve `fileName`, `fileMime`, `fileSize`, and `fileText` in storage. Render a
|
|
||||||
`Create File` button for workspace file captures with text content. Write the
|
|
||||||
safe file path through `api.files.writeText`, publish the converted event, and
|
|
||||||
remove the capture on success.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
|
||||||
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: browser inbox smoke and official plugin check pass.
|
|
||||||
|
|
||||||
### Task 4: Roadmap Documentation
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified file capture/conversion behavior.
|
|
||||||
- Produces roadmap language that marks text file attachment conversion complete
|
|
||||||
while leaving binary attachments for a future public API slice.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update docs**
|
|
||||||
|
|
||||||
Describe Browser Inbox text file capture and conversion through the public Files
|
|
||||||
API.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "text file attachment|binary attachment|browser.capture.file|Create File" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md docs/superpowers/specs/2026-06-29-browser-inbox-text-file-attachment-design.md docs/superpowers/plans/2026-06-29-browser-inbox-text-file-attachment.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: whitespace check exits 0 and `rg` shows updated status lines.
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
# Browser Receiver Pairing Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add a token-based pairing gate to the local browser capture receiver.
|
|
||||||
|
|
||||||
**Architecture:** Keep Browser Inbox as a plugin. The desktop runtime generates
|
|
||||||
and requires an installation-local receiver token, while the plugin and browser
|
|
||||||
extension expose the settings transfer and rotation flow.
|
|
||||||
|
|
||||||
**Tech Stack:** Go desktop core package tests, browser extension protocol docs, Markdown docs.
|
|
||||||
|
|
||||||
## Completion Status (2026-07-10)
|
|
||||||
|
|
||||||
Completed. The implementation also added bounded ingress validation, token
|
|
||||||
persistence and rotation, the `browser.receiver.manage` SDK permission, a
|
|
||||||
Browser Inbox settings panel, and extension popup token persistence.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Do not move Browser Inbox queues or conversion workflows into desktop core.
|
|
||||||
- Production startup must fail closed when a token cannot be persisted.
|
|
||||||
- Paired mode must not publish capture events for missing or wrong tokens.
|
|
||||||
- Use TDD: write the failing Go receiver test first, run it red, then implement.
|
|
||||||
- Commit and push each affected repository after meaningful changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Document The Pairing Contract
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-docs/docs/superpowers/specs/2026-06-29-browser-receiver-pairing-design.md`
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-docs/docs/superpowers/plans/2026-06-29-browser-receiver-pairing.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces documented `X-Verstak-Receiver-Token` pairing contract.
|
|
||||||
|
|
||||||
- [x] **Step 1: Write spec and plan**
|
|
||||||
|
|
||||||
Write the design and this implementation plan.
|
|
||||||
|
|
||||||
- [x] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: exits 0.
|
|
||||||
|
|
||||||
- [x] **Step 3: Commit and push docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git add docs/superpowers/specs/2026-06-29-browser-receiver-pairing-design.md docs/superpowers/plans/2026-06-29-browser-receiver-pairing.md
|
|
||||||
git commit -m "docs: plan browser receiver pairing"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: docs `main` is clean and pushed.
|
|
||||||
|
|
||||||
### Task 2: Desktop Receiver Token Gate
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop/internal/core/browserreceiver/receiver_test.go`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop/internal/core/browserreceiver/receiver.go`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces:
|
|
||||||
- `type Options struct { RequireToken bool; ReceiverToken string }`
|
|
||||||
- `func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProvider) *Receiver`
|
|
||||||
|
|
||||||
- [x] **Step 1: Write the failing tests**
|
|
||||||
|
|
||||||
Add tests proving missing/wrong token rejection and correct token acceptance.
|
|
||||||
|
|
||||||
- [x] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
|
||||||
go test ./internal/core/browserreceiver
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because `Options` / `NewWithOptions` do not exist.
|
|
||||||
|
|
||||||
- [x] **Step 3: Implement token gate**
|
|
||||||
|
|
||||||
Add `Options`, `NewWithOptions`, header validation, and constant-time token
|
|
||||||
comparison. Keep `New` behavior unchanged.
|
|
||||||
|
|
||||||
- [x] **Step 4: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
|
||||||
go test ./internal/core/browserreceiver
|
|
||||||
go test ./internal/core/...
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
- [x] **Step 5: Commit and push desktop**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
|
||||||
git add internal/core/browserreceiver/receiver.go internal/core/browserreceiver/receiver_test.go
|
|
||||||
git commit -m "feat: require token for paired browser receiver"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: commit is pushed. Existing unrelated `frontend/wailsjs/go/models.ts`
|
|
||||||
remains unstaged.
|
|
||||||
|
|
||||||
### Task 3: Extension And Roadmap Docs
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/README.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified receiver token gate.
|
|
||||||
- Produces docs matching implemented pairing behavior.
|
|
||||||
|
|
||||||
- [x] **Step 1: Update extension README**
|
|
||||||
|
|
||||||
Change the receiver token header description from optional future work to:
|
|
||||||
|
|
||||||
```md
|
|
||||||
- `X-Verstak-Receiver-Token: <token>` required when the desktop receiver is in paired mode
|
|
||||||
```
|
|
||||||
|
|
||||||
- [x] **Step 2: Verify and commit extension docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension
|
|
||||||
git diff --check
|
|
||||||
git add README.md
|
|
||||||
git commit -m "docs: describe receiver token pairing"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: extension `main` is clean and pushed.
|
|
||||||
|
|
||||||
- [x] **Step 3: Update platform docs**
|
|
||||||
|
|
||||||
In `05_Official_Plugins.md`, describe that Browser Inbox receives captures
|
|
||||||
through the local receiver token pairing model. In
|
|
||||||
`07_Full_Implementation_Roadmap.md`, mark:
|
|
||||||
|
|
||||||
```md
|
|
||||||
- [x] define local receiver permission/pairing model;
|
|
||||||
```
|
|
||||||
|
|
||||||
- [x] **Step 4: Verify and commit docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "define local receiver permission/pairing model|X-Verstak-Receiver-Token" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
git add 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
git commit -m "docs: mark browser receiver pairing complete"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: docs `main` is clean and pushed.
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
# Journal Worklog Plugin Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Create a baseline `verstak.journal` plugin that stores worklog entries and imports Activity worklog suggestions.
|
|
||||||
|
|
||||||
**Architecture:** Implement Journal as a plain official frontend plugin. Store entries in plugin settings by workspace key, render a workspace/global Journal view, and consume Activity suggestions through `api.commands.executeFor`.
|
|
||||||
|
|
||||||
**Tech Stack:** Official plugin JSON manifest, browserless JavaScript bundle, Node smoke test, Markdown docs.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Journal/worklog remains plugin functionality, not desktop core.
|
|
||||||
- Do not build billing reports or invoice export in this slice.
|
|
||||||
- Import from Activity must degrade cleanly when Activity is unavailable.
|
|
||||||
- Use TDD: write and run a failing smoke test before creating the plugin.
|
|
||||||
- Commit and push each affected repository after meaningful changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Document The Slice
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-docs/docs/superpowers/specs/2026-06-29-journal-worklog-plugin-design.md`
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-docs/docs/superpowers/plans/2026-06-29-journal-worklog-plugin.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces a written contract for `verstak.journal`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write spec and plan**
|
|
||||||
|
|
||||||
Write the design and this implementation plan.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: exits 0.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit and push docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git add docs/superpowers/specs/2026-06-29-journal-worklog-plugin-design.md docs/superpowers/plans/2026-06-29-journal-worklog-plugin.md
|
|
||||||
git commit -m "docs: plan journal worklog plugin"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: docs `main` is clean and pushed.
|
|
||||||
|
|
||||||
### Task 2: Journal Plugin
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/plugin.json`
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/frontend/src/index.js`
|
|
||||||
- Create: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-journal-plugin.js`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/check.sh`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces plugin id `verstak.journal`.
|
|
||||||
- Produces component `JournalView`.
|
|
||||||
- Consumes `api.commands.executeFor('verstak.activity', 'verstak.activity.suggestWorklog', args)`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing smoke test**
|
|
||||||
|
|
||||||
Create `scripts/smoke-journal-plugin.js` and assert manifest identity,
|
|
||||||
mounting, manual entry storage, Activity import, duplicate prevention, and
|
|
||||||
global aggregation.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run RED**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
node scripts/smoke-journal-plugin.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: fails because the Journal manifest and frontend entry do not exist.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Add manifest and frontend**
|
|
||||||
|
|
||||||
Create `plugins/journal/plugin.json` with `provides` of `worklog`, `journal`,
|
|
||||||
and `report.worklog`, optional dependency `activity.reconstruction`, and
|
|
||||||
workspace/sidebar UI contributions. Create `JournalView` that stores entries
|
|
||||||
under `worklog:workspace:<encoded workspace root>`.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Wire check script**
|
|
||||||
|
|
||||||
Add `node "$ROOT/scripts/smoke-journal-plugin.js"` to the frontend smoke
|
|
||||||
section in `scripts/check.sh`.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Run GREEN**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
node scripts/smoke-journal-plugin.js
|
|
||||||
./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: both commands exit 0.
|
|
||||||
|
|
||||||
- [ ] **Step 6: Commit and push official plugin**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
|
||||||
git add plugins/journal scripts/smoke-journal-plugin.js scripts/check.sh
|
|
||||||
git commit -m "feat: add journal worklog plugin"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: official plugins `main` is clean and pushed.
|
|
||||||
|
|
||||||
### Task 3: Mark Roadmap Progress
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
|
||||||
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes verified Journal plugin commit.
|
|
||||||
- Produces docs matching implemented behavior.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Update Journal status**
|
|
||||||
|
|
||||||
Add a current status sentence describing the baseline Journal plugin, manual
|
|
||||||
worklog entries, and Activity suggestion import.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update roadmap**
|
|
||||||
|
|
||||||
Change:
|
|
||||||
|
|
||||||
```md
|
|
||||||
- implement journal/worklog plugin that can consume activity suggestions.
|
|
||||||
```
|
|
||||||
|
|
||||||
to:
|
|
||||||
|
|
||||||
```md
|
|
||||||
- [x] implement journal/worklog plugin that can consume activity suggestions.
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git diff --check
|
|
||||||
rg -n "implement journal/worklog plugin that can consume activity suggestions|baseline `verstak.journal`" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `git diff --check` exits 0 and `rg` shows the checked roadmap line
|
|
||||||
and Journal status.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit and push docs**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/mirivlad/git/verstak2/verstak-docs
|
|
||||||
git add 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md
|
|
||||||
git commit -m "docs: mark journal worklog plugin complete"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: docs `main` is clean and pushed.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# Activity Worklog Suggestions Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
`verstak.activity` already records public plugin events and displays scoped
|
|
||||||
activity streams. The next roadmap step is to turn that stream into a small,
|
|
||||||
useful reconstruction layer that can suggest worklog entries without making
|
|
||||||
Journal a core feature.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice adds worklog suggestions to the official Activity plugin only.
|
|
||||||
It does not create the Journal plugin, reports, billing states, manual time
|
|
||||||
editing, or final worklog persistence. Those remain the next roadmap item.
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
- Activity reconstructs suggestions from the same normalized events it already
|
|
||||||
renders.
|
|
||||||
- Suggestions are scoped the same way as the Activity view:
|
|
||||||
- a workspace Activity view uses only that workspace stream;
|
|
||||||
- the global Activity view aggregates all streams and produces suggestions
|
|
||||||
per workspace/day.
|
|
||||||
- Events are grouped by workspace and calendar day using `occurredAt` or
|
|
||||||
`receivedAt`.
|
|
||||||
- Each group produces one suggestion with:
|
|
||||||
- stable `suggestionId`;
|
|
||||||
- `workspaceRootPath`;
|
|
||||||
- `date`;
|
|
||||||
- `title`;
|
|
||||||
- `summary`;
|
|
||||||
- `minutes`;
|
|
||||||
- source `eventIds`.
|
|
||||||
- Estimated time is conservative:
|
|
||||||
- one event: 15 minutes;
|
|
||||||
- multiple events: time span between first and last event, rounded up to
|
|
||||||
15-minute increments;
|
|
||||||
- minimum 15 minutes, maximum 480 minutes.
|
|
||||||
|
|
||||||
## Runtime Contract
|
|
||||||
|
|
||||||
The Activity plugin contributes and registers this command:
|
|
||||||
|
|
||||||
```text
|
|
||||||
verstak.activity.suggestWorklog
|
|
||||||
```
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workspaceRootPath": "Project"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`workspaceRootPath` is optional. When omitted, the command returns suggestions
|
|
||||||
for all available activity streams.
|
|
||||||
|
|
||||||
Result:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"suggestions": [
|
|
||||||
{
|
|
||||||
"suggestionId": "worklog:Project:2026-06-27",
|
|
||||||
"workspaceRootPath": "Project",
|
|
||||||
"date": "2026-06-27",
|
|
||||||
"title": "Project work on 2026-06-27",
|
|
||||||
"summary": "Example Article; Saved note",
|
|
||||||
"minutes": 30,
|
|
||||||
"eventIds": ["capture-1", "note-1"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If the command cannot read settings, it returns an empty suggestions array and
|
|
||||||
does not throw for normal UI consumption.
|
|
||||||
|
|
||||||
## UI
|
|
||||||
|
|
||||||
Activity shows a compact suggestions band above the event list when suggestions
|
|
||||||
exist. The band is informational only in this slice. It exposes stable DOM data
|
|
||||||
attributes for smoke tests and later Journal wiring:
|
|
||||||
|
|
||||||
- `data-activity-section="worklog-suggestions"`
|
|
||||||
- `data-worklog-suggestion="<suggestionId>"`
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`scripts/smoke-activity-plugin.js` must verify:
|
|
||||||
|
|
||||||
- the manifest requests `commands.register`;
|
|
||||||
- the manifest declares `verstak.activity.suggestWorklog`;
|
|
||||||
- Activity registers the command when mounted;
|
|
||||||
- workspace Activity renders a worklog suggestion for multiple events;
|
|
||||||
- the command returns the same suggestion shape;
|
|
||||||
- global Activity groups suggestions by workspace and day;
|
|
||||||
- clearing Activity clears suggestions.
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# Binary File Write And Browser Attachments Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Browser Inbox still cannot complete binary file attachment conversion because
|
|
||||||
the public Files API can read bounded bytes but cannot write them. This slice
|
|
||||||
adds a bounded public `files.writeBytes` contract and uses it to convert browser
|
|
||||||
file captures that contain base64 data.
|
|
||||||
|
|
||||||
The goal is not streaming or large-file import. It is a safe, testable binary
|
|
||||||
write path for small attachments that keeps plugins on public APIs.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice adds:
|
|
||||||
|
|
||||||
- `api.files.writeBytes(relativePath, dataBase64, options)` for enabled plugins
|
|
||||||
with `files.write`;
|
|
||||||
- desktop service support for atomic bounded byte writes up to 8 MB;
|
|
||||||
- sync op payload support for `dataBase64`, while preserving older text
|
|
||||||
`content` payloads;
|
|
||||||
- SDK type/mock/test coverage for `files.writeBytes`;
|
|
||||||
- browser extension file capture that sends `file.dataBase64` for all selected
|
|
||||||
files up to 8 MB and keeps `file.text` only for text-compatible files;
|
|
||||||
- local receiver flattening of `fileDataBase64`;
|
|
||||||
- Browser Inbox conversion that prefers `api.files.writeBytes` when binary data
|
|
||||||
is present and falls back to `api.files.writeText` for text-only captures.
|
|
||||||
|
|
||||||
It does not add chunked streaming, folders, drag-and-drop from pages, download
|
|
||||||
interception, or binary preview changes.
|
|
||||||
|
|
||||||
## Files API Contract
|
|
||||||
|
|
||||||
`files.writeBytes(relativePath, dataBase64, options)`:
|
|
||||||
|
|
||||||
- accepts canonical vault-relative slash paths;
|
|
||||||
- rejects traversal, absolute paths, `.verstak`, symlinks, folders, missing
|
|
||||||
parents, and conflicts using the same path policy as `writeText`;
|
|
||||||
- rejects invalid base64;
|
|
||||||
- rejects decoded payloads over `MaxBinaryReadBytes` (8 MB);
|
|
||||||
- writes atomically through a temp file in the target directory;
|
|
||||||
- requires `files.write`;
|
|
||||||
- records the same `file.changed` activity shape as text writes;
|
|
||||||
- records sync create/update payloads as `{ "path": "...", "dataBase64": "..." }`.
|
|
||||||
|
|
||||||
Remote sync apply supports both:
|
|
||||||
|
|
||||||
- old text payloads: `{ "path": "...", "content": "..." }`;
|
|
||||||
- new byte payloads: `{ "path": "...", "dataBase64": "..." }`.
|
|
||||||
|
|
||||||
## Browser Capture Contract
|
|
||||||
|
|
||||||
For `kind: "file"`, the extension sends:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"file": {
|
|
||||||
"name": "photo.png",
|
|
||||||
"mime": "image/png",
|
|
||||||
"size": 1234,
|
|
||||||
"dataBase64": "iVBORw0KGgo...",
|
|
||||||
"text": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`file.name` is required. A file capture is valid when either `file.dataBase64`
|
|
||||||
or `file.text` is present. The extension rejects selected files over 8 MB before
|
|
||||||
sending.
|
|
||||||
|
|
||||||
## Browser Inbox Conversion
|
|
||||||
|
|
||||||
Browser Inbox stores both `fileText` and `fileDataBase64`. `Create File` writes:
|
|
||||||
|
|
||||||
- `api.files.writeBytes(path, fileDataBase64, { createIfMissing: true,
|
|
||||||
overwrite: false })` when binary data is present;
|
|
||||||
- otherwise `api.files.writeText(path, fileText, { createIfMissing: true,
|
|
||||||
overwrite: false })`.
|
|
||||||
|
|
||||||
On success it publishes `browser.capture.converted` with
|
|
||||||
`conversionType: "file"` and removes the capture. On write failure it leaves the
|
|
||||||
capture in the queue and renders an error.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Required checks:
|
|
||||||
|
|
||||||
- core Files service writes bytes, rejects invalid/oversized payloads, and
|
|
||||||
preserves conflict/path behavior;
|
|
||||||
- desktop API bridge enforces `files.write`, records sync `dataBase64`, and
|
|
||||||
applies remote binary payloads;
|
|
||||||
- frontend plugin API exposes `files.writeBytes`;
|
|
||||||
- SDK types and mock API include `writeBytes`;
|
|
||||||
- browser extension protocol builds, validates, and queues binary captures;
|
|
||||||
- receiver accepts file captures with `dataBase64`;
|
|
||||||
- Browser Inbox smoke proves binary conversion uses `writeBytes`;
|
|
||||||
- full verification scripts pass for touched repos.
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# Browser Inbox Conversion Activity Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Browser Inbox now publishes `browser.capture.converted` when a capture becomes
|
|
||||||
a Markdown note. Activity should record that conversion through the same public
|
|
||||||
event subscription model it already uses for file, note, workspace, and browser
|
|
||||||
capture events.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice wires one public event into `verstak.activity`:
|
|
||||||
|
|
||||||
- subscribe to `browser.capture.converted`;
|
|
||||||
- declare the event in the Activity plugin `activityProviders` contribution;
|
|
||||||
- store and render conversion activity in the existing workspace/global streams;
|
|
||||||
- include conversion events in worklog reconstruction.
|
|
||||||
|
|
||||||
It does not add a direct Browser Inbox to Activity API, special conversion UI,
|
|
||||||
or new desktop core behavior.
|
|
||||||
|
|
||||||
## Event Shape
|
|
||||||
|
|
||||||
Browser Inbox emits:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
captureId: "capture-id",
|
|
||||||
conversionType: "note",
|
|
||||||
notePath: "Project/Notes/Example.md",
|
|
||||||
workspaceRootPath: "Project",
|
|
||||||
title: "Example",
|
|
||||||
url: "https://example.com",
|
|
||||||
sourcePluginId: "verstak.browser-inbox"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Activity stores the event using its normal event normalization. The title should
|
|
||||||
come from `payload.title`; summary should naturally include the best available
|
|
||||||
payload text such as `notePath` through the existing summary logic.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`scripts/smoke-activity-plugin.js` must prove:
|
|
||||||
|
|
||||||
- `browser.capture.converted` is subscribed by the Activity frontend;
|
|
||||||
- the Activity manifest advertises the event in `activityProviders`;
|
|
||||||
- a workspace-scoped conversion event is stored under that workspace key;
|
|
||||||
- the rendered Activity view includes the conversion title and event type;
|
|
||||||
- worklog suggestions include the conversion event id.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Browser Inbox Create Link Conversion Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Browser captures should be convertible into a lightweight link artifact without
|
|
||||||
forcing every URL into Notes. This slice adds a public Files API conversion that
|
|
||||||
creates an ordinary human-readable `.url` file in the workspace.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice adds one conversion action inside `verstak.browser-inbox`:
|
|
||||||
|
|
||||||
- render `Create Link` for captures that have both `workspaceRootPath` and a
|
|
||||||
URL;
|
|
||||||
- write a `.url` shortcut file to `<workspaceRootPath>/Links/<safe-title>.url`
|
|
||||||
through `api.files.writeText`;
|
|
||||||
- publish `browser.capture.converted` with `conversionType: "link"`;
|
|
||||||
- remove the converted capture from the inbox queue after a successful write;
|
|
||||||
- leave the capture in place and show an error if the write fails.
|
|
||||||
|
|
||||||
It does not add a core link entity model, a link resolver plugin, bulk
|
|
||||||
conversion, or file attachment capture.
|
|
||||||
|
|
||||||
## Link File Format
|
|
||||||
|
|
||||||
The generated file is a plain text Internet shortcut:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[InternetShortcut]
|
|
||||||
URL=https://example.com/article
|
|
||||||
```
|
|
||||||
|
|
||||||
The plugin writes with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
api.files.writeText(linkPath, content, {
|
|
||||||
createIfMissing: true,
|
|
||||||
overwrite: false
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
This preserves explicit conflict behavior. The plugin must not silently append a
|
|
||||||
suffix when a link file already exists.
|
|
||||||
|
|
||||||
## Event Contract
|
|
||||||
|
|
||||||
After a successful conversion the plugin publishes:
|
|
||||||
|
|
||||||
```js
|
|
||||||
api.events.publish('browser.capture.converted', {
|
|
||||||
captureId,
|
|
||||||
conversionType: 'link',
|
|
||||||
linkPath,
|
|
||||||
workspaceRootPath,
|
|
||||||
title,
|
|
||||||
url,
|
|
||||||
sourcePluginId: 'verstak.browser-inbox'
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Activity can record this through the already-supported
|
|
||||||
`browser.capture.converted` event.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`scripts/smoke-browser-inbox-plugin.js` must prove:
|
|
||||||
|
|
||||||
- a workspace capture with a URL renders `Create Link`;
|
|
||||||
- clicking it writes the `.url` file through `api.files.writeText`;
|
|
||||||
- the write uses `createIfMissing: true` and `overwrite: false`;
|
|
||||||
- the capture is removed from its queue after success;
|
|
||||||
- `browser.capture.converted` is published with `conversionType: "link"`;
|
|
||||||
- when the write rejects, the capture remains in the queue and an error status
|
|
||||||
is rendered.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# Browser Inbox Create Note Conversion Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Browser Inbox should turn a captured page, selection, or link into useful vault
|
|
||||||
material without knowing Notes internals. This first conversion slice creates an
|
|
||||||
ordinary Markdown note through the public Files API.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice adds one conversion action inside `verstak.browser-inbox`:
|
|
||||||
|
|
||||||
- render `Create Note` for captures that have a `workspaceRootPath`;
|
|
||||||
- write a Markdown file to `<workspaceRootPath>/Notes/<safe-title>.md` using
|
|
||||||
`api.files.writeText`;
|
|
||||||
- publish `browser.capture.converted` after a successful write;
|
|
||||||
- remove the converted capture from the inbox queue;
|
|
||||||
- leave the capture in place and show an error if the write fails.
|
|
||||||
|
|
||||||
It does not add a separate Notes plugin API, link entity model, file attachment
|
|
||||||
conversion, bulk conversion, or Activity/Journal-specific UI. Other plugins can
|
|
||||||
react to the published conversion event through the public event bus.
|
|
||||||
|
|
||||||
## Note Path And Content
|
|
||||||
|
|
||||||
The generated note filename is a human-readable safe projection of the capture
|
|
||||||
title:
|
|
||||||
|
|
||||||
1. Prefer capture `title`.
|
|
||||||
2. Fall back to capture `domain`.
|
|
||||||
3. Fall back to capture `captureId`.
|
|
||||||
4. Replace unsafe filename characters with `_`.
|
|
||||||
5. Append `.md`.
|
|
||||||
|
|
||||||
The plugin writes with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
api.files.writeText(notePath, markdown, {
|
|
||||||
createIfMissing: true,
|
|
||||||
overwrite: false
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
This preserves explicit conflict behavior. The plugin must not silently append a
|
|
||||||
suffix when a note already exists.
|
|
||||||
|
|
||||||
Markdown content:
|
|
||||||
|
|
||||||
```md
|
|
||||||
# <title>
|
|
||||||
|
|
||||||
Source: <url>
|
|
||||||
Captured: <capturedAt>
|
|
||||||
Kind: <kind>
|
|
||||||
|
|
||||||
<captured text when present>
|
|
||||||
```
|
|
||||||
|
|
||||||
Missing URL or text sections are omitted.
|
|
||||||
|
|
||||||
## Event Contract
|
|
||||||
|
|
||||||
After a successful conversion the plugin publishes:
|
|
||||||
|
|
||||||
```js
|
|
||||||
api.events.publish('browser.capture.converted', {
|
|
||||||
captureId,
|
|
||||||
conversionType: 'note',
|
|
||||||
notePath,
|
|
||||||
workspaceRootPath,
|
|
||||||
title,
|
|
||||||
url,
|
|
||||||
sourcePluginId: 'verstak.browser-inbox'
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
The event is informational. Browser Inbox does not require Activity to be
|
|
||||||
installed and does not directly call Activity, Notes, Journal, or Search.
|
|
||||||
|
|
||||||
## Permissions
|
|
||||||
|
|
||||||
`verstak.browser-inbox` must add:
|
|
||||||
|
|
||||||
- `files.write`;
|
|
||||||
- `events.publish`.
|
|
||||||
|
|
||||||
Existing permissions remain unchanged.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`scripts/smoke-browser-inbox-plugin.js` must prove:
|
|
||||||
|
|
||||||
- a workspace capture renders `Create Note`;
|
|
||||||
- clicking it writes the Markdown note through `api.files.writeText`;
|
|
||||||
- the write uses `createIfMissing: true` and `overwrite: false`;
|
|
||||||
- the capture is removed from its queue after success;
|
|
||||||
- `browser.capture.converted` is published with `conversionType: "note"`;
|
|
||||||
- when the write rejects, the capture remains in the queue and an error status
|
|
||||||
is rendered.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Browser Inbox Domain Binding Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Browser captures often arrive while the user is not focused on the workspace
|
|
||||||
that should receive them. Domain binding routes unscoped browser captures into a
|
|
||||||
workspace queue by matching the capture domain to a plugin-owned binding table.
|
|
||||||
|
|
||||||
This keeps Browser Inbox behavior in the official plugin. Desktop core still
|
|
||||||
only publishes browser capture events and may annotate the current workspace
|
|
||||||
when it knows one.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice implements domain-to-workspace routing inside `verstak.browser-inbox`:
|
|
||||||
|
|
||||||
- store bindings in the Browser Inbox plugin settings namespace;
|
|
||||||
- match capture domains case-insensitively;
|
|
||||||
- route only captures that do not already include `workspaceRootPath`;
|
|
||||||
- preserve the global aggregate view;
|
|
||||||
- keep the binding model independent from Notes, Files, Activity, and Journal.
|
|
||||||
|
|
||||||
It does not implement conversion into notes/links/files/activity, browser UI for
|
|
||||||
editing bindings, or a desktop core domain binding API.
|
|
||||||
|
|
||||||
## Settings Contract
|
|
||||||
|
|
||||||
The plugin reads `domainBindings` from its settings object:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"domainBindings": {
|
|
||||||
"example.com": "Project",
|
|
||||||
"client.example.com": "ClientA"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Keys are hostnames. Values are top-level `workspaceRootPath` strings.
|
|
||||||
|
|
||||||
Normalization rules:
|
|
||||||
|
|
||||||
- trim whitespace around keys and values;
|
|
||||||
- lowercase domains for matching;
|
|
||||||
- strip one or more leading dots from binding keys;
|
|
||||||
- ignore empty domains and empty workspace roots.
|
|
||||||
|
|
||||||
## Routing Rules
|
|
||||||
|
|
||||||
For each incoming `browser.capture.page`, `browser.capture.selection`, or
|
|
||||||
`browser.capture.link` event:
|
|
||||||
|
|
||||||
1. If payload already has `workspaceRootPath`, keep it unchanged.
|
|
||||||
2. Otherwise, derive a domain from `payload.domain` or `payload.url`.
|
|
||||||
3. If an exact normalized domain binding exists, set `workspaceRootPath` and
|
|
||||||
`workspaceName` to the bound workspace root before storage.
|
|
||||||
4. If no binding exists, keep current behavior and store the capture in the
|
|
||||||
receiving view scope.
|
|
||||||
|
|
||||||
Subdomain fallback is intentionally out of scope for this first slice. A
|
|
||||||
binding for `example.com` does not match `docs.example.com` until a later design
|
|
||||||
adds explicit wildcard or suffix semantics.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`scripts/smoke-browser-inbox-plugin.js` must prove:
|
|
||||||
|
|
||||||
- an unscoped capture with `domain: "client.example.com"` and a
|
|
||||||
`domainBindings` entry for that domain is stored under
|
|
||||||
`captures:workspace:ClientA`;
|
|
||||||
- the routed capture appears in the ClientA workspace view;
|
|
||||||
- the global view still aggregates the routed capture;
|
|
||||||
- an event that already includes `workspaceRootPath: "Project"` is not
|
|
||||||
overwritten by a domain binding for another workspace.
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
# Browser Inbox Text File Attachment Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Browser Inbox needs a first file attachment path that uses the current public
|
|
||||||
plugin API instead of adding a private core inbox or binary write shortcut. This
|
|
||||||
slice supports text file attachments end to end: the browser extension reads a
|
|
||||||
user-selected text file, the local receiver publishes a `browser.capture.file`
|
|
||||||
event, and Browser Inbox converts the capture into an ordinary workspace file
|
|
||||||
through `api.files.writeText`.
|
|
||||||
|
|
||||||
Binary attachments remain out of scope until the platform has an explicit
|
|
||||||
public binary write API.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice adds:
|
|
||||||
|
|
||||||
- extension protocol support for `kind: "file"`;
|
|
||||||
- popup file selection for text files;
|
|
||||||
- local receiver validation and event publication for file captures;
|
|
||||||
- Browser Inbox storage/rendering of file metadata and text content;
|
|
||||||
- a `Create File` conversion action for workspace-scoped text file captures.
|
|
||||||
|
|
||||||
It does not add drag-and-drop from web pages, screenshot capture, native download
|
|
||||||
interception, binary files, folders, or a core `.verstak/inbox` staging area.
|
|
||||||
|
|
||||||
## Capture Contract
|
|
||||||
|
|
||||||
The extension sends schema version 1 with a new file object:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"captureId": "generated-id",
|
|
||||||
"capturedAt": "2026-06-29T00:00:00.000Z",
|
|
||||||
"source": "verstak-browser-extension",
|
|
||||||
"kind": "file",
|
|
||||||
"page": {
|
|
||||||
"url": "https://example.com/current-page",
|
|
||||||
"title": "Current page",
|
|
||||||
"domain": "example.com"
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"name": "notes.txt",
|
|
||||||
"mime": "text/plain",
|
|
||||||
"size": 120,
|
|
||||||
"text": "file contents"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"name": "Firefox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`file.name` and `file.text` are required for `kind: "file"`. The extension
|
|
||||||
limits text content to 2 MB so it stays within the current Files API text-read
|
|
||||||
size and avoids turning local receiver events into large binary transport.
|
|
||||||
|
|
||||||
## Receiver Event
|
|
||||||
|
|
||||||
The desktop receiver accepts `kind: "file"` and publishes
|
|
||||||
`browser.capture.file`. The event payload flattens file fields for Browser Inbox:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
captureId,
|
|
||||||
capturedAt,
|
|
||||||
source,
|
|
||||||
kind: 'file',
|
|
||||||
url,
|
|
||||||
title,
|
|
||||||
domain,
|
|
||||||
fileName,
|
|
||||||
fileMime,
|
|
||||||
fileSize,
|
|
||||||
fileText,
|
|
||||||
browserName
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Current workspace annotation continues to work the same as page, selection, and
|
|
||||||
link captures.
|
|
||||||
|
|
||||||
## Browser Inbox Conversion
|
|
||||||
|
|
||||||
Browser Inbox subscribes to `browser.capture.file` and stores file fields in its
|
|
||||||
plugin-owned queue. In a workspace view, file captures render a `Create File`
|
|
||||||
action.
|
|
||||||
|
|
||||||
On success:
|
|
||||||
|
|
||||||
- write `<workspaceRootPath>/Files/<safe-file-name>` through
|
|
||||||
`api.files.writeText`;
|
|
||||||
- use `{ createIfMissing: true, overwrite: false }`;
|
|
||||||
- publish `browser.capture.converted` with `conversionType: "file"` and
|
|
||||||
`filePath`;
|
|
||||||
- remove the capture from the queue.
|
|
||||||
|
|
||||||
On write failure, Browser Inbox leaves the capture in the queue and renders an
|
|
||||||
error status.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Required checks:
|
|
||||||
|
|
||||||
- browser extension protocol test builds and validates `kind: "file"`;
|
|
||||||
- receiver unit test accepts a file capture and publishes
|
|
||||||
`browser.capture.file`;
|
|
||||||
- Browser Inbox smoke test verifies `Create File` write path, content, write
|
|
||||||
options, queue removal, converted event, and failure behavior;
|
|
||||||
- full repo checks continue to pass for touched repositories.
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Browser Receiver Pairing Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
The browser extension sends captures to the desktop local receiver with
|
|
||||||
`X-Verstak-Receiver-Token`. The desktop runtime starts that receiver only in
|
|
||||||
paired mode, without moving Browser Inbox queues or conversion behavior into
|
|
||||||
desktop core.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice covers only the local receiver permission/pairing model:
|
|
||||||
|
|
||||||
- generation and local persistence of a receiver token;
|
|
||||||
- receiver token validation and rotation without a server restart;
|
|
||||||
- bounded capture payload validation before an event is published;
|
|
||||||
- Browser Inbox and extension settings for transferring the token.
|
|
||||||
|
|
||||||
It does not implement browser UI for pairing QR codes or encrypted keyring
|
|
||||||
storage. The current token is kept in the installation-local app settings file,
|
|
||||||
which the manager writes with mode `0600`.
|
|
||||||
|
|
||||||
## Model
|
|
||||||
|
|
||||||
The production receiver always starts in paired mode. On startup it generates a
|
|
||||||
32-byte random token when none exists, stores it outside the vault in
|
|
||||||
`~/.config/verstak/config.json`, and does not log it. If that token cannot be
|
|
||||||
persisted, the local receiver is disabled rather than opened without a token.
|
|
||||||
Every capture request must include:
|
|
||||||
|
|
||||||
```text
|
|
||||||
X-Verstak-Receiver-Token: <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
The receiver compares the supplied token to the configured token using a
|
|
||||||
constant-time comparison. It does not publish browser capture events when the
|
|
||||||
token is missing or wrong. The package keeps the open constructor for embedded
|
|
||||||
legacy callers, but `main.go` does not use it.
|
|
||||||
|
|
||||||
## HTTP Contract
|
|
||||||
|
|
||||||
Endpoint:
|
|
||||||
|
|
||||||
```text
|
|
||||||
POST /api/browser-inbox/v1/captures
|
|
||||||
```
|
|
||||||
|
|
||||||
Successful capture:
|
|
||||||
|
|
||||||
```http
|
|
||||||
202 Accepted
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "status": "accepted", "captureId": "capture-id" }
|
|
||||||
```
|
|
||||||
|
|
||||||
Missing token in paired mode:
|
|
||||||
|
|
||||||
```http
|
|
||||||
401 Unauthorized
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "error": "receiver token required" }
|
|
||||||
```
|
|
||||||
|
|
||||||
Wrong token in paired mode:
|
|
||||||
|
|
||||||
```http
|
|
||||||
401 Unauthorized
|
|
||||||
```
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "error": "receiver token invalid" }
|
|
||||||
```
|
|
||||||
|
|
||||||
Other validation behavior remains unchanged: invalid payloads return `400`,
|
|
||||||
missing Browser Inbox consumers return `503`, and non-POST methods return
|
|
||||||
`405`. Payloads over 12 MiB return `413`; capture text, file metadata, encoded
|
|
||||||
binary data, and decoded file content are capped before publication.
|
|
||||||
|
|
||||||
## Runtime API
|
|
||||||
|
|
||||||
Desktop core exposes the model inside `internal/core/browserreceiver`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Options struct {
|
|
||||||
RequireToken bool
|
|
||||||
ReceiverToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWithOptions(bus *events.Bus, options Options, providers ...WorkspaceProvider) *Receiver
|
|
||||||
```
|
|
||||||
|
|
||||||
`New(bus, providers...)` remains the open legacy constructor.
|
|
||||||
|
|
||||||
The Wails bridge exposes `PluginBrowserReceiverPairing` and
|
|
||||||
`PluginRotateBrowserReceiverToken` only to plugins declaring the dangerous
|
|
||||||
`browser.receiver.manage` permission. `verstak.browser-inbox` presents the
|
|
||||||
receiver URL, token copy action, and rotation control; the browser extension
|
|
||||||
stores the values in its local settings.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`internal/core/browserreceiver/receiver_test.go` must prove:
|
|
||||||
|
|
||||||
- paired receivers reject missing tokens with `401`;
|
|
||||||
- paired receivers reject wrong tokens with `401`;
|
|
||||||
- paired receivers accept correct tokens and publish the capture event;
|
|
||||||
- a token persists across settings reload and changes immediately on rotation;
|
|
||||||
- oversized or malformed capture payloads do not publish an event;
|
|
||||||
- Browser Inbox and extension smoke tests cover the settings transfer path.
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Journal Worklog Plugin Design
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Add the first official Journal plugin as a replaceable plugin, not a desktop
|
|
||||||
core feature. It must give users a visible worklog surface and consume Activity
|
|
||||||
worklog suggestions through the command runtime added in
|
|
||||||
`verstak.activity.suggestWorklog`.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This slice creates `verstak.journal` with workspace-scoped worklog storage,
|
|
||||||
manual entry creation, and one-click import from Activity suggestions.
|
|
||||||
It does not implement billing reports, invoice export, timers, sync-specific
|
|
||||||
conflict handling, or cross-workspace report aggregation.
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
- Journal appears as both a sidebar view and a workspace item.
|
|
||||||
- In a workspace, entries are stored under a workspace settings key.
|
|
||||||
- In the global view, Journal aggregates entries from every workspace key.
|
|
||||||
- A manual entry contains:
|
|
||||||
- `entryId`;
|
|
||||||
- `workspaceRootPath`;
|
|
||||||
- `date`;
|
|
||||||
- `title`;
|
|
||||||
- `summary`;
|
|
||||||
- `minutes`;
|
|
||||||
- `billable`;
|
|
||||||
- optional `sourceSuggestionId`.
|
|
||||||
- Importing Activity suggestions calls:
|
|
||||||
|
|
||||||
```text
|
|
||||||
api.commands.executeFor(
|
|
||||||
'verstak.activity',
|
|
||||||
'verstak.activity.suggestWorklog',
|
|
||||||
{ workspaceRootPath }
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Imported suggestions become non-billable entries by default.
|
|
||||||
- Import is idempotent by `sourceSuggestionId`: importing the same Activity
|
|
||||||
suggestion twice does not create duplicates.
|
|
||||||
- If Activity is unavailable, Journal stays usable for manual entries and shows
|
|
||||||
a status message.
|
|
||||||
|
|
||||||
## Storage
|
|
||||||
|
|
||||||
Workspace entries use settings keys:
|
|
||||||
|
|
||||||
```text
|
|
||||||
worklog:workspace:<encoded workspace root>
|
|
||||||
```
|
|
||||||
|
|
||||||
Global aggregation reads all settings keys with the `worklog:workspace:`
|
|
||||||
prefix.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
`scripts/smoke-journal-plugin.js` must verify:
|
|
||||||
|
|
||||||
- manifest identity, capabilities, optional Activity dependency, and UI
|
|
||||||
contributions;
|
|
||||||
- the Journal view mounts;
|
|
||||||
- manual entry creation stores and renders an entry;
|
|
||||||
- Activity import calls the command runtime and stores suggestions as entries;
|
|
||||||
- importing the same suggestion twice does not duplicate entries;
|
|
||||||
- global Journal aggregates workspace entries.
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
# Persistent Search Index And Runtime Provider Hosting Design
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
Phase 4 requires `verstak.search` to move beyond live recursive scans:
|
|
||||||
|
|
||||||
- keep search as an official plugin, not a core feature;
|
|
||||||
- persist a workspace-scoped search index;
|
|
||||||
- host `searchProviders` contributed by other plugins at runtime;
|
|
||||||
- preserve the local-first, readable vault model;
|
|
||||||
- avoid copying code or architecture from the old Verstak repository.
|
|
||||||
|
|
||||||
Current implementation status:
|
|
||||||
|
|
||||||
- `verstak.search` is a workspace item and contributes `searchProviders`;
|
|
||||||
- it searches while typing by walking files through `api.files.list`;
|
|
||||||
- it reads text-like files through `api.files.readText`;
|
|
||||||
- it opens file results through `api.workbench.openResource`;
|
|
||||||
- the contribution registry already exposes `searchProviders`;
|
|
||||||
- `api.commands.executeFor(pluginId, commandId, args)` already hosts frontend
|
|
||||||
provider handlers for other contribution types;
|
|
||||||
- desktop backend already has plugin-scoped JSON data methods
|
|
||||||
`ReadPluginDataJSON` and `WritePluginDataJSON`, but the frontend plugin API
|
|
||||||
currently exposes only `settings`.
|
|
||||||
|
|
||||||
UX direction:
|
|
||||||
|
|
||||||
- the primary search entry point belongs in the workspace header next to the
|
|
||||||
workspace title;
|
|
||||||
- `verstak.search` remains the owner of indexing and provider hosting;
|
|
||||||
- a standalone Search workspace item may stay as an expanded results surface,
|
|
||||||
but it is not the primary entry point.
|
|
||||||
|
|
||||||
No visual companion is needed for this design because the decision is about
|
|
||||||
runtime contracts and data flow, not layout.
|
|
||||||
|
|
||||||
## Assumptions
|
|
||||||
|
|
||||||
- The next implementation should be a small reversible platform step.
|
|
||||||
- Search remains replaceable plugin functionality; core should expose generic
|
|
||||||
storage and contribution execution only.
|
|
||||||
- `searchProviders[].handler` is treated as a command id. A provider plugin
|
|
||||||
must declare that command in `contributes.commands` and register its frontend
|
|
||||||
handler with `api.commands.register`.
|
|
||||||
- The initial persistent index is JSON-backed plugin data, not SQLite FTS and
|
|
||||||
not a background sidecar.
|
|
||||||
- The index is an optimization and discovery layer. User files remain the
|
|
||||||
source of truth.
|
|
||||||
|
|
||||||
## Alternatives Considered
|
|
||||||
|
|
||||||
### Recommended: plugin-owned index plus command-backed provider hosting
|
|
||||||
|
|
||||||
`verstak.search` owns its local index in plugin storage, uses public Files API
|
|
||||||
and events to keep it fresh, and fans out to contributed providers with
|
|
||||||
`api.commands.executeFor`.
|
|
||||||
|
|
||||||
Trade-offs:
|
|
||||||
|
|
||||||
- smallest core/runtime change;
|
|
||||||
- matches existing Files/Notes contribution execution pattern;
|
|
||||||
- keeps user-facing search outside core;
|
|
||||||
- JSON index is simpler than full text search but sufficient for the current
|
|
||||||
roadmap item.
|
|
||||||
|
|
||||||
### Core search service
|
|
||||||
|
|
||||||
Desktop core would own indexing and execute provider searches.
|
|
||||||
|
|
||||||
Trade-offs:
|
|
||||||
|
|
||||||
- easier to centralize later ranking and indexing;
|
|
||||||
- violates the current direction that search is a plugin-level user feature;
|
|
||||||
- makes core understand official search semantics too early.
|
|
||||||
|
|
||||||
### Sidecar or SQLite FTS indexer now
|
|
||||||
|
|
||||||
Introduce a dedicated indexer process or SQLite FTS schema immediately.
|
|
||||||
|
|
||||||
Trade-offs:
|
|
||||||
|
|
||||||
- better long-term scalability;
|
|
||||||
- too large for the current milestone;
|
|
||||||
- adds migration, lifecycle, and sync/cache policy decisions before the basic
|
|
||||||
provider runtime contract is proven.
|
|
||||||
|
|
||||||
## Chosen Design
|
|
||||||
|
|
||||||
Use the recommended approach.
|
|
||||||
|
|
||||||
`verstak.search` becomes both:
|
|
||||||
|
|
||||||
- the workspace search runtime and expanded results UI;
|
|
||||||
- the runtime host for all enabled `searchProviders`.
|
|
||||||
|
|
||||||
The shell may render the compact input in the workspace header, but it should
|
|
||||||
call into the Search plugin/runtime contract rather than implement search
|
|
||||||
semantics in core.
|
|
||||||
|
|
||||||
The desktop and SDK expose a generic frontend storage surface:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
api.storage.data.read(name: string): Promise<Record<string, unknown>>
|
|
||||||
api.storage.data.write(name: string, data: Record<string, unknown>): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
This maps to existing plugin-scoped backend methods. It requires
|
|
||||||
`storage.namespace`, follows current plugin ownership rules, and does not let a
|
|
||||||
plugin read another plugin's namespace.
|
|
||||||
|
|
||||||
The Search plugin stores its persistent index as plugin data named
|
|
||||||
`search-index`. Settings remain for user preferences only.
|
|
||||||
|
|
||||||
## Provider Runtime Contract
|
|
||||||
|
|
||||||
`searchProviders` keep their current manifest shape:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "verstak.search.vault-text",
|
|
||||||
"label": "Vault Text Search",
|
|
||||||
"handler": "verstak.search.searchVaultText"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `handler` value must name a command declared by the same plugin:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"contributes": {
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
"id": "verstak.search.searchVaultText",
|
|
||||||
"title": "Search Vault Text",
|
|
||||||
"handler": "searchVaultText"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"searchProviders": [
|
|
||||||
{
|
|
||||||
"id": "verstak.search.vault-text",
|
|
||||||
"label": "Vault Text Search",
|
|
||||||
"handler": "verstak.search.searchVaultText"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The provider registers the command handler at mount time:
|
|
||||||
|
|
||||||
```js
|
|
||||||
api.commands.register('verstak.search.searchVaultText', searchVaultText)
|
|
||||||
```
|
|
||||||
|
|
||||||
Runtime availability:
|
|
||||||
|
|
||||||
- this milestone does not auto-start unloaded frontend bundles or sidecars;
|
|
||||||
- a provider is executable only after its command handler is registered in the
|
|
||||||
current frontend runtime;
|
|
||||||
- declared but unregistered providers are skipped and reported as unavailable;
|
|
||||||
- later sidecar/background activation can extend this without changing the
|
|
||||||
provider manifest shape.
|
|
||||||
|
|
||||||
Provider input:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
source: 'search',
|
|
||||||
providerId: string,
|
|
||||||
query: string,
|
|
||||||
workspaceRootPath: string,
|
|
||||||
limit: number
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Provider output:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
results: SearchResult[]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`SearchResult`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
id?: string,
|
|
||||||
path?: string,
|
|
||||||
title?: string,
|
|
||||||
snippet?: string,
|
|
||||||
matchType?: string,
|
|
||||||
providerId?: string,
|
|
||||||
providerLabel?: string,
|
|
||||||
type?: 'file' | 'folder' | 'activity' | 'worklog' | string,
|
|
||||||
openable?: boolean,
|
|
||||||
line?: number,
|
|
||||||
score?: number,
|
|
||||||
resource?: {
|
|
||||||
kind: 'vault-file',
|
|
||||||
path: string,
|
|
||||||
mode?: 'view' | 'edit'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The Search host normalizes missing optional fields. Invalid provider responses
|
|
||||||
are ignored with a visible status warning; they do not fail the whole search.
|
|
||||||
|
|
||||||
## Persistent Index Shape
|
|
||||||
|
|
||||||
The first index version is intentionally small:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"workspaceRootPath": "Project",
|
|
||||||
"builtAt": "2026-06-29T00:00:00Z",
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"path": "Project/Docs/case.md",
|
|
||||||
"name": "case.md",
|
|
||||||
"type": "file",
|
|
||||||
"extension": "md",
|
|
||||||
"size": 1234,
|
|
||||||
"modifiedAt": "2026-06-29T00:00:00Z",
|
|
||||||
"text": "short normalized searchable text or snippet"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
|
|
||||||
- index only the current workspace root;
|
|
||||||
- store vault-relative slash paths;
|
|
||||||
- index folders and regular files;
|
|
||||||
- read content only for text-like files already handled by current Search;
|
|
||||||
- rely on the host `readText` limit for large text files;
|
|
||||||
- store short normalized text, not full arbitrary binary data;
|
|
||||||
- rebuild when version or workspace root differs.
|
|
||||||
|
|
||||||
## Index Lifecycle
|
|
||||||
|
|
||||||
On mount:
|
|
||||||
|
|
||||||
1. Read `api.storage.data.read('search-index')`.
|
|
||||||
2. If version/root matches, use it immediately.
|
|
||||||
3. If missing or stale, build an index from `api.files.list` and text reads.
|
|
||||||
4. Write the built index with `api.storage.data.write`.
|
|
||||||
5. Subscribe to `file.changed`.
|
|
||||||
|
|
||||||
On `file.changed`:
|
|
||||||
|
|
||||||
- if event path is outside the workspace, ignore it;
|
|
||||||
- for create/update, refresh that path's metadata/content if readable;
|
|
||||||
- for delete/trash, remove the path and descendants;
|
|
||||||
- for move, remove `fromPath` when present and refresh the new path;
|
|
||||||
- write the updated index after the change is applied.
|
|
||||||
|
|
||||||
If an incremental update fails, mark the index stale in UI and continue serving
|
|
||||||
the last usable index until rebuild succeeds.
|
|
||||||
|
|
||||||
## Search Flow
|
|
||||||
|
|
||||||
For a query shorter than two characters, return no results.
|
|
||||||
|
|
||||||
For a valid query:
|
|
||||||
|
|
||||||
1. Query local persistent index for path, name, folder, and text matches.
|
|
||||||
2. List enabled `searchProviders` through `api.contributions.list`.
|
|
||||||
3. Call each provider except duplicates that would recurse into the same
|
|
||||||
in-flight command.
|
|
||||||
4. Merge and normalize results.
|
|
||||||
5. Sort by score, then provider order, then path/title.
|
|
||||||
6. Render provider label, match type, path/title, snippet, and open action when
|
|
||||||
the result has a supported resource.
|
|
||||||
|
|
||||||
Provider failures are isolated. The status line reports how many providers
|
|
||||||
failed without hiding successful results.
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
- Missing `storage` API: fall back to live scanning and show a degraded status.
|
|
||||||
- Missing `storage.namespace` permission: Search runs without persistence and
|
|
||||||
reports degraded persistence.
|
|
||||||
- Corrupt index JSON: ignore it, rebuild, and overwrite only after a successful
|
|
||||||
rebuild.
|
|
||||||
- Provider command not declared or not registered: skip that provider and show a
|
|
||||||
warning count.
|
|
||||||
- File read errors: skip that file, matching the current Search behavior.
|
|
||||||
- Event subscription errors: keep search usable, mark index refresh as manual.
|
|
||||||
|
|
||||||
## Testing And Verification
|
|
||||||
|
|
||||||
Expected test coverage:
|
|
||||||
|
|
||||||
- SDK types include `api.storage.data.read/write` and search result/provider
|
|
||||||
contracts.
|
|
||||||
- SDK mock API stores plugin data separately from settings.
|
|
||||||
- Desktop frontend bridge exposes `api.storage.data.read/write`.
|
|
||||||
- Desktop bridge smoke test verifies plugin data round-trip through Wails mock.
|
|
||||||
- Search plugin smoke test verifies:
|
|
||||||
- persisted index is read before scanning;
|
|
||||||
- missing/stale index triggers build and write;
|
|
||||||
- query uses persisted entries;
|
|
||||||
- `file.changed` updates or removes entries;
|
|
||||||
- provider fan-out uses `contributions.list('searchProviders')` and
|
|
||||||
`commands.executeFor`;
|
|
||||||
- provider failure does not hide local results.
|
|
||||||
- Roadmap and official plugin docs are updated after implementation.
|
|
||||||
|
|
||||||
Manual smoke after implementation:
|
|
||||||
|
|
||||||
- start desktop frontend/dev flow used by the repository;
|
|
||||||
- open Search workspace item;
|
|
||||||
- search a known text file;
|
|
||||||
- reload/remount Search and confirm results come from stored index;
|
|
||||||
- enable a test provider and confirm its results appear with provider label.
|
|
||||||
|
|
||||||
## Out Of Scope
|
|
||||||
|
|
||||||
- SQLite FTS;
|
|
||||||
- typo/layout tolerant search;
|
|
||||||
- binary OCR or PDF extraction;
|
|
||||||
- background sidecar indexing;
|
|
||||||
- cross-workspace global search;
|
|
||||||
- sync policy for index cache;
|
|
||||||
- journal/worklog/activity reconstruction implementation.
|
|
||||||
|
|
||||||
Those remain later roadmap items.
|
|
||||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 62 KiB |