Документация: полная ревизия — удалены устаревшие планы и спеки, актуализированы архитектурные документы, добавлен EN README
19
00_README.md
|
|
@ -1,12 +1,12 @@
|
|||
# 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.
|
||||
- [03_Repositories.md](03_Repositories.md) - разбиение на репозитории и назначение каждого.
|
||||
- [04_Plugin_System.md](04_Plugin_System.md) - динамические плагины, manifest, lifecycle, capabilities, settings, permissions.
|
||||
- [05_Official_Plugins.md](05_Official_Plugins.md) - состав официальных плагинов и их зависимости через capabilities.
|
||||
- [06_Migration_Strategy.md](06_Migration_Strategy.md) - как развивать Verstak v2 как отдельную платформу без временных мостов к первой версии.
|
||||
- [07_Full_Implementation_Roadmap.md](07_Full_Implementation_Roadmap.md) - порядок доведения Verstak2 до полной standalone-реализации.
|
||||
- [AGENTS.md](AGENTS.md) - инструкция для coding agents, чтобы они не возвращали проект к монолиту.
|
||||
- [05_Official_Plugins.md](05_Official_Plugins.md) - состав официальных плагинов, их возможности и текущий статус.
|
||||
- [06_Migration_Strategy.md](06_Migration_Strategy.md) - стратегия развития платформы и definition of done.
|
||||
- [07_Full_Implementation_Roadmap.md](07_Full_Implementation_Roadmap.md) - полная дорожная карта реализации с текущими статусами фаз.
|
||||
|
||||
Детальная документация по runtime:
|
||||
|
||||
- [Plugin Runtime](../verstak-desktop/docs/PLUGIN_RUNTIME.md) — подробный reference по plugin lifecycle, API, contribution points.
|
||||
|
||||
## Главный архитектурный инвариант
|
||||
|
||||
Core не знает о конкретных функциях вроде "заметки", "файловый менеджер" или "markdown editor". Core знает о:
|
||||
Core не знает о конкретных функциях вроде «заметки», «файловый менеджер» или «markdown editor». Core знает о:
|
||||
|
||||
- vault;
|
||||
- plugin runtime;
|
||||
|
|
@ -33,4 +36,4 @@ Core не знает о конкретных функциях вроде "зам
|
|||
- storage API;
|
||||
- UI shell.
|
||||
|
||||
Все рабочие инструменты поставляются плагинами. Даже официальные плагины должны жить по тем же правилам, что и будущие сторонние.
|
||||
Все рабочие инструменты поставляются плагинами. Даже официальные плагины живут по тем же правилам, что и будущие сторонние.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,28 @@
|
|||
|
||||
## 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`
|
||||
|
||||
|
|
@ -24,7 +41,11 @@ Project board можно вести на уровне organization: roadmap, iss
|
|||
- storage API;
|
||||
- event bus;
|
||||
- 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`
|
||||
|
||||
Монорепозиторий официальных плагинов.
|
||||
Монорепозиторий официальных плагинов:
|
||||
|
||||
Содержит:
|
||||
|
||||
```text
|
||||
```
|
||||
plugins/
|
||||
files/
|
||||
notes/
|
||||
markdown-editor/
|
||||
file-preview/
|
||||
activity/
|
||||
journal/
|
||||
browser-inbox/
|
||||
search/
|
||||
secrets/
|
||||
templates/
|
||||
packages/
|
||||
plugin-sdk/
|
||||
shared-ui/
|
||||
test-harness/
|
||||
platform-test/ — тестовый плагин для проверки runtime
|
||||
files/ — файловый менеджер
|
||||
notes/ — заметки
|
||||
default-editor/ — редактор текста/markdown
|
||||
file-preview/ — предпросмотр изображений
|
||||
activity/ — активность
|
||||
journal/ — журнал работ
|
||||
browser-inbox/ — браузерный inbox
|
||||
search/ — поиск
|
||||
secrets/ — хранилище секретов
|
||||
todo/ — задачи
|
||||
trash/ — корзина
|
||||
sync/ — синхронизация
|
||||
```
|
||||
|
||||
Официальные плагины должны устанавливаться и загружаться так же, как сторонние. Нельзя делать для них скрытый privileged path, кроме явно описанных platform permissions.
|
||||
Все плагины используют префикс `verstak.*` в id. Официальные плагины загружаются через тот же plugin runtime, что и сторонние.
|
||||
|
||||
### `verstak-sdk`
|
||||
|
||||
Общие контракты для разработки плагинов.
|
||||
Общие контракты для разработки плагинов:
|
||||
|
||||
Содержит:
|
||||
|
||||
- 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 начнет стабилизироваться.
|
||||
- JSON Schema для manifest, capabilities, contributions, permissions, sync;
|
||||
- TypeScript типы для plugin API;
|
||||
- тестовые утилиты.
|
||||
|
||||
### `verstak-sync-server`
|
||||
|
||||
Отдельный сервер синхронизации.
|
||||
|
||||
Содержит:
|
||||
Отдельный сервер синхронизации:
|
||||
|
||||
- HTTP API;
|
||||
- auth/pairing;
|
||||
- device registry;
|
||||
- vault operation log;
|
||||
- blob upload/download;
|
||||
- conflict handling;
|
||||
- retention/deleted file policy;
|
||||
- server migrations;
|
||||
- embedded web console;
|
||||
- deployment docs.
|
||||
|
||||
Sync server не должен импортировать desktop UI или official plugins.
|
||||
|
||||
### `verstak-browser-extension`
|
||||
|
||||
Расширение браузера.
|
||||
Расширение браузера (Firefox/Chromium):
|
||||
|
||||
Содержит:
|
||||
|
||||
- Firefox/Chromium extension;
|
||||
- local pairing with Verstak;
|
||||
- page capture;
|
||||
- selected text capture;
|
||||
- link sending;
|
||||
- pending queue if desktop is offline;
|
||||
- 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`.
|
||||
- page capture, selection, link, file;
|
||||
- pending queue;
|
||||
- domain bindings;
|
||||
- passive domain activity (opt-in).
|
||||
|
||||
### `verstak-docs`
|
||||
|
||||
Публичная документация:
|
||||
Платформенная и архитектурная документация:
|
||||
|
||||
- user guide;
|
||||
- developer guide;
|
||||
- plugin authoring guide;
|
||||
- sync setup;
|
||||
- security model;
|
||||
- migration guides.
|
||||
- product vision;
|
||||
- platform architecture;
|
||||
- plugin system overview;
|
||||
- official plugins reference;
|
||||
- development strategy;
|
||||
- implementation roadmap.
|
||||
|
||||
Может быть отдельным репозиторием позже. Сейчас допустимо держать архитектурные документы рядом с `verstak-desktop`.
|
||||
## 3. Версионирование
|
||||
|
||||
## 4. Что не дробить слишком рано
|
||||
|
||||
Не стоит сразу создавать отдельный репозиторий на каждый официальный плагин. Это увеличит накладные расходы и усложнит синхронные изменения SDK/API.
|
||||
|
||||
Лучше:
|
||||
|
||||
```text
|
||||
verstak-official-plugins - один repo для официальных плагинов
|
||||
```
|
||||
|
||||
А отдельные repo оставить для сторонних плагинов или крупных независимых модулей.
|
||||
|
||||
## 5. Версионирование
|
||||
|
||||
Версии должны существовать на трех уровнях:
|
||||
Версии существуют на трёх уровнях:
|
||||
|
||||
- app version: версия `verstak-desktop`;
|
||||
- platform apiVersion: версия API плагинов;
|
||||
|
|
@ -164,8 +132,9 @@ verstak-official-plugins - один repo для официальных плаг
|
|||
```json
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"apiVersion": "1"
|
||||
"apiVersion": "0.1.0"
|
||||
}
|
||||
```
|
||||
|
||||
Core может загрузить плагин только при совместимости `apiVersion`.
|
||||
Совместимые компоненты следует собирать из одной релизной линии.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Verstak Plugin System
|
||||
|
||||
> Подробный reference по реализации: [Plugin Runtime](../verstak-desktop/docs/PLUGIN_RUNTIME.md).
|
||||
|
||||
## 1. Цель
|
||||
|
||||
Плагины превращают Верстак из монолитного приложения в платформу. Любая пользовательская функция должна быть реализуема как плагин:
|
||||
|
|
@ -19,11 +21,12 @@
|
|||
## 2. Структура плагина
|
||||
|
||||
```text
|
||||
official.notes/
|
||||
verstak.notes/
|
||||
plugin.json
|
||||
frontend/
|
||||
index.js
|
||||
style.css
|
||||
dist/
|
||||
index.js
|
||||
style.css
|
||||
backend/
|
||||
plugin-linux-amd64
|
||||
plugin-windows-amd64.exe
|
||||
|
|
@ -37,14 +40,16 @@ official.notes/
|
|||
|
||||
## 3. Manifest
|
||||
|
||||
Manifest — файл `plugin.json` в корне директории плагина.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "official.notes",
|
||||
"id": "verstak.notes",
|
||||
"name": "Notes",
|
||||
"version": "0.1.0",
|
||||
"apiVersion": "1",
|
||||
"description": "Markdown notes inside Verstak cases.",
|
||||
"apiVersion": "0.1.0",
|
||||
"description": "Markdown notes inside Verstak workspaces.",
|
||||
"source": "official",
|
||||
"localization": {
|
||||
"defaultLocale": "en",
|
||||
|
|
@ -66,16 +71,17 @@ official.notes/
|
|||
"search.provider"
|
||||
],
|
||||
"permissions": [
|
||||
"vault.read",
|
||||
"vault.write",
|
||||
"files.read",
|
||||
"files.write",
|
||||
"storage.namespace",
|
||||
"ui.register",
|
||||
"events.publish",
|
||||
"events.subscribe"
|
||||
"events.subscribe",
|
||||
"workbench.open"
|
||||
],
|
||||
"frontend": {
|
||||
"entry": "frontend/index.js",
|
||||
"style": "frontend/style.css"
|
||||
"entry": "frontend/dist/index.js",
|
||||
"style": "frontend/dist/style.css"
|
||||
},
|
||||
"backend": {
|
||||
"type": "sidecar",
|
||||
|
|
@ -129,7 +135,7 @@ Desktop поддерживает `system`, `en` и `ru`. В режиме `system
|
|||
|
||||
```json
|
||||
{
|
||||
"requires": ["official.markdown-editor"]
|
||||
"requires": ["verstak.default-editor"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -152,12 +158,11 @@ Desktop поддерживает `system`, `en` и `ru`. В режиме `system
|
|||
Пример:
|
||||
|
||||
```text
|
||||
official.notes
|
||||
verstak.notes
|
||||
requires:
|
||||
vault.files
|
||||
verstak/core/files/v1
|
||||
optionalRequires:
|
||||
editor.text.markdown
|
||||
preview.markdown
|
||||
search.provider
|
||||
```
|
||||
|
||||
|
|
@ -240,8 +245,8 @@ Contribution должен быть декларативным, где возмо
|
|||
"contributes": {
|
||||
"settingsPanels": [
|
||||
{
|
||||
"id": "official.markdown-editor.general",
|
||||
"title": "Markdown Editor",
|
||||
"id": "verstak.default-editor.general",
|
||||
"title": "Default Editor",
|
||||
"component": "settings.general"
|
||||
}
|
||||
]
|
||||
|
|
@ -254,7 +259,7 @@ Plugin Manager показывает кнопку настроек только
|
|||
Настройки хранятся в namespace плагина:
|
||||
|
||||
```text
|
||||
plugin_settings.official.markdown-editor
|
||||
plugin_settings/<plugin_id>/
|
||||
```
|
||||
|
||||
Плагин не должен читать/писать настройки другого плагина без разрешения.
|
||||
|
|
@ -326,19 +331,19 @@ Uninstall не должен автоматически удалять польз
|
|||
|
||||
## 13. Packaging
|
||||
|
||||
Плагин распространяется как zip/tar package:
|
||||
Плагин распространяется как директория внутри `plugins/` или как архив:
|
||||
|
||||
```text
|
||||
official.notes-0.1.0.vpkg
|
||||
verstak.notes-0.1.0.tar.gz
|
||||
```
|
||||
|
||||
Пакет содержит:
|
||||
|
||||
- `plugin.json`;
|
||||
- frontend bundle;
|
||||
- frontend bundle (`frontend/dist/`);
|
||||
- backend binaries if any;
|
||||
- migrations;
|
||||
- README;
|
||||
- checksums/signature later.
|
||||
|
||||
На первом этапе допустима ручная установка папкой в plugin directory.
|
||||
Подробнее о сборке: [PACKAGING.md](../verstak-official-plugins/docs/PACKAGING.md).
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
# Official Plugins
|
||||
|
||||
Официальные плагины - это базовый набор инструментов Верстака. Они не должны быть скрытыми частями core. Их задача - показать, что платформа действительно работает через capabilities и contribution points.
|
||||
Официальные плагины — базовый набор инструментов Верстака. Они используют тот же
|
||||
plugin runtime, что и сторонние. Их задача — показать, что платформа работает
|
||||
через capabilities и contribution points.
|
||||
|
||||
## 1. `official.files`
|
||||
Все официальные плагины используют префикс `verstak.*` в id.
|
||||
|
||||
## 1. `verstak.files`
|
||||
|
||||
Назначение:
|
||||
|
||||
- дерево/список файлов внутри дела;
|
||||
- добавление файлов;
|
||||
- перемещение/копирование в vault;
|
||||
- открытие системным приложением;
|
||||
- file metadata;
|
||||
- file actions registry consumer.
|
||||
- добавление файлов и папок;
|
||||
- перемещение/переименование;
|
||||
- открытие через Workbench (openProvider routing);
|
||||
- trash/restore (через `verstak.trash`);
|
||||
- file metadata.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
workspace.files
|
||||
vault.files
|
||||
entity.file
|
||||
|
|
@ -24,72 +28,64 @@ file.browser
|
|||
|
||||
Optional requires:
|
||||
|
||||
```text
|
||||
```
|
||||
editor.text
|
||||
viewer.file
|
||||
preview.file
|
||||
search.provider
|
||||
```
|
||||
|
||||
Поведение:
|
||||
Текущий статус: реализован. Открывает файлы через `api.workbench.openResource()`,
|
||||
не импортирует редактор напрямую. Корзина выделена в отдельный плагин `verstak.trash`.
|
||||
|
||||
- если есть подходящий editor capability, показывает "Edit";
|
||||
- если есть viewer/preview capability, показывает "Preview";
|
||||
- если подходящего provider/capability нет, показывает понятное no-provider
|
||||
состояние; открытие внешним приложением остается отдельной отложенной
|
||||
возможностью.
|
||||
|
||||
## 2. `official.notes`
|
||||
## 2. `verstak.notes`
|
||||
|
||||
Назначение:
|
||||
|
||||
- markdown notes as a UI-level context over ordinary Markdown files;
|
||||
- canonical `Notes/` folder inside case/project;
|
||||
- `Overview.md` is allowed only as an ordinary Markdown filename, not as a
|
||||
special UI entity;
|
||||
- markdown notes как UI-слой над обычными `.md` файлами;
|
||||
- каноническая папка `Notes/` внутри дела;
|
||||
- `Overview.md` — обычное markdown-имя файла, не специальная UI-сущность;
|
||||
- note metadata;
|
||||
- note links.
|
||||
- rename с синхронизацией title/filename;
|
||||
- conflict dialog при конфликте имён.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
workspace.notes
|
||||
note.registry
|
||||
```
|
||||
|
||||
Requires:
|
||||
|
||||
```text
|
||||
vault.files
|
||||
```
|
||||
verstak/core/files/v1
|
||||
```
|
||||
|
||||
Optional requires:
|
||||
|
||||
```text
|
||||
```
|
||||
editor.text.markdown
|
||||
preview.markdown
|
||||
search.provider
|
||||
```
|
||||
|
||||
Важное правило:
|
||||
Текущий статус: реализован. Title и filename синхронизированы. При конфликте
|
||||
имени показывается conflict dialog.
|
||||
|
||||
- title и filename должны оставаться синхронизированными;
|
||||
- filename - человекочитаемая проекция title;
|
||||
- при конфликте имени не добавлять `_2` молча, а показывать понятный conflict dialog.
|
||||
|
||||
## 3. `official.markdown-editor`
|
||||
## 3. `verstak.default-editor`
|
||||
|
||||
Назначение:
|
||||
|
||||
- редактирование markdown/text;
|
||||
- редактирование текста и markdown;
|
||||
- toolbar;
|
||||
- save flow;
|
||||
- dirty state;
|
||||
- keyboard shortcuts.
|
||||
- markdown preview (встроен, не отдельный плагин);
|
||||
- keyboard shortcuts (Ctrl+S).
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
editor.text
|
||||
editor.text.markdown
|
||||
editor.note.markdown
|
||||
|
|
@ -97,52 +93,47 @@ editor.note.markdown
|
|||
|
||||
Optional requires:
|
||||
|
||||
```text
|
||||
```
|
||||
link.resolver
|
||||
```
|
||||
|
||||
Не должен:
|
||||
Текущий статус: реализован как единый плагин с тремя openProviders
|
||||
(`text`, `generic-markdown`, `notes-markdown`). Использует `api.files.readText` /
|
||||
`api.files.writeText`. Markdown preview — часть редактора, без отдельного
|
||||
плагина предпросмотра.
|
||||
|
||||
- сам решать, где хранятся notes;
|
||||
- напрямую зависеть от `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`
|
||||
## 4. `verstak.file-preview`
|
||||
|
||||
Назначение:
|
||||
|
||||
- inline preview for image files through bounded `api.files.readBytes`;
|
||||
- metadata view for previewed files;
|
||||
- `Open External` action through `api.files.openExternal`.
|
||||
- inline preview для изображений через `api.files.readBytes`;
|
||||
- metadata view;
|
||||
- `Open External` через `api.files.openExternal`.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
viewer.file
|
||||
viewer.image
|
||||
preview.file
|
||||
```
|
||||
|
||||
Текущий статус: базовый `verstak.file-preview` renders image files inline via
|
||||
the public Files API and shows file metadata. Text-like files, code, and
|
||||
Markdown stay with editor plugins.
|
||||
Текущий статус: базовый рендеринг изображений через публичный Files API,
|
||||
плюс metadata файла. Текстовые файлы, код и markdown остаются за
|
||||
editor-плагинами.
|
||||
|
||||
## 5. `official.activity`
|
||||
## 5. `verstak.activity`
|
||||
|
||||
Назначение:
|
||||
|
||||
- сбор activity events;
|
||||
- отображение истории;
|
||||
- отображение истории активности;
|
||||
- реконструкция работы;
|
||||
- подсказки для worklog.
|
||||
- worklog suggestions для Journal.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
activity.log
|
||||
activity.provider
|
||||
activity.reconstruction
|
||||
|
|
@ -150,52 +141,33 @@ activity.reconstruction
|
|||
|
||||
Subscribes:
|
||||
|
||||
```text
|
||||
```
|
||||
file.opened
|
||||
file.changed
|
||||
note.saved
|
||||
action.started
|
||||
browser.capture.received
|
||||
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
|
||||
view and a workspace item. Workspace tabs store and display only their own
|
||||
activity stream; the global sidebar view aggregates activity from all workspace
|
||||
streams plus unscoped global activity. It contributes `activityProviders`; the
|
||||
desktop runtime hosts those providers and records subscribed public events into
|
||||
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.
|
||||
Текущий статус: реализован как глобальный sidebar view и workspace item.
|
||||
Workspace tabs хранят свою activity stream; глобальный view агрегирует все
|
||||
workspace streams. Предоставляет `activityProviders`; desktop runtime записывает
|
||||
события в plugin storage даже когда Activity view не смонтирован. Реконструирует
|
||||
worklog suggestions через контракт `verstak.activity.suggestWorklog`.
|
||||
|
||||
## Sync Conflict UX Contract
|
||||
|
||||
`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`
|
||||
## 6. `verstak.journal`
|
||||
|
||||
Назначение:
|
||||
|
||||
- ручной журнал работ;
|
||||
- billable/non-billable;
|
||||
- отчеты по делу/клиенту;
|
||||
- принятие suggested time из activity.
|
||||
- приём suggested time из activity;
|
||||
- создание записи из завершённого todo.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
worklog
|
||||
journal
|
||||
report.worklog
|
||||
|
|
@ -203,31 +175,27 @@ report.worklog
|
|||
|
||||
Optional requires:
|
||||
|
||||
```text
|
||||
```
|
||||
activity.reconstruction
|
||||
```
|
||||
|
||||
Текущий статус: базовый `verstak.journal` implemented as both a global sidebar
|
||||
view and a workspace item. Workspace views store manual worklog entries in the
|
||||
plugin settings namespace, import non-billable entries from
|
||||
`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.
|
||||
Текущий статус: реализован как глобальный sidebar view и workspace item.
|
||||
Хранит записи в plugin settings namespace, импортирует не-billable записи
|
||||
из `verstak.activity.suggestWorklog`, дедуплицирует по Activity suggestion id.
|
||||
|
||||
## 7. `official.browser-inbox`
|
||||
## 7. `verstak.browser-inbox`
|
||||
|
||||
Назначение:
|
||||
|
||||
- прием ссылок, выделенного текста, страниц и snippets из browser extension;
|
||||
- pending queue;
|
||||
- привязка доменов к делам;
|
||||
- создание inbox entries;
|
||||
- превращение inbox entry в note/link/file/activity.
|
||||
- приём страниц, ссылок, выделенного текста и файлов из browser extension;
|
||||
- pending queue с scoping по workspace;
|
||||
- domain bindings;
|
||||
- конвертация inbox entry в note/link/file;
|
||||
- запись конвертаций в Activity.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
capture.browser
|
||||
browser.inbox
|
||||
domain.binding
|
||||
|
|
@ -235,133 +203,150 @@ domain.binding
|
|||
|
||||
Requires:
|
||||
|
||||
```text
|
||||
```
|
||||
network.local
|
||||
```
|
||||
|
||||
Optional requires:
|
||||
|
||||
```text
|
||||
```
|
||||
workspace.notes
|
||||
activity.log
|
||||
search.provider
|
||||
```
|
||||
|
||||
Текущий статус: базовый `verstak.browser-inbox` implemented as both a global
|
||||
sidebar view and a workspace item. Workspace tabs keep their own pending queue;
|
||||
the global sidebar view aggregates queues from all workspaces plus unscoped
|
||||
global captures. The local receiver starts in paired mode: it generates an
|
||||
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.
|
||||
Текущий статус: реализован как глобальный sidebar view и workspace item.
|
||||
Local receiver в paired mode с токеном. Поддерживает конвертацию в note
|
||||
(через публичный Files API), link (`.url` файлы), text file attachments
|
||||
и bounded binary attachments. Конвертации записываются в Activity.
|
||||
|
||||
## 9. `official.search`
|
||||
## 8. `verstak.search`
|
||||
|
||||
Назначение:
|
||||
|
||||
- workspace-scoped search UI;
|
||||
- baseline recursive text search through public Files API;
|
||||
- search provider contribution for platform discovery;
|
||||
- index notes/files/activity/worklog later;
|
||||
- typo/layout tolerant search later.
|
||||
- workspace-scoped search;
|
||||
- поиск по мере ввода;
|
||||
- поиск по именам файлов/папок и содержимому текстовых файлов;
|
||||
- persistent search index;
|
||||
- cross-provider runtime hosting.
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
search
|
||||
search.provider
|
||||
search.indexer
|
||||
```
|
||||
|
||||
Текущий статус: базовый `verstak.search` implemented as a workspace item and
|
||||
`searchProviders` contribution. It searches as the user types, matches vault
|
||||
file/folder names and paths, scans text-like file contents through
|
||||
`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.
|
||||
Текущий статус: реализован как workspace item и `searchProviders` contribution.
|
||||
Ищет по мере ввода, индексирует vault, обновляет индекс по файловым событиям,
|
||||
поддерживает других search providers через runtime.
|
||||
|
||||
Target UX: search should be available from the workspace header next to the
|
||||
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`
|
||||
## 9. `verstak.secrets`
|
||||
|
||||
Назначение:
|
||||
|
||||
- защищенное хранилище клиентских доступов;
|
||||
- SSH/CMS/VPS/database/API secrets;
|
||||
- bridge secret, sync token, device private key, pairing token;
|
||||
- permissions for secret access.
|
||||
- защищённое хранилище доступов;
|
||||
- workspace-scoped secrets;
|
||||
- master password (AES-GCM).
|
||||
|
||||
Provides:
|
||||
|
||||
```text
|
||||
```
|
||||
secret-store
|
||||
secrets.read-ui
|
||||
secrets.write-ui
|
||||
```
|
||||
|
||||
Важное правило:
|
||||
Текущий статус: реализован. Desktop core имеет AES-GCM secret store,
|
||||
разблокируемый один раз за сессию. Плагин показывает global и workspace-scoped
|
||||
секреты, поддерживает редактирование, удаление, копирование markdown secret links.
|
||||
Рендерит `verstak-secret://...` ссылки.
|
||||
|
||||
- секреты не должны храниться как обычный markdown/plain text;
|
||||
- доступ к 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`
|
||||
## 10. `verstak.todo`
|
||||
|
||||
Назначение:
|
||||
|
||||
- шаблоны дел;
|
||||
- client/project/server/device structures;
|
||||
- initial folder/note/action layout.
|
||||
- списки задач внутри дел и глобально;
|
||||
- статусы: open, done, cancelled;
|
||||
- приоритеты, due date, reminders;
|
||||
- создание Journal записи из завершённого todo.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Optional requires:
|
||||
Текущий статус: реализован через built-in templates в desktop core
|
||||
(General, Project, Writing, Admin, Minimal). Модальное окно создания
|
||||
дела показывает описание шаблона и его вкладки.
|
||||
|
||||
```text
|
||||
workspace.notes
|
||||
workspace.files
|
||||
```
|
||||
## 14. `verstak.platform-test`
|
||||
|
||||
## 12. Первый минимальный набор
|
||||
Назначение:
|
||||
|
||||
Для первого платформенного этапа достаточно:
|
||||
- тестовый плагин для отладки runtime;
|
||||
- диагностические панели для проверки API;
|
||||
- не для конечных пользователей.
|
||||
|
||||
- `official.files`;
|
||||
- `official.notes`;
|
||||
- `official.markdown-editor`;
|
||||
- `official.file-preview`;
|
||||
- `official.activity`;
|
||||
- `official.browser-inbox`;
|
||||
|
||||
Но все они должны быть настоящими динамическими плагинами, даже если поставляются вместе с приложением.
|
||||
Текущий статус: используется только при разработке.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
## 1. Цель
|
||||
|
||||
Verstak v2 разрабатывается как отдельное приложение и платформа. Цель -
|
||||
собрать рабочий local-first vault вокруг дел без временных мостов к первой
|
||||
версии:
|
||||
Verstak развивается как local-first платформа с динамическими плагинами:
|
||||
|
||||
- local-first vault;
|
||||
- дела как центр контекста;
|
||||
|
|
@ -12,23 +10,19 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||
- заметки, файлы, журнал, активность, браузерные материалы вокруг дела;
|
||||
- синхронизация и расширение как отдельные части, а не ядро смысла.
|
||||
|
||||
Это не миграция v1 -> v2 и не перенос старого монолита частями. Это развитие
|
||||
самостоятельной платформенной модели.
|
||||
|
||||
## 2. Нельзя делать
|
||||
|
||||
- Нельзя добавлять новые функции в монолитный `App.svelte`.
|
||||
- Нельзя делать official plugins скрытыми compile-time modules.
|
||||
- Нельзя связывать плагины по именам, если нужна capability.
|
||||
- Нельзя делать notes/files/editor обязательными частями core.
|
||||
- Нельзя добавлять временные compatibility bridges к первой версии.
|
||||
- Нельзя менять v2 vault layout без явного плана изменения формата.
|
||||
- Нельзя менять vault layout без явного плана изменения формата.
|
||||
- Нельзя хранить секреты как обычные заметки или plain text.
|
||||
- Нельзя молча менять title/filename note при конфликте.
|
||||
|
||||
## 3. Этап 1 - Platform Skeleton
|
||||
|
||||
Сделать в `verstak-desktop`:
|
||||
Сделано в `verstak-desktop`:
|
||||
|
||||
- plugin manifest schema;
|
||||
- plugin discovery from plugin directories;
|
||||
|
|
@ -41,17 +35,9 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||
- basic event bus;
|
||||
- diagnostics panel.
|
||||
|
||||
Проверки:
|
||||
|
||||
- приложение запускается без плагинов;
|
||||
- приложение показывает пустой Plugin Manager;
|
||||
- тестовый плагин появляется в списке;
|
||||
- enable/disable работает;
|
||||
- failed plugin не роняет приложение.
|
||||
|
||||
## 4. Этап 2 - Frontend Plugin Host
|
||||
|
||||
Сделать:
|
||||
Сделано:
|
||||
|
||||
- загрузку frontend bundle;
|
||||
- `VerstakPluginAPI`;
|
||||
|
|
@ -59,16 +45,11 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||
- plugin UI error boundary;
|
||||
- settings panel invocation from Plugin Manager.
|
||||
|
||||
Проверки:
|
||||
|
||||
- тестовый плагин регистрирует view;
|
||||
- тестовый плагин регистрирует settings panel;
|
||||
- выключение плагина убирает view/settings;
|
||||
- ошибка в plugin UI не роняет shell.
|
||||
|
||||
## 5. Этап 3 - Backend Sidecar Host
|
||||
|
||||
Сделать:
|
||||
Отложено до отдельного milestone.
|
||||
|
||||
План:
|
||||
|
||||
- sidecar launch protocol;
|
||||
- local RPC;
|
||||
|
|
@ -76,57 +57,63 @@ Verstak v2 разрабатывается как отдельное прилож
|
|||
- sidecar shutdown/restart;
|
||||
- logs/diagnostics.
|
||||
|
||||
Проверки:
|
||||
|
||||
- sidecar отвечает health check;
|
||||
- sidecar не получает API без permissions;
|
||||
- падение sidecar переводит плагин в failed;
|
||||
- disable останавливает sidecar.
|
||||
|
||||
## 6. Этап 4 - Official Plugin Development
|
||||
|
||||
Развивать official plugins по одному, через общий plugin runtime:
|
||||
Развиваются по одному, через общий plugin runtime:
|
||||
|
||||
1. `official.markdown-editor`;
|
||||
2. `official.files`;
|
||||
3. `official.notes`;
|
||||
4. `official.file-preview`;
|
||||
5. `official.activity`;
|
||||
6. `official.browser-inbox`.
|
||||
1. `verstak.default-editor` ✅
|
||||
2. `verstak.files` ✅
|
||||
3. `verstak.notes` ✅
|
||||
4. `verstak.file-preview` ✅ (базовый)
|
||||
5. `verstak.activity` ✅
|
||||
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;
|
||||
- проверить запуск;
|
||||
- проверить Plugin Manager;
|
||||
- проверить enable/disable;
|
||||
- проверить degraded mode при отключении optional plugin;
|
||||
- проверить, что v2 vault остается читаемым и не получает скрытых
|
||||
plugin-specific truth-слоев для пользовательских документов;
|
||||
- проверить, что vault остается читаемым;
|
||||
- проверить, что UI contributions исчезают при disable.
|
||||
|
||||
## 7. Этап 5 - Repository Split
|
||||
|
||||
Когда plugin runtime работает:
|
||||
Выполнено:
|
||||
|
||||
- оставить core и shell в `verstak-desktop`;
|
||||
- вынести official plugins в `verstak-official-plugins`;
|
||||
- вынести sync server в `verstak-sync-server`;
|
||||
- вынести browser extension в `verstak-browser-extension`;
|
||||
- выделить `verstak-sdk` после стабилизации API.
|
||||
- core и shell в `verstak-desktop`;
|
||||
- official plugins в `verstak-official-plugins`;
|
||||
- sync server в `verstak-sync-server`;
|
||||
- browser extension в `verstak-browser-extension`;
|
||||
- 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 работают как плагины;
|
||||
- Plugin Manager умеет включать/выключать плагины;
|
||||
- плагин может иметь свое settings окно;
|
||||
- capability registry управляет видимостью actions;
|
||||
- отсутствие optional capability не считается ошибкой;
|
||||
- v2 vault layout остается стабильным и читаемым без compatibility bridges к v1;
|
||||
- документация соответствует реализации.
|
||||
Оставшиеся крупные задачи:
|
||||
|
||||
- [ ] Sidecar/sandbox изоляция;
|
||||
- [ ] Production-grade packaging и автообновление;
|
||||
- [ ] Operation-log retention (sync server);
|
||||
- [ ] Синхронизация Secrets, plugin settings, Todo, Journal, Activity, Browser Inbox;
|
||||
- [ ] UX-полировка (Today flow, mobile layout, search в workspace header).
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
# Verstak2 Full Implementation Roadmap
|
||||
# Verstak Implementation Roadmap
|
||||
|
||||
## 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;
|
||||
- official plugins provide the user-facing tools;
|
||||
- vault data stays human-readable and local-first;
|
||||
- sync UI/settings, browser capture, activity, journal, preview, search, and
|
||||
secrets are plugin/runtime extensions; sync correctness (scanner, operation
|
||||
log, reconciliation, and workspace identity) stays in Desktop core;
|
||||
- no compatibility bridge to the first Verstak version is introduced.
|
||||
log, reconciliation, and workspace identity) stays in Desktop core.
|
||||
|
||||
## 2. Non-negotiable Constraints
|
||||
|
||||
- No v1 migration bridge or temporary compatibility layer.
|
||||
- No notes/files/editor/activity/journal/browser inbox feature may become a
|
||||
required core module.
|
||||
- User documents remain ordinary vault files unless a feature explicitly needs
|
||||
protected storage, such as secrets.
|
||||
- `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.
|
||||
- Every significant step must be verified, committed, and pushed separately.
|
||||
|
||||
|
|
@ -33,47 +31,39 @@ Implemented:
|
|||
- bundled frontend plugin host and `VerstakPluginAPI`;
|
||||
- Command Palette UI host for `commands` contributions;
|
||||
- Status Bar UI host for `statusBarItems`, vault status, and settings menu;
|
||||
- workspace top-level folder model and workspace item host;
|
||||
- Files Core API with safe path policy, durable snapshot scanning, and core sync
|
||||
operation recording;
|
||||
- public `files.openExternal` / `files.showInFolder` API and Files plugin usage;
|
||||
- workspace top-level folder model, workspace item host, workspace templates;
|
||||
- Files Core API with safe path policy, durable snapshot scanning, atomic
|
||||
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;
|
||||
- official Files plugin, Notes plugin, Markdown Editor plugin, Search plugin,
|
||||
Sync plugin, and platform-test plugin;
|
||||
- browser inbox local receiver and minimal official Browser Inbox plugin;
|
||||
- sync server with device/user auth and operation push/pull;
|
||||
- SDK manifest/types/schema coverage for current plugin APIs;
|
||||
- all official plugins: Files, Notes, Default Editor, File Preview, Activity,
|
||||
Journal, Browser Inbox, Search, Secrets, Todo, Trash, Sync, Templates;
|
||||
- browser inbox local receiver with paired mode, domain bindings, and
|
||||
create-note/link/file conversion;
|
||||
- sync server with device/user auth, operation push/pull, Blob transport,
|
||||
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.
|
||||
- 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.
|
||||
|
||||
Known remaining gaps:
|
||||
|
||||
- `fileActions`, `noteActions`, and `contextMenuEntries` are exposed through the
|
||||
desktop contribution summary and hosted by the official Files/Notes surfaces.
|
||||
- Sidecar host is not implemented.
|
||||
- Files/Notes are usable but not complete: chunked streaming/large-file import,
|
||||
richer conflict UX, and remaining Notes polish are still incomplete.
|
||||
- Activity, Journal, Browser Inbox conversion workflows, indexed Search, and
|
||||
Secrets now have baseline plugin implementations and public API contracts.
|
||||
Their remaining work is product UX depth: richer Today aggregation,
|
||||
actionable Activity to Journal review flows, capture-from-clipboard/manual
|
||||
capture, production-grade reporting, and final polish.
|
||||
- Templates plugin is not implemented yet.
|
||||
- File/image preview exists as a basic provider with bounded inline image
|
||||
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.
|
||||
- Browser extension UI localization is not yet migrated to the shared
|
||||
multilingual product policy.
|
||||
- Sidecar host is not implemented (bundled plugins run in shared JS context).
|
||||
- Chunked streaming/large-file import is deferred; bounded text (2 MB) and
|
||||
byte (8 MB) APIs are the current limits.
|
||||
- UX polish is ongoing: Today flow as work-resume surface, Activity-to-Journal
|
||||
review, mobile/responsive layout, search in workspace header.
|
||||
- Production-grade packaging, auto-update, and release workflow is partial:
|
||||
build scripts exist, but not a polished update channel.
|
||||
- Sync operation-log retention/compaction is intentionally deferred.
|
||||
- Secrets, plugin settings, Todo, Journal, Activity, and Browser Inbox are not
|
||||
synchronized yet.
|
||||
- Browser extension chunked large-file capture is future work.
|
||||
- Automatic conflict resolution is intentionally absent.
|
||||
|
||||
## 4. Implementation Phases
|
||||
|
||||
|
|
@ -93,11 +83,7 @@ Tasks:
|
|||
practical;
|
||||
- [x] document each public API in SDK schemas and desktop runtime docs.
|
||||
|
||||
Verification:
|
||||
|
||||
- focused e2e/smoke tests for command palette, status bar, action surfaces, and
|
||||
contribution lifecycle;
|
||||
- `go test ./...`, frontend build/e2e, official plugin checks, SDK tests.
|
||||
Status: done.
|
||||
|
||||
### Phase 2 - Files And Notes Product Surface
|
||||
|
||||
|
|
@ -111,14 +97,11 @@ Tasks:
|
|||
- [x] add Notes delete/trash through Files API with confirmation;
|
||||
- [x] add Files restore metadata view;
|
||||
- [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 safe binary read/streaming contract only after text workflows are stable.
|
||||
- [x] add safe binary read/streaming contract.
|
||||
|
||||
Verification:
|
||||
|
||||
- plugin smoke tests and desktop e2e for create/open/rename/trash/reload;
|
||||
- no `.verstak/notes`, no special `Overview.md`, no direct backend bypasses.
|
||||
Status: done.
|
||||
|
||||
### Phase 3 - Sync Hardening
|
||||
|
||||
|
|
@ -131,74 +114,32 @@ Verified in the current implementation:
|
|||
- [x] add server/device revocation checks for sync auth paths;
|
||||
- [x] persist and display sync errors in Sync plugin;
|
||||
- [x] add retry/backoff for sync client operations;
|
||||
- [x] persist an atomic core snapshot under `.verstak/sync/`; scan on vault
|
||||
open, before manual sync, and after watcher debounce; exclude internal paths,
|
||||
trash, temporary files, and symlinks;
|
||||
- [x] reconcile initial vaults safely: pull before local bootstrap, never turn
|
||||
an empty initial snapshot into deletes, and stop on an incompatible
|
||||
local/remote path conflict;
|
||||
- [x] apply pulled operations strictly by `server_sequence`, stop at the first
|
||||
failed operation, retain cursor/retry state across restart, and avoid watcher
|
||||
echo after a successful remote apply;
|
||||
- [x] sync workspace/Deal create, rename, trash, and restore through a
|
||||
core-owned entity carrying the durable `workspaceId` rather than exposing the
|
||||
workspace marker to plugins;
|
||||
- [x] add real two-vault smoke scenarios for external create/update/delete,
|
||||
scanner-based rename representation, no remote echo, and workspace lifecycle;
|
||||
- [x] document deployment and backup procedures for `verstak-sync-server`.
|
||||
- [x] persist an atomic core snapshot under `.verstak/sync/`;
|
||||
- [x] reconcile initial vaults safely;
|
||||
- [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, and document nginx/Caddy
|
||||
trusted-proxy deployment;
|
||||
- [x] bound JSON/push fields and pull pages, return stable public error codes,
|
||||
and make Desktop stop/persist cursor at the first failed page operation;
|
||||
- [x] add streamed Blob transport with SHA-256/size verification, atomic local
|
||||
apply, per-user/vault ownership references, file/quota limits, and immediate
|
||||
revoked-device denial;
|
||||
- [x] hash newly issued device, session, confirmation, and reset tokens;
|
||||
persist sessions, require CSRF for browser mutations, and use transactions
|
||||
for pairing, credential, blob, and multi-table user/device changes.
|
||||
- [x] add an embedded, dependency-free sync-server web console with shared
|
||||
responsive templates for public/account/admin flows, Russian/English/system
|
||||
locale preference, user device revoke, and operational admin sections for
|
||||
users, devices, vaults, storage, audit, SMTP settings, and diagnostics;
|
||||
protect its mutations with sessions, CSRF, security headers, and
|
||||
administrator re-authentication for sensitive changes.
|
||||
- [x] remove the former hardcoded server HTML and split the embedded console
|
||||
into shared layout/sidebar plus public, account, and per-admin-section
|
||||
templates; add locale-aware timestamps, local confirmation dialog, safe
|
||||
public-form CSRF, server-side filters/sorting/pagination, user email
|
||||
confirmation/device lifecycle actions, one-time generated admin password
|
||||
resets without URL/initial-HTML/audit/cookie/plaintext persistence, vault metadata diagnostics without file
|
||||
payloads, sanitised diagnostics download, and an interactive headless-
|
||||
Chromium smoke script without npm dependencies.
|
||||
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.
|
||||
|
||||
Known limits in this phase:
|
||||
Known limits:
|
||||
|
||||
- [x] The Files plugin API remains bounded to 2 MB text / 8 MB byte reads;
|
||||
core sync itself sends binary/larger files through the Blob API. Files over
|
||||
configured Blob/quota limits remain persistent visible warnings and are not
|
||||
marked synchronized.
|
||||
- [x] External rename is represented as delete + create; it is not a
|
||||
cross-device rename detector for ordinary files.
|
||||
- [ ] Automatic conflict resolution is intentionally absent. Operation-log
|
||||
retention/compaction is also intentionally deferred: without a server
|
||||
checkpoint/materialized state and proven device recovery cursors, pruning
|
||||
operations could make a newly paired device unrecoverable. Safe cleanup is
|
||||
limited to sessions/tokens/idempotency/audit/temp uploads/rate buckets.
|
||||
- [x] Files plugin API remains bounded to 2 MB text / 8 MB byte reads.
|
||||
- [x] External rename is represented as delete + create.
|
||||
- [ ] Operation-log retention/compaction is intentionally deferred.
|
||||
- [ ] Secrets, plugin settings, Todo, Journal, Activity, and Browser Inbox are
|
||||
not synchronized in this phase.
|
||||
- [ ] The embedded control plane intentionally remains server-rendered. A
|
||||
separate milestone may improve its visual design or add richer client-side
|
||||
interactions, but must preserve the local-first desktop model and server-side
|
||||
authorization/CSRF checks.
|
||||
not synchronized.
|
||||
|
||||
Verification:
|
||||
|
||||
- sync server `go test ./...`, `go vet ./...`, systemd validation and a local
|
||||
release-package smoke;
|
||||
- desktop unit/API tests and real two-vault smoke including a binary above the
|
||||
former inline limit;
|
||||
- SDK schema/type checks and official Sync plugin build/localization checks.
|
||||
Status: core sync foundations complete. Retention and additional data domains
|
||||
are future milestones.
|
||||
|
||||
### Phase 4 - Preview, Search, Activity, Journal
|
||||
|
||||
|
|
@ -206,30 +147,18 @@ Goal: add the next visible product layer as replaceable plugins.
|
|||
|
||||
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;
|
||||
- [x] implement basic image metadata preview plugin;
|
||||
- [x] implement baseline `verstak.search` workspace plugin and expose
|
||||
`searchProviders` in contribution summaries;
|
||||
- [x] add type-as-you-search behavior and vault path/name matches to
|
||||
`verstak.search`;
|
||||
- [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 baseline `verstak.search` workspace plugin;
|
||||
- [x] add type-as-you-search behavior and vault path/name matches;
|
||||
- [x] implement baseline `verstak.activity` event log plugin;
|
||||
- [x] expose Activity and Browser Inbox as global sidebar views;
|
||||
- [x] implement persistent search index and cross-provider runtime hosting;
|
||||
- [x] implement activity reconstruction and worklog suggestions;
|
||||
- [x] implement journal/worklog plugin that can consume activity suggestions.
|
||||
|
||||
Verification:
|
||||
|
||||
- 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.
|
||||
Status: done. Remaining work is UX depth (Today flow, reporting, timers).
|
||||
|
||||
### Phase 5 - Browser Inbox
|
||||
|
||||
|
|
@ -239,31 +168,19 @@ receiver and an official inbox plugin.
|
|||
Tasks:
|
||||
|
||||
- [x] define browser capture payload protocol;
|
||||
- [x] implement minimal `verstak.browser-inbox` plugin with workspace-scoped
|
||||
pending queues and a global aggregate view;
|
||||
- [x] implement browser extension capture scaffold for URL, selected text,
|
||||
page title, and link captures;
|
||||
- [x] implement minimal `verstak.browser-inbox` plugin;
|
||||
- [x] implement browser extension capture scaffold;
|
||||
- [x] define local receiver permission/pairing model;
|
||||
- [x] require an installation-local pairing token and expose its rotation through
|
||||
Browser Inbox settings;
|
||||
- [x] bound browser receiver payloads and file content before publishing events;
|
||||
- [x] require an installation-local pairing token;
|
||||
- [x] add domain-to-workspace binding;
|
||||
- [x] convert inbox entries into notes through public plugin APIs;
|
||||
- [x] record converted inbox entries in Activity through public plugin events;
|
||||
- [x] convert inbox entries into link files through public plugin APIs;
|
||||
- [x] convert captured text file attachments through public plugin APIs;
|
||||
- [x] convert captured bounded binary attachments through public plugin APIs.
|
||||
- [x] record converted inbox entries in Activity;
|
||||
- [x] convert inbox entries into link files;
|
||||
- [x] convert captured text file attachments;
|
||||
- [x] convert captured bounded binary attachments.
|
||||
|
||||
Verification:
|
||||
|
||||
- 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.
|
||||
Status: done. Remaining work: capture-from-clipboard/manual capture,
|
||||
chunked large-file capture.
|
||||
|
||||
### Phase 6 - Secrets
|
||||
|
||||
|
|
@ -275,13 +192,9 @@ Tasks:
|
|||
- [x] define secret-store capability and permissions;
|
||||
- [x] implement encrypted local secret storage;
|
||||
- [x] add UI-only official secrets plugin;
|
||||
- [x] integrate secret references with workspaces without exposing raw values to
|
||||
unrelated plugins.
|
||||
- [x] integrate secret references with workspaces.
|
||||
|
||||
Verification:
|
||||
|
||||
- permission denial tests;
|
||||
- storage encryption and no-plaintext regression checks.
|
||||
Status: done.
|
||||
|
||||
### Phase 7 - Sidecar/Sandbox Boundary
|
||||
|
||||
|
|
@ -289,16 +202,13 @@ Goal: move from trusted bundled plugin JavaScript toward safer plugin execution.
|
|||
|
||||
Tasks:
|
||||
|
||||
- implement sidecar launch protocol and local RPC;
|
||||
- permission-scope sidecar APIs;
|
||||
- stop sidecars on plugin disable;
|
||||
- surface sidecar logs and health in Plugin Manager;
|
||||
- document supported sidecar languages and packaging rules.
|
||||
- [ ] implement sidecar launch protocol and local RPC;
|
||||
- [ ] permission-scope sidecar APIs;
|
||||
- [ ] stop sidecars on plugin disable;
|
||||
- [ ] surface sidecar logs and health in Plugin Manager;
|
||||
- [ ] document supported sidecar languages and packaging rules.
|
||||
|
||||
Verification:
|
||||
|
||||
- sidecar health and permission tests;
|
||||
- disable/failure lifecycle tests.
|
||||
Status: not started.
|
||||
|
||||
### Phase 8 - Packaging, Update, Release
|
||||
|
||||
|
|
@ -306,40 +216,31 @@ Goal: produce installable, recoverable releases.
|
|||
|
||||
Tasks:
|
||||
|
||||
- define release artifact matrix for desktop, official plugins, SDK, sync server,
|
||||
and browser extension;
|
||||
- add plugin package signing/verification or equivalent integrity checks;
|
||||
- add backup/restore docs for vault and sync server;
|
||||
- add crash/log collection for local diagnostics;
|
||||
- build release smoke checklist.
|
||||
- [x] define release artifact matrix (desktop, plugins, SDK, sync server,
|
||||
browser extension);
|
||||
- [x] add build scripts for .deb, AppImage, Windows portable, and plugin
|
||||
packages;
|
||||
- [ ] add plugin package signing/verification or equivalent integrity checks;
|
||||
- [x] add backup/restore docs for vault and sync server;
|
||||
- [ ] add crash/log collection for local diagnostics;
|
||||
- [ ] build release smoke checklist.
|
||||
|
||||
Verification:
|
||||
|
||||
- clean build from clone;
|
||||
- packaged desktop starts with bundled official plugins;
|
||||
- documented recovery path works on a sample vault.
|
||||
Status: packaging scripts exist and produce release artifacts. Signing,
|
||||
auto-update, and release smoke checklist are future work.
|
||||
|
||||
## 5. Immediate Execution Order
|
||||
|
||||
1. [x] Command Palette UI host in `verstak-desktop`.
|
||||
2. [x] Status bar item host in `verstak-desktop`.
|
||||
3. [x] External open public v2 API to replace Files fallback.
|
||||
4. [x] Notes trash/delete UX in `verstak-official-plugins`.
|
||||
5. [x] Sync hardening pass: snapshots/reconciliation, workspace identity,
|
||||
bounded pagination, Blob transport/ownership/quota, secure token/session
|
||||
policy, and expanded real two-vault smoke. Checkpoint-based operation
|
||||
retention and additional data domains remain future work.
|
||||
6. [x] Browser inbox protocol design, extension scaffold, local receiver,
|
||||
minimal inbox plugin, and note/link/text-file/binary-file conversions are
|
||||
implemented.
|
||||
7. [ ] Product UX follow-up in `verstak-desktop`: make the shell-level Today
|
||||
flow the command center for captures, recent activity, Activity worklog
|
||||
suggestions, and Journal import/review. This should reuse existing official
|
||||
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.
|
||||
1. [x] Command Palette UI host.
|
||||
2. [x] Status bar item host.
|
||||
3. [x] External open public API.
|
||||
4. [x] Notes trash/delete UX.
|
||||
5. [x] Sync hardening pass.
|
||||
6. [x] Browser inbox protocol, extension scaffold, local receiver, inbox plugin,
|
||||
and conversions.
|
||||
7. [ ] Product UX follow-up: make the shell-level Today flow the command center
|
||||
for captures, recent activity, Activity worklog suggestions, and Journal
|
||||
import/review. This should reuse existing official plugin contracts instead
|
||||
of moving Activity, Browser Inbox, or Journal into desktop core.
|
||||
|
||||
## 6. Stop Conditions
|
||||
|
||||
|
|
|
|||
10
AGENTS.md
|
|
@ -26,11 +26,11 @@
|
|||
|
||||
Перед работой прочитать:
|
||||
|
||||
- `verstak-platform-docs/01_Product_Vision.md`
|
||||
- `verstak-platform-docs/02_Platform_Architecture.md`
|
||||
- `verstak-platform-docs/04_Plugin_System.md`
|
||||
- `verstak-platform-docs/05_Official_Plugins.md`
|
||||
- `verstak-platform-docs/06_Migration_Strategy.md`
|
||||
- `verstak-docs/01_Product_Vision.md`
|
||||
- `verstak-docs/02_Platform_Architecture.md`
|
||||
- `verstak-docs/04_Plugin_System.md`
|
||||
- `verstak-docs/05_Official_Plugins.md`
|
||||
- `verstak-docs/06_Migration_Strategy.md`
|
||||
|
||||
Если код противоречит документам, не молча подгонять документы под код. Сначала понять, это старый монолитный долг или осознанное новое решение.
|
||||
|
||||
|
|
|
|||
60
README.md
|
|
@ -1,25 +1,35 @@
|
|||
<div align="center">
|
||||
|
||||
# Verstak Platform Docs
|
||||
|
||||
### Architecture and platform documentation for Verstak.
|
||||
|
||||
**English** · [Русский](README.ru.md)
|
||||
|
||||
</div>
|
||||
|
||||
# Platform Architecture Reference
|
||||
|
||||
Сводные архитектурные артефакты платформы Верстак.
|
||||
Architecture artifacts for the Verstak platform.
|
||||
|
||||
> Первый публичный выпуск — alpha. Инструкции для сборки и упаковки каждого
|
||||
> исполняемого компонента находятся в его README: Desktop, official plugins,
|
||||
> browser extension и SDK должны собираться из одной release-линейки.
|
||||
> 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.
|
||||
|
||||
## Содержание
|
||||
## Contents
|
||||
|
||||
- [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) — план развития v2
|
||||
- [Full Implementation Roadmap](07_Full_Implementation_Roadmap.md) — путь до полной реализации Verstak2
|
||||
- [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
|
||||
## SDK Schemas
|
||||
|
||||
Схемы данных и контракты SDK находятся в репозитории
|
||||
[`verstak-sdk`](https://github.com/mirivlad/verstak-sdk).
|
||||
Data schemas and SDK contracts live in the
|
||||
[`verstak-sdk`](https://github.com/mirivlad/verstak-sdk) repository.
|
||||
|
||||
- [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)
|
||||
|
|
@ -28,18 +38,18 @@
|
|||
- [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`](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) | Документация |
|
||||
| [`verstak-official-plugins`](https://github.com/mirivlad/verstak-official-plugins) | Official plugins |
|
||||
| [`verstak-sdk`](https://github.com/mirivlad/verstak-sdk) | Plugin SDK and schemas |
|
||||
| [`verstak-sync-server`](https://github.com/mirivlad/verstak-sync-server) | Sync server |
|
||||
| [`verstak-browser-extension`](https://github.com/mirivlad/verstak-browser-extension) | Browser extension |
|
||||
| [`verstak-docs`](https://github.com/mirivlad/verstak-docs) | Documentation |
|
||||
|
||||
## Лицензия
|
||||
## License
|
||||
|
||||
Copyright © 2026 Verstak contributors. Документация распространяется на
|
||||
условиях [GNU AGPLv3 или новее](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,281 +0,0 @@
|
|||
# Platform Localization Implementation Plan
|
||||
|
||||
> **Execution note:** implement task-by-task in the current session. Do not use
|
||||
> subagents. Use TDD for every production boundary and keep commits scoped to a
|
||||
> single repository.
|
||||
|
||||
**Goal:** Add persisted System/Russian/English language selection to Verstak
|
||||
Desktop, localize the shell and all official plugins, and establish the public
|
||||
SDK contract used by future multilingual plugins.
|
||||
|
||||
**Architecture:** Desktop owns the application preference, locale resolution,
|
||||
shell catalogs, safe plugin-catalog loading, and a generic runtime bridge. Each
|
||||
plugin owns its manifest and UI catalogs and accesses them through
|
||||
`api.i18n`. Manifest literals remain English fallbacks. Locale changes update
|
||||
mounted UI without remounting plugin components.
|
||||
|
||||
**Tech stack:** Go/Wails backend tests, Svelte 4 and plain JavaScript frontend,
|
||||
TypeScript SDK with Vitest, JSON Schema, browserless Node plugin smoke tests,
|
||||
Playwright mocked-Wails E2E.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- `~/git/verstak` is UI reference only; do not copy its implementation.
|
||||
- Core must not contain official plugin messages or depend on official plugin
|
||||
IDs.
|
||||
- Official and third-party plugins use the same manifest and runtime API.
|
||||
- Store only `system`, `ru`, or `en`; default to `system`.
|
||||
- Resolve `ru-*` system locales to `ru`, everything else to `en`.
|
||||
- Do not remount plugins on locale changes or discard form/editor state.
|
||||
- Preserve existing unrelated generated Wails binding changes in the desktop
|
||||
worktree.
|
||||
- Add tests before production changes and observe each focused test fail before
|
||||
implementing its boundary.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: SDK localization contract
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-sdk/schemas/manifest.json`
|
||||
- Modify `verstak-sdk/src/types.ts`
|
||||
- Modify `verstak-sdk/src/plugin-api.ts`
|
||||
- Modify `verstak-sdk/src/test-utils.ts`
|
||||
- Modify `verstak-sdk/src/plugin-api.test.ts`
|
||||
- Generated by build: `verstak-sdk/dist/*`
|
||||
|
||||
**Contract produced:**
|
||||
|
||||
```ts
|
||||
interface PluginLocalizationConfig {
|
||||
defaultLocale: string;
|
||||
locales: Record<string, string>;
|
||||
}
|
||||
|
||||
interface PluginI18nAPI {
|
||||
getLocale(): 'ru' | 'en';
|
||||
t(key: string, params?: Record<string, string | number>, fallback?: string): string;
|
||||
onDidChangeLocale(listener: (locale: 'ru' | 'en') => void): Unsubscribe;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Add failing schema/type/mock tests for a valid manifest localization
|
||||
block, required default catalog, safe relative paths, API shape, fallback,
|
||||
interpolation, and locale-change notification.
|
||||
- [ ] Run `npm test -- --run src/plugin-api.test.ts` and confirm RED.
|
||||
- [ ] Add `localization` to the JSON Schema and TypeScript manifest types.
|
||||
- [ ] Add `i18n` to `VerstakPluginAPI` and configurable locale/messages to
|
||||
`createMockPluginAPI`.
|
||||
- [ ] Run focused tests GREEN.
|
||||
- [ ] Run `npm test`, `npm run lint`, and `npm run build`.
|
||||
- [ ] Commit only SDK source/schema/generated dist changes.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Desktop application language setting
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-desktop/internal/core/appsettings/manager.go`
|
||||
- Modify `verstak-desktop/internal/core/appsettings/manager_test.go`
|
||||
- Modify `verstak-desktop/internal/api/app.go`
|
||||
- Modify `verstak-desktop/internal/api/app_test.go`
|
||||
|
||||
**Behavior:** missing language becomes `system`; valid updates persist; invalid
|
||||
updates return an error; unrelated settings remain unchanged.
|
||||
|
||||
- [ ] Add failing manager tests for default, reload, and preservation.
|
||||
- [ ] Add failing API tests for `GetAppSettings` and accepted/rejected patches.
|
||||
- [ ] Run focused Go tests and confirm RED.
|
||||
- [ ] Add `Language` to `appsettings.Config`, default/load normalization, and
|
||||
update handling without changing unrelated values.
|
||||
- [ ] Expose and validate `language` in the Wails API.
|
||||
- [ ] Run focused tests GREEN.
|
||||
- [ ] Run `gofmt` on changed Go files and `go test ./internal/core/appsettings
|
||||
./internal/api`.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Plugin localization metadata and safe catalog loading
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-desktop/internal/core/plugin/plugin.go`
|
||||
- Modify `verstak-desktop/internal/core/plugin/plugin_test.go`
|
||||
- Modify `verstak-desktop/internal/api/app.go`
|
||||
- Modify `verstak-desktop/internal/api/app_test.go`
|
||||
- Generated Wails bindings are intentionally not regenerated over existing
|
||||
user changes; frontend imports call the binding only after generation is
|
||||
explicitly reconciled.
|
||||
|
||||
**Backend API:**
|
||||
|
||||
```go
|
||||
GetPluginLocalization(pluginID, locale string) (map[string]string, string)
|
||||
```
|
||||
|
||||
- [ ] Add failing manifest parsing tests for localization metadata.
|
||||
- [ ] Add failing API tests for declared catalog reads, locale fallback input,
|
||||
malformed JSON, non-string values, missing declarations, absolute paths,
|
||||
backslashes, traversal, and containment.
|
||||
- [ ] Run focused Go tests and confirm RED.
|
||||
- [ ] Add localization manifest structs and the read-only safe catalog API.
|
||||
- [ ] Run focused tests GREEN and `go test ./internal/core/plugin ./internal/api`.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Frontend localization service
|
||||
|
||||
**Files:**
|
||||
|
||||
- Add `verstak-desktop/frontend/src/lib/i18n/catalogs/en.js`
|
||||
- Add `verstak-desktop/frontend/src/lib/i18n/catalogs/ru.js`
|
||||
- Add `verstak-desktop/frontend/src/lib/i18n/index.js`
|
||||
- Add `verstak-desktop/frontend/tests/i18n-test.mjs`
|
||||
- Modify `verstak-desktop/frontend/src/main.js`
|
||||
- Modify `verstak-desktop/frontend/src/lib/test/wails-mock.js`
|
||||
|
||||
- [ ] Add a failing standalone Node test for preference validation, system
|
||||
locale resolution, interpolation, shell fallback, plugin catalog fallback,
|
||||
subscription, and contribution-copy localization.
|
||||
- [ ] Run `node frontend/tests/i18n-test.mjs` and confirm RED.
|
||||
- [ ] Implement the framework-independent locale store and catalog cache.
|
||||
- [ ] Initialize it from `GetAppSettings` before mounting Svelte.
|
||||
- [ ] Extend the Wails mock with language state and plugin catalogs.
|
||||
- [ ] Run the focused test GREEN and `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Runtime `api.i18n` bridge
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-desktop/frontend/src/lib/plugin-host/VerstakPluginAPI.js`
|
||||
- Modify `verstak-desktop/frontend/src/lib/plugin-host/PluginBundleHost.svelte`
|
||||
- Modify `verstak-desktop/frontend/tests/plugin-api-contributions-test.mjs`
|
||||
- Modify `verstak-desktop/frontend/tests/bundle-host-test.cjs`
|
||||
|
||||
- [ ] Extend bridge tests first to require `getLocale`, `t`, and disposable
|
||||
`onDidChangeLocale` behavior.
|
||||
- [ ] Confirm focused RED.
|
||||
- [ ] Preload the plugin catalog before mount and expose the i18n service
|
||||
through the public API.
|
||||
- [ ] Keep locale subscriptions under existing API disposal cleanup.
|
||||
- [ ] Translate host loading/error states through shell catalogs.
|
||||
- [ ] Confirm bridge/bundle tests GREEN.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Language selector and shell migration
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-desktop/frontend/src/App.svelte`
|
||||
- Modify every Svelte file under `verstak-desktop/frontend/src/lib/shell/`
|
||||
- Modify every Svelte file under
|
||||
`verstak-desktop/frontend/src/lib/plugin-manager/`
|
||||
- Modify `verstak-desktop/frontend/src/lib/plugin-host/PluginBundleHost.svelte`
|
||||
- Modify `verstak-desktop/frontend/e2e/status-bar.spec.js`
|
||||
- Add `verstak-desktop/frontend/e2e/localization.spec.js`
|
||||
- Modify `verstak-desktop/frontend/tests/shell-source-contract-test.mjs` where
|
||||
assertions intentionally depend on translated literals.
|
||||
|
||||
- [ ] Add failing E2E assertions for the language submenu/radio state,
|
||||
persistence, Russian system locale, English fallback, and live shell update.
|
||||
- [ ] Confirm focused Playwright RED.
|
||||
- [ ] Add the language submenu to StatusBar and persist changes through
|
||||
`UpdateAppSettings`.
|
||||
- [ ] Replace shell user-facing literals with shell catalog keys, including
|
||||
aria labels, tooltips, empty/loading/error/confirmation text.
|
||||
- [ ] Localize copies of plugin manifests and contributions before display.
|
||||
- [ ] Confirm focused E2E GREEN, standalone shell tests GREEN, and frontend
|
||||
production build GREEN.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Official plugin localization declarations and catalog checks
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify every `verstak-official-plugins/plugins/*/plugin.json`
|
||||
- Add `locales/en.json` and `locales/ru.json` under every official plugin
|
||||
- Add `verstak-official-plugins/scripts/check-locales.mjs`
|
||||
- Modify `verstak-official-plugins/scripts/check.sh`
|
||||
- Modify relevant plugin smoke harness API mocks under
|
||||
`verstak-official-plugins/scripts/`
|
||||
|
||||
- [ ] Add the locale checker first and confirm it fails for missing metadata
|
||||
and catalogs.
|
||||
- [ ] Add localization declarations to all 13 manifests.
|
||||
- [ ] Add English and Russian manifest/contribution keys to all catalogs.
|
||||
- [ ] Verify catalog parity, string-only values, safe paths, and required
|
||||
derived manifest keys.
|
||||
- [ ] Run `./scripts/check.sh` GREEN before migrating runtime UI strings.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Plain-JavaScript official plugin UI migration
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `frontend/src/index.js` for activity, browser-inbox, default-editor,
|
||||
file-preview, files, journal, notes, platform-test, search, secrets, todo, and
|
||||
trash.
|
||||
- Expand each plugin's `locales/en.json` and `locales/ru.json`.
|
||||
- Modify focused `scripts/smoke-*-plugin.js` tests.
|
||||
|
||||
For each plugin, repeat independently:
|
||||
|
||||
- [ ] Add a failing bilingual rendering assertion and a locale-change state
|
||||
preservation assertion to its existing smoke test.
|
||||
- [ ] Add a local `t` helper backed only by `api.i18n`.
|
||||
- [ ] Replace user-visible literals, attributes, empty/loading/error states,
|
||||
confirmation prompts, and known validation messages with stable keys.
|
||||
- [ ] Subscribe once at mount, update only rendered text/state, and unsubscribe
|
||||
at unmount.
|
||||
- [ ] Run the focused plugin smoke test GREEN before moving to the next plugin.
|
||||
|
||||
Do not translate IDs, paths, user data, logs, API labels in diagnostics, or
|
||||
provider-returned values.
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Svelte Sync plugin migration
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-official-plugins/plugins/sync/frontend/src/SyncSettings.svelte`
|
||||
- Modify `verstak-official-plugins/plugins/sync/frontend/src/SyncStatusBar.svelte`
|
||||
- Modify `verstak-official-plugins/plugins/sync/frontend/src/index.js`
|
||||
- Expand `verstak-official-plugins/plugins/sync/locales/en.json`
|
||||
- Expand `verstak-official-plugins/plugins/sync/locales/ru.json`
|
||||
- Modify Sync smoke/E2E tests as appropriate.
|
||||
|
||||
- [ ] Add failing English/Russian and live-change assertions.
|
||||
- [ ] Bridge `api.i18n` into reactive Svelte state without remounting.
|
||||
- [ ] Translate UI labels and known status/error chrome.
|
||||
- [ ] Run Sync plugin build and focused tests GREEN.
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Full verification and documentation
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify `verstak-docs/04_Plugin_System.md`
|
||||
- Modify `verstak-docs/07_Full_Implementation_Roadmap.md`
|
||||
- Modify other contract docs only where the implemented behavior requires it.
|
||||
|
||||
- [ ] Run SDK: `npm test`, `npm run lint`, `npm run build`.
|
||||
- [ ] Run desktop backend: `go test -count=1 ./...` and `go vet ./...`.
|
||||
- [ ] Run desktop standalone frontend tests, production build, and focused then
|
||||
full `npm run test:e2e`.
|
||||
- [ ] Run official plugins: `./scripts/check.sh` and all plugin builds.
|
||||
- [ ] Run `git diff --check` in every changed repository.
|
||||
- [ ] Inspect changed-file lists and ensure pre-existing Wails generated changes
|
||||
are not staged or overwritten.
|
||||
- [ ] Update docs to match only verified behavior.
|
||||
- [ ] Commit scoped changes in each repository.
|
||||
- [ ] Report exact verification labels. If native Wails/WebKit was not exercised,
|
||||
state: `GUI behavior was not verified in the real desktop shell.`
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
# Alpha Activity And Journal 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:** Make Activity a durable, background-maintained session log that safely proposes Journal entries without creating a Дело automatically.
|
||||
|
||||
**Architecture:** Desktop adds a plugin background-service lifecycle, unsubscribe-capable event bridge, and append-only Activity log. The Activity plugin owns session reconstruction and watermarks; Journal receives an explicit reviewed candidate and requires a destination for unassigned sessions.
|
||||
|
||||
**Tech Stack:** Go, Wails, Svelte, plain JavaScript plugin bundles, Go tests, Node smoke tests, Playwright.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Activity raw events are retained for 60 days, 10,000 events, or 8 MiB, whichever is reached first.
|
||||
- Raw activity uses append/compaction, not settings.json rewrites.
|
||||
- Session scope is workspaceId plus path cache or unassigned.
|
||||
- Point-event duration is capped at 10 minutes per adjacent pair; gap over 20 minutes starts another session.
|
||||
- Accepted/dismissed watermarks consume only the reviewed slice.
|
||||
- Journal save is always an explicit user action.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add background services, event unsubscription, and an append-only Activity log
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/plugin/plugin.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/plugin/plugin_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/events/bus.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/storage/api.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/storage/api_test.go
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/plugin-host/BackgroundPluginHost.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/App.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/plugin-host/VerstakPluginAPI.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/src/types.ts
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/schemas/contributions.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/schemas/manifest.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/src/plugin-api.test.ts
|
||||
|
||||
**Interfaces:**
|
||||
- Adds contributes.backgroundServices with id and component fields.
|
||||
- Bus Subscribe returns subscription ID; Unsubscribe removes only that handler.
|
||||
- Adds ActivityLogAppend, ActivityLogRead, ActivityLogDeleteWorkspace, and ActivityLogCompact backend methods.
|
||||
- Background bundle registration exposes start(api) returning a cleanup function.
|
||||
|
||||
- [ ] **Step 1: Write failing manifest, bus, and storage tests**
|
||||
|
||||
Add a manifest validation test and isolated bus-unsubscribe test:
|
||||
|
||||
~~~go
|
||||
subA := bus.Subscribe("browser.activity.domain", handlerA)
|
||||
subB := bus.Subscribe("browser.activity.domain", handlerB)
|
||||
bus.Unsubscribe("browser.activity.domain", subA)
|
||||
bus.Publish(events.Event{Name: "browser.activity.domain"})
|
||||
if gotA != 0 || gotB != 1 { t.Fatalf("wrong selective unsubscribe: %d %d", gotA, gotB) }
|
||||
~~~
|
||||
|
||||
Add an append test asserting two appends create two NDJSON records and a
|
||||
compaction test keeping only the newest record under a small test limit.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/plugin ./internal/core/events ./internal/core/storage ./internal/api -run 'Test.*(Background|Unsubscribe|ActivityLog)' -count=1
|
||||
~~~
|
||||
|
||||
Expected: FAIL because subscriptions have no IDs and the log API does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement the platform boundary**
|
||||
|
||||
Define:
|
||||
|
||||
~~~go
|
||||
type ContributionBackgroundService struct {
|
||||
ID string `json:"id"`
|
||||
Component string `json:"component"`
|
||||
}
|
||||
|
||||
type ActivityLogRecord struct {
|
||||
ActivityID string `json:"activityId"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
~~~
|
||||
|
||||
Append NDJSON under plugin-data/verstak.activity/events.ndjson using a locked
|
||||
append. Compact to the declared retention limits by rewriting a temporary file.
|
||||
Keep only compact candidate state in plugin data. BackgroundPluginHost loads
|
||||
enabled plugins with a background contribution once at app startup and calls the
|
||||
registered start function; it calls cleanup on disable/reload/destroy.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/plugin ./internal/core/events ./internal/core/storage ./internal/api -count=1
|
||||
git add internal/core/plugin internal/core/events internal/core/storage internal/api frontend/src/App.svelte frontend/src/lib/plugin-host
|
||||
git commit -m "feat: add background activity runtime"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-sdk add src/types.ts schemas/contributions.json schemas/manifest.json src/plugin-api.test.ts
|
||||
git -C /home/mirivlad/git/verstak2/verstak-sdk commit -m "feat: define plugin background services"
|
||||
~~~
|
||||
|
||||
Expected: focused tests prove one subscription, selective cleanup, append, and compaction.
|
||||
|
||||
### Task 2: Rebuild Activity sessions and persistent candidate watermarks
|
||||
|
||||
**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/plugins/activity/locales/en.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/locales/ru.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-activity-plugin.js
|
||||
|
||||
**Interfaces:**
|
||||
- Activity background service registers ActivityService.start(api).
|
||||
- Event record includes activityId, scope, occurredAt, durationSeconds, and sessionId.
|
||||
- Candidate contains sessionId, handledThrough, estimatedMinutes, dateSlices, and source activities.
|
||||
|
||||
- [ ] **Step 1: Write failing smoke scenarios**
|
||||
|
||||
Add three explicit scenarios:
|
||||
|
||||
~~~js
|
||||
const nineMinutes = eventsAt('2026-07-12T10:00:00Z', '2026-07-12T10:09:00Z');
|
||||
assert.equal(buildSessions(nineMinutes)[0].estimatedMinutes, 9);
|
||||
const late = appendLateEvent(existingSession, eventAt('2026-07-12T09:58:00Z'));
|
||||
assert.equal(late.sessionId, existingSession.sessionId);
|
||||
assert.equal(candidateAfterDismiss.sourceActivityIds.includes('a'), false);
|
||||
~~~
|
||||
|
||||
Also assert a 23:50 to 00:30 session has one session ID and date slices for both
|
||||
local dates.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
||||
node scripts/smoke-activity-plugin.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because Activity derives candidate IDs from first and last events.
|
||||
|
||||
- [ ] **Step 3: Implement background session state**
|
||||
|
||||
Replace view-mounted event recording with ActivityService.start(api). Persist
|
||||
immutable generated session IDs and anchors in activity-state.json. Build scope
|
||||
as either workspaceId plus root path or unassigned. Sum explicit browser
|
||||
duration and only zero-duration adjacent point intervals, cap a point interval
|
||||
at 10 minutes, split at 20 minutes, and cap a session at 120 minutes.
|
||||
|
||||
For accept or dismiss, store the ordered handledThrough watermark. New events
|
||||
after the watermark require another 10 minutes before a new candidate appears;
|
||||
late events at or before the watermark remain diagnostic only.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
node scripts/smoke-activity-plugin.js
|
||||
./scripts/check.sh
|
||||
git add plugins/activity scripts/smoke-activity-plugin.js
|
||||
git commit -m "feat: persist activity sessions and watermarks"
|
||||
~~~
|
||||
|
||||
Expected: session, late-event, dismissal, browser-duration, and midnight smoke tests pass.
|
||||
|
||||
### Task 3: Review candidates safely in Journal and expose sessions in UI
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/frontend/src/index.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/frontend/src/index.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/locales/en.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/locales/ru.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-journal-plugin.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/activity.spec.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/todo.spec.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/test/wails-mock.js
|
||||
|
||||
**Interfaces:**
|
||||
- Journal candidate request accepts destinationWorkspaceId only for unassigned scope.
|
||||
- Saved Journal entry stores sourceSessionId and handledThrough in addition to activity IDs.
|
||||
- Activity UI has Sessions default and Events diagnostic view.
|
||||
|
||||
- [ ] **Step 1: Write failing E2E and smoke assertions**
|
||||
|
||||
Require an unassigned candidate to show a destination selector and reject Save
|
||||
until an active workspace ID is selected:
|
||||
|
||||
~~~js
|
||||
await page.getByRole('button', { name: /review/i }).click();
|
||||
await expect(page.getByText(/choose.*дело/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /save/i })).toBeDisabled();
|
||||
~~~
|
||||
|
||||
Require the mock WritePluginSetting path to retain sourceSessionId after reload.
|
||||
Require Clear Activity and Journal Delete to show cancellation-safe confirmation
|
||||
before changing stored rows.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend
|
||||
npm run test:e2e -- activity.spec.js todo.spec.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because the current mock discards settings and Journal only accepts path-scoped candidates.
|
||||
|
||||
- [ ] **Step 3: Implement review UI and local dates**
|
||||
|
||||
Render domain rows as hostname plus duration without URLs. Keep raw event
|
||||
details in the Events tab. For unassigned scope, list active workspace IDs and
|
||||
require a selection before handing the candidate to Journal. Preselect the
|
||||
largest date slice, show both date slices, use local date conversion, and save
|
||||
the session watermark with the Journal entry. Show a visible action if Journal
|
||||
is disabled instead of silently dropping the request. Put a confirm/cancel
|
||||
dialog in front of Clear Activity and Journal Delete; cancelling leaves the
|
||||
append log, candidate state, and entry list untouched.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins && node scripts/smoke-activity-plugin.js && node scripts/smoke-journal-plugin.js
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend && npm run test:e2e -- activity.spec.js todo.spec.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins add plugins/activity plugins/journal scripts/smoke-activity-plugin.js scripts/smoke-journal-plugin.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins commit -m "feat: review activity sessions in journal"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop add frontend/e2e/activity.spec.js frontend/e2e/todo.spec.js frontend/src/lib/test/wails-mock.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "test: cover activity journal review flow"
|
||||
~~~
|
||||
|
||||
Expected: candidate-to-Journal, local date, and persisted mock workflows pass.
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
# Alpha Browser Domain 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:** Add explicitly consented, privacy-minimal active-tab domain timing to the browser extension and deliver immutable activity batches to Desktop.
|
||||
|
||||
**Architecture:** Manual captures remain unchanged. A new tracker stores mutable accumulators, immutable pending batches, and acknowledged IDs locally; Desktop accepts a separate authenticated activity endpoint and emits browser.activity.domain.
|
||||
|
||||
**Tech Stack:** Plain WebExtension JavaScript, Node tests, Go HTTP handler tests, shared JSON vectors.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Passive tracking defaults to disabled and requires explicit informed consent.
|
||||
- Track only normalized hostname plus duration for an active tab in a focused window.
|
||||
- Never send URL, title, text, page content, keystrokes, or history.
|
||||
- A pending batch is byte-for-byte immutable; acknowledge only its own ID.
|
||||
- Discard ambiguous or negative clock deltas and gaps over 10 minutes.
|
||||
- Use hostname-normalization-v1 for extension, Desktop receiver, exclusions, and bindings.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Define and test canonical hostname normalization
|
||||
|
||||
**Files:**
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-sdk/schemas/hostname-normalization-v1.json
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/hostname.js
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/hostname-normalization-v1.json
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-hostname.js
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/normalize.go
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/normalize_test.go
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/testdata/hostname-normalization-v1.json
|
||||
|
||||
**Interfaces:**
|
||||
- Produces JavaScript normalizeHostnameV1(value, mode).
|
||||
- Produces Go hostname.NormalizeV1(value string, mode Mode) (string, error).
|
||||
- Mode is URLSource for HTTP(S) URLs and BareHost for binding/exclusion input.
|
||||
|
||||
- [ ] **Step 1: Write the shared vector corpus and failing tests**
|
||||
|
||||
Include vectors for Unicode/punycode, trailing dot, port stripping/rejection,
|
||||
IPv4, bracketed IPv6, localhost, one-label internal names, malformed labels,
|
||||
and an overlong name:
|
||||
|
||||
~~~json
|
||||
[
|
||||
{"mode":"bare","input":"пример.рф.","want":"xn--e1afmkfd.xn--p1ai"},
|
||||
{"mode":"url","input":"https://Example.COM:8443/a","want":"example.com"},
|
||||
{"mode":"bare","input":"example.com:8443","error":"port"},
|
||||
{"mode":"bare","input":"[::1]","want":"::1"}
|
||||
]
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension
|
||||
node scripts/test-hostname.js
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/hostname -count=1
|
||||
~~~
|
||||
|
||||
Expected: both fail because the normalizers do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement both normalizers**
|
||||
|
||||
Use the browser URL parser plus ASCII IDNA conversion in JavaScript. Use
|
||||
net/url, net/netip, and golang.org/x/net/idna.Lookup.ToASCII in Go. Store
|
||||
ASCII A-labels, lowercase names, no ports or IPv6 brackets, and reject malformed
|
||||
or over-limit values.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension && npm test
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop && GOCACHE=/tmp/verstak-go-cache go test ./internal/core/hostname -count=1
|
||||
cmp /home/mirivlad/git/verstak2/verstak-sdk/schemas/hostname-normalization-v1.json /home/mirivlad/git/verstak2/verstak-browser-extension/shared/hostname-normalization-v1.json
|
||||
cmp /home/mirivlad/git/verstak2/verstak-sdk/schemas/hostname-normalization-v1.json /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/testdata/hostname-normalization-v1.json
|
||||
git -C /home/mirivlad/git/verstak2/verstak-sdk add schemas/hostname-normalization-v1.json
|
||||
git -C /home/mirivlad/git/verstak2/verstak-sdk commit -m "feat: define hostname normalization vectors"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-browser-extension add shared/hostname.js shared/hostname-normalization-v1.json scripts/test-hostname.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-browser-extension commit -m "feat: normalize activity hostnames"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop add internal/core/hostname
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "feat: normalize browser hostnames"
|
||||
~~~
|
||||
|
||||
Expected: every copied corpus passes in both implementations.
|
||||
|
||||
### Task 2: Add a separate authenticated Desktop activity receiver
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/browserreceiver/receiver.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/browserreceiver/receiver_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
|
||||
**Interfaces:**
|
||||
- Accepts POST /activities with BrowserDomainActivity.
|
||||
- Emits browser.activity.domain with normalized hostname, duration, ID, and optional workspaceId.
|
||||
- Returns status accepted and activityId for first delivery and duplicate delivery.
|
||||
|
||||
- [ ] **Step 1: Write failing handler tests**
|
||||
|
||||
Create a valid immutable request and then retry the same ID:
|
||||
|
||||
~~~go
|
||||
payload := "{\"schemaVersion\":1,\"activityId\":\"a-1\",\"hostname\":\"пример.рф\",\"durationSeconds\":300,\"startedAt\":\"2026-07-12T10:00:00Z\",\"endedAt\":\"2026-07-12T10:05:00Z\"}"
|
||||
req := httptest.NewRequest(http.MethodPost, "/activities", strings.NewReader(payload))
|
||||
req.Header.Set("X-Verstak-Receiver-Token", "pair-token")
|
||||
~~~
|
||||
|
||||
Assert invalid tokens, URL-like hostnames, zero or oversize duration, and a
|
||||
duplicate ID do not publish a second event.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/browserreceiver -run Activity -count=1
|
||||
~~~
|
||||
|
||||
Expected: FAIL because /activities is not routed.
|
||||
|
||||
- [ ] **Step 3: Implement the bounded idempotent route**
|
||||
|
||||
Add BrowserDomainActivity, a 30-day bounded receiver ID cache, and
|
||||
handleActivity which invokes hostname.NormalizeV1. Do not reuse capture types
|
||||
or persistence. In App, resolve an active exact hostname binding to workspaceId
|
||||
and a path cache before Activity receives the event.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/browserreceiver ./internal/api -count=1
|
||||
git add internal/core/browserreceiver/receiver.go internal/core/browserreceiver/receiver_test.go internal/api/app.go internal/api/app_test.go
|
||||
git commit -m "feat: receive browser domain activity"
|
||||
~~~
|
||||
|
||||
Expected: authentication, normalization, idempotency, and binding tests pass.
|
||||
|
||||
### Task 3: Build the opt-in extension tracker and immutable delivery queue
|
||||
|
||||
**Files:**
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/activity-tracker.js
|
||||
- Create: /home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-activity-tracker.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/api.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.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.css
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/locales/en.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/locales/ru.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/chromium/manifest.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/firefox/manifest.json
|
||||
|
||||
**Interfaces:**
|
||||
- Persists activeAccumulator, pendingBatches, and acknowledgedIds under verstak.activityTracker.
|
||||
- Produces sendActivity(receiverUrl, token, immutablePayload).
|
||||
- Settings include passiveActivityEnabled false and excludedDomains empty.
|
||||
|
||||
- [ ] **Step 1: Write failing state-machine tests**
|
||||
|
||||
Cover consent, active-tab-only timing, long-gap discard, and the A/B batch case:
|
||||
|
||||
~~~js
|
||||
tracker.addElapsed('example.com', 600, start, end);
|
||||
const batchA = tracker.freeze('example.com');
|
||||
tracker.addElapsed('example.com', 300, laterStart, laterEnd);
|
||||
assert.equal(tracker.pendingBatches[0].payload.durationSeconds, 600);
|
||||
tracker.acknowledge(batchA.activityId);
|
||||
assert.equal(tracker.activeAccumulator['example.com'].durationSeconds, 300);
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-browser-extension
|
||||
node scripts/test-activity-tracker.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because the state machine is absent.
|
||||
|
||||
- [ ] **Step 3: Implement tracker, disclosure, and listeners**
|
||||
|
||||
Implement the tracker as pure functions. In background.js, register
|
||||
tabs.onActivated, tabs.onUpdated, windows.onFocusChanged, idle.onStateChanged,
|
||||
and a five-minute alarm only while opted in. Set idle detection to 600 seconds.
|
||||
On a gap over 600 seconds, clock rollback, lock, or browser startup, reset the
|
||||
active checkpoint without adding time. Retry frozen batches oldest-first and
|
||||
remove only matching IDs.
|
||||
|
||||
Show an unchecked Russian/English consent explanation in settings. Add windows,
|
||||
alarms, and idle permissions to both manifests. Keep manual capture queue and
|
||||
popup actions unchanged.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
npm test
|
||||
node scripts/test-activity-tracker.js
|
||||
git add shared chromium/manifest.json firefox/manifest.json scripts/test-activity-tracker.js
|
||||
git commit -m "feat: add opt-in browser domain tracker"
|
||||
~~~
|
||||
|
||||
Expected: tracker unit tests and existing extension tests pass.
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
# Alpha Browser Inbox 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:** Preserve a manually captured link when it leaves the global Inbox, give Browser Inbox a usable archive, and make links durable across case lifecycle changes.
|
||||
|
||||
**Architecture:** A capture remains one canonical record. Its global archive state is independent of its workspaceId relation; domain bindings use the same stable identity. Browser Inbox owns capture UI and migration, while Desktop exposes a narrow URL-open capability for user-initiated links.
|
||||
|
||||
**Tech Stack:** Plain JavaScript plugin bundle, Go Wails API, Node smoke tests, Playwright.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Only manual extension actions create Browser Inbox captures.
|
||||
- Captures have at most one optional case assignment in this alpha.
|
||||
- Remove from global Inbox archives; only Delete everywhere is permanent.
|
||||
- Assignment and binding use workspaceId, never path as identity.
|
||||
- Create .url files through files.write and never overwrite a name silently.
|
||||
- URL opening is direct HTTP(S) opening, not a Linux .url association.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Migrate Browser Inbox records and implement archive state
|
||||
|
||||
**Files:**
|
||||
- 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/locales/en.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/locales/ru.json
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
|
||||
**Interfaces:**
|
||||
- Canonical capture fields: globalState, workspaceId, workspaceState, workspaceRootPath, workspaceTrashId.
|
||||
- Domain binding value: workspaceId plus workspaceRootPath cache plus state.
|
||||
- Mutations: archive, restore, assign, unassign, deleteEverywhere, migrate.
|
||||
|
||||
- [ ] **Step 1: Write failing migration and archive smoke tests**
|
||||
|
||||
Use a legacy path assignment and assert it resolves to a stable ID. Test the
|
||||
original data-loss regression:
|
||||
|
||||
~~~js
|
||||
await inbox.assignWorkspace('capture-1', activeWorkspace);
|
||||
await inbox.archiveCapture('capture-1');
|
||||
assert.equal(globalRows().some(row => row.captureId === 'capture-1'), false);
|
||||
assert.equal(workspaceRows(activeWorkspace.workspaceId).some(row => row.captureId === 'capture-1'), true);
|
||||
await inbox.restoreCapture('capture-1');
|
||||
assert.equal(globalRows().some(row => row.captureId === 'capture-1'), true);
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
||||
node scripts/smoke-browser-inbox-plugin.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because archive and assignment are currently one mutable path field.
|
||||
|
||||
- [ ] **Step 3: Implement canonical state and idempotent migration**
|
||||
|
||||
Normalize every capture on read. Set legacy captures globalState active. Resolve
|
||||
legacy paths through Desktop identities: active marker gives workspaceId,
|
||||
otherwise set workspaceState unavailable. Store global captures once; workspace
|
||||
views filter the canonical collection by workspaceId rather than duplicate
|
||||
storage keys. Reject permanent deletion without an explicit confirm token from
|
||||
the UI.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
node scripts/smoke-browser-inbox-plugin.js
|
||||
./scripts/check.sh
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins add plugins/browser-inbox scripts/smoke-browser-inbox-plugin.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins commit -m "feat: preserve archived browser inbox captures"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop add internal/api/app.go internal/api/app_test.go
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "feat: migrate browser inbox relations"
|
||||
~~~
|
||||
|
||||
Expected: migration, archive, restore, and assignment tests pass.
|
||||
|
||||
### Task 2: Handle rename, Trash, restore, purge, and archive UI
|
||||
|
||||
**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
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/browser-inbox.spec.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/test/wails-mock.js
|
||||
|
||||
**Interfaces:**
|
||||
- Receives workspace.renamed, workspace.trashed, workspace.restored, and workspace.purged.
|
||||
- Status filter values are active, archive, all.
|
||||
- Archive bulk action is restore; bulk permanent delete is not offered.
|
||||
|
||||
- [ ] **Step 1: Write failing lifecycle and UI tests**
|
||||
|
||||
Assert a rename changes only path cache, trash disables routing, restore restores
|
||||
the same workspace ID, purge makes captures unassigned and bindings orphaned:
|
||||
|
||||
~~~js
|
||||
await emitWorkspaceEvent('workspace.trashed', { workspaceId: 'w-1', trashId: 't-1' });
|
||||
assert.equal(capture.workspaceState, 'trashed');
|
||||
assert.equal(domainBinding('client.example').state, 'trashed');
|
||||
await emitWorkspaceEvent('workspace.purged', { workspaceId: 'w-1', trashId: 't-1' });
|
||||
assert.equal(capture.workspaceState, 'unassigned');
|
||||
assert.equal(domainBinding('client.example').state, 'orphaned');
|
||||
~~~
|
||||
|
||||
In Playwright, select Archive, restore one visible item, and bulk-restore only
|
||||
the filtered archive rows. Also click Delete everywhere for an assigned capture,
|
||||
assert the dialog names its assigned Дело, cancel, and assert that the global
|
||||
and assigned rows still exist.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend
|
||||
npm run test:e2e -- browser-inbox.spec.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because the current UI has only processed filters and destructive Clear.
|
||||
|
||||
- [ ] **Step 3: Implement lifecycle and labels**
|
||||
|
||||
Subscribe once from the Browser Inbox background service. Update path caches on
|
||||
rename only when workspaceId matches. On external unavailable, do not route to
|
||||
the same path again. Add Active, Archive, and All filters; make search apply
|
||||
inside the selected filter; show Archive badge inside a case; add Restore to
|
||||
Inbox and visible filtered bulk restore. Rename Clear to an archive action with
|
||||
count confirmation. Keep Delete everywhere separate and require a dialog that
|
||||
names the assigned Дело; cancellation must perform no mutation.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins && node scripts/smoke-browser-inbox-plugin.js
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend && npm run test:e2e -- browser-inbox.spec.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins add plugins/browser-inbox scripts/smoke-browser-inbox-plugin.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins commit -m "feat: add browser inbox archive lifecycle"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop add frontend/e2e/browser-inbox.spec.js frontend/src/lib/test/wails-mock.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "test: cover browser inbox archive lifecycle"
|
||||
~~~
|
||||
|
||||
Expected: archive filters, restore, rename, Trash, restore, and purge tests pass.
|
||||
|
||||
### Task 3: Save and open durable links without file-association reliance
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/permissions/registry.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/plugin-host/VerstakPluginAPI.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/plugin.json
|
||||
- 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:**
|
||||
- Permission and API: urls.openExternal(url string) for user-initiated HTTP(S) URLs only.
|
||||
- SaveLinkInCase(captureId, workspaceId, filename) creates Links/name.url with InternetShortcut URL field.
|
||||
- Collision response offers a proposed unique filename and never overwrites.
|
||||
|
||||
- [ ] **Step 1: Write failing API and plugin tests**
|
||||
|
||||
Add tests for invalid URL, readonly failure, unique filename, collision, and
|
||||
direct opener argument:
|
||||
|
||||
~~~go
|
||||
if errStr := app.OpenExternalURL("browser.plugin", "https://example.test/a"); errStr != "" {
|
||||
t.Fatal(errStr)
|
||||
}
|
||||
if got := opener.Arguments[0]; got != "https://example.test/a" {
|
||||
t.Fatalf("opened %q, want URL not .url path", got)
|
||||
}
|
||||
~~~
|
||||
|
||||
Add plugin smoke assertions for a title-derived name, Link fallback, and
|
||||
proposal Link (2).url after a collision.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/api -run 'Test.*OpenExternalURL' -count=1
|
||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
||||
node scripts/smoke-browser-inbox-plugin.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because only file-path external opening exists.
|
||||
|
||||
- [ ] **Step 3: Implement safe save and open**
|
||||
|
||||
Register urls.openExternal as dangerous. Validate HTTP(S), invoke the existing
|
||||
OS opener with the URL string, and expose api.urls.openExternal. Browser Inbox
|
||||
requests that permission, creates Links through files.createFolder and
|
||||
files.writeText using:
|
||||
|
||||
~~~text
|
||||
[InternetShortcut]
|
||||
URL=https://example.test/
|
||||
~~~
|
||||
|
||||
Sanitize a 96-character filename stem. On collision show an editable dialog
|
||||
with proposed name (2).url and Cancel; leave capture state unchanged on errors.
|
||||
When opening a saved link, parse and validate URL= before invoking the URL API.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop && GOCACHE=/tmp/verstak-go-cache go test ./internal/api -count=1
|
||||
cd /home/mirivlad/git/verstak2/verstak-official-plugins && node scripts/smoke-browser-inbox-plugin.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop add internal/core/permissions internal/api frontend/src/lib/plugin-host
|
||||
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "feat: open external URLs safely"
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins add plugins/browser-inbox scripts/smoke-browser-inbox-plugin.js
|
||||
git -C /home/mirivlad/git/verstak2/verstak-official-plugins commit -m "feat: save browser inbox links safely"
|
||||
~~~
|
||||
|
||||
Expected: URL API tests and link save/collision smoke tests pass.
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
# Alpha Shell UX 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:** Make the Desktop overview scoped and actionable, hide diagnostic plugin IDs by default, and remove alpha UI strings that expose implementation noise.
|
||||
|
||||
**Architecture:** The shell consumes stable workspace IDs and plugin capabilities rather than hard-coded namespaces. Overview builds deterministic case-scoped projections; debug display is a presentation preference plus a session-only command-line override.
|
||||
|
||||
**Tech Stack:** Svelte, Wails app settings, Go tests, Playwright E2E, existing i18n catalogs.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Default UI labels are Russian and use Дела, Входящие, Активности, Журнал.
|
||||
- A normal tab never shows a plugin ID.
|
||||
- Settings Debug Show plugin IDs defaults false; Desktop --debug enables it only for that run.
|
||||
- Overview never leaks an unscoped event into every case.
|
||||
- Todo entries are included only if todo.workspace capability is loaded.
|
||||
- No action opens or creates a Дело implicitly.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add effective debug display setting and human plugin tab labels
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/main.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/appsettings/manager.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/appsettings/manager_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/shell/WorkspaceHost.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/plugin-manager/PluginManager.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/i18n/catalogs/en.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/i18n/catalogs/ru.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/plugin-api-bridge.spec.js
|
||||
|
||||
**Interfaces:**
|
||||
- Persisted app setting showPluginIds bool defaults false.
|
||||
- GetAppSettings returns effectiveShowPluginIds and UpdateAppSettings accepts showPluginIds.
|
||||
- Runtime --debug sets effectiveShowPluginIds true without persisting it.
|
||||
|
||||
- [ ] **Step 1: Write failing app-setting and E2E tests**
|
||||
|
||||
Add a Go test proving the setting preserves theme and language, plus a browser
|
||||
test proving normal mode has no raw ID and debug mode has one:
|
||||
|
||||
~~~js
|
||||
await expect(page.locator('[role="tab"]').filter({ hasText: 'verstak.' })).toHaveCount(0);
|
||||
await mockAppSettings({ showPluginIds: true });
|
||||
await expect(page.locator('[role="tab"]').filter({ hasText: 'verstak.default-editor' })).toHaveCount(1);
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/appsettings ./internal/api -run 'Test.*PluginIds' -count=1
|
||||
cd frontend && npm run test:e2e -- plugin-api-bridge.spec.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because no display preference exists.
|
||||
|
||||
- [ ] **Step 3: Implement presentation-only debug state**
|
||||
|
||||
Parse --debug before Wails startup and keep it in App runtime state. Merge it
|
||||
with the persisted bool when returning effective settings. Render the localized
|
||||
manifest title normally; append the plugin ID only when effectiveShowPluginIds
|
||||
is true. Add the Debug settings checkbox and localized explanatory copy.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/appsettings ./internal/api -count=1
|
||||
cd frontend && npm run test:e2e -- plugin-api-bridge.spec.js
|
||||
git add main.go internal/core/appsettings internal/api frontend/src frontend/e2e/plugin-api-bridge.spec.js
|
||||
git commit -m "feat: hide plugin IDs outside debug mode"
|
||||
~~~
|
||||
|
||||
Expected: normal and debug labels are both verified.
|
||||
|
||||
### Task 2: Implement deterministic case-scoped Overview
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/shell/TodaySurface.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/shell/WorkspaceHost.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/i18n/catalogs/en.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/i18n/catalogs/ru.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/ux-today.spec.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/ux-p0.spec.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/test/wails-mock.js
|
||||
|
||||
**Interfaces:**
|
||||
- Every overview row has workspaceId and lastMeaningfulAt.
|
||||
- Needs attention maximum is five; Continue work maximum is four; Recent changes maximum is eight.
|
||||
- Hidden recommendation state is keyed by entity ID and invalidated by later meaningful change.
|
||||
|
||||
- [ ] **Step 1: Write failing scope and ranking tests**
|
||||
|
||||
Add fixture events for two workspace IDs plus an unscoped event:
|
||||
|
||||
~~~js
|
||||
await seedOverview({
|
||||
selectedWorkspaceId: 'client-a',
|
||||
events: [
|
||||
{ activityId: 'a', workspaceId: 'client-a', type: 'note.saved', occurredAt: now },
|
||||
{ activityId: 'b', workspaceId: 'client-b', type: 'file.changed', occurredAt: now },
|
||||
{ activityId: 'global', type: 'workspace.selected', occurredAt: now }
|
||||
]
|
||||
});
|
||||
await expect(page.getByText(/client-b/i)).toHaveCount(0);
|
||||
await expect(page.locator('[data-overview-section="continue"] [data-overview-item]')).toHaveCount(4);
|
||||
~~~
|
||||
|
||||
Also assert no selection shows only the select/create prompt and up to five
|
||||
unassigned active Inbox captures.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend
|
||||
npm run test:e2e -- ux-today.spec.js ux-p0.spec.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL because rowsFor currently accepts untagged events for every workspace.
|
||||
|
||||
- [ ] **Step 3: Implement exact overview projections**
|
||||
|
||||
Replace path-only filtering with workspaceId equality. Build:
|
||||
|
||||
1. Needs attention: ready Journal candidates, active unprocessed captures, then
|
||||
urgent Todo rows only if todo.workspace capability exists; take five.
|
||||
2. Continue work: distinct entities updated in 14 days; sort by
|
||||
lastMeaningfulAt descending and the documented type tie-break; take four.
|
||||
3. Recent changes: note save/create, file create/rename or last change per
|
||||
file, saved link, and Journal create in seven days; take eight.
|
||||
|
||||
Persist Hide recommendation without changing underlying data. Use localized
|
||||
empty states and remove English technical strings.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
npm run test:e2e -- ux-today.spec.js ux-p0.spec.js
|
||||
git add src/lib/shell/TodaySurface.svelte src/lib/shell/WorkspaceHost.svelte src/lib/i18n frontend/e2e/ux-today.spec.js frontend/e2e/ux-p0.spec.js src/lib/test/wails-mock.js
|
||||
git commit -m "feat: make overview case-scoped and actionable"
|
||||
~~~
|
||||
|
||||
Expected: limits, scope, empty state, and recommendation hide tests pass.
|
||||
|
||||
### Task 3: Verify alpha UX as a user sees it
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/ux-followup.spec.js
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/scripts/check-gui.sh
|
||||
|
||||
**Interfaces:**
|
||||
- E2E fixture uses effectiveShowPluginIds false by default.
|
||||
- GUI checker accepts a --debug-labels case.
|
||||
|
||||
- [ ] **Step 1: Write the final failing alpha scenarios**
|
||||
|
||||
Add a scenario that creates a capture, assigns it, archives it globally, opens
|
||||
the assigned case, and verifies the capture is still present. Add a session
|
||||
candidate path to Journal and assert normal tab labels are human-readable.
|
||||
|
||||
~~~js
|
||||
await archiveGlobalCapture('capture-1');
|
||||
await openWorkspace('client-a');
|
||||
await expect(page.getByTestId('browser-capture-capture-1')).toBeVisible();
|
||||
await expect(page.getByRole('tab', { name: /verstak\./i })).toHaveCount(0);
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
npm run test:e2e -- ux-followup.spec.js
|
||||
~~~
|
||||
|
||||
Expected: FAIL until the prior plans are integrated.
|
||||
|
||||
- [ ] **Step 3: Add deterministic GUI smoke commands**
|
||||
|
||||
Extend check-gui.sh to start an isolated test vault, run normal and --debug
|
||||
screens, and save screenshots under frontend/e2e-results. The script must not
|
||||
read or mutate the user's normal vault.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
npm run test:e2e -- ux-followup.spec.js
|
||||
GOCACHE=/tmp/verstak-go-cache ./scripts/check-gui.sh
|
||||
git add frontend/e2e/ux-followup.spec.js scripts/check-gui.sh
|
||||
git commit -m "test: cover first alpha UX smoke flow"
|
||||
~~~
|
||||
|
||||
Expected: E2E and isolated GUI smoke pass.
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
# Alpha Workspace Identity 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:** Give every managed Дело a durable UUID identity and publish lifecycle events that let alpha features follow a case across rename, Trash, restore, and path reuse.
|
||||
|
||||
**Architecture:** The filesystem remains the source of truth for whether a case folder exists. A UUID marker inside each case is the relation identity; Desktop metadata indexes that marker and paths remain presentation caches.
|
||||
|
||||
**Tech Stack:** Go, Wails bindings, JSON metadata, Go tests.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- A new Дело is created only by an explicit user action.
|
||||
- workspaceId is UUID v4; a path is never relation identity.
|
||||
- The marker is .verstak/workspace.json inside the case.
|
||||
- Existing writable cases migrate without data loss; non-writable cases are view-only.
|
||||
- Use TDD and make one commit per task.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Store and resolve durable workspace identities
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager_test.go
|
||||
|
||||
**Interfaces:**
|
||||
- Produces Workspace.ID string and Metadata.WorkspaceID string.
|
||||
- Produces func (m *Manager) EnsureWorkspaceIdentity(name string) (WorkspaceIdentity, error).
|
||||
- Produces func (m *Manager) ListWorkspaceIdentities() ([]WorkspaceIdentity, error).
|
||||
|
||||
- [ ] **Step 1: Write the failing identity tests**
|
||||
|
||||
Add tests proving creation writes a UUID marker, rename retains it, and a newly
|
||||
created folder after external removal gets another UUID:
|
||||
|
||||
~~~go
|
||||
ws, err := m.CreateWorkspace("Clients", "default")
|
||||
if err != nil { t.Fatal(err) }
|
||||
first, err := m.EnsureWorkspaceIdentity(ws.Name)
|
||||
if err != nil || first.WorkspaceID == "" { t.Fatalf("identity = %+v, %v", first, err) }
|
||||
if err := m.RenameWorkspace("Clients", "Clients-2026"); err != nil { t.Fatal(err) }
|
||||
renamed, _ := m.EnsureWorkspaceIdentity("Clients-2026")
|
||||
if renamed.WorkspaceID != first.WorkspaceID { t.Fatal("rename changed workspace ID") }
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run the focused test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
cd /home/mirivlad/git/verstak2/verstak-desktop
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace -run 'Test.*Workspace.*Identity' -count=1
|
||||
~~~
|
||||
|
||||
Expected: FAIL because WorkspaceIdentity and EnsureWorkspaceIdentity do not exist.
|
||||
|
||||
- [ ] **Step 3: Write the minimal marker implementation**
|
||||
|
||||
Add these types and use them in CreateWorkspace, GetWorkspaceMetadata, and
|
||||
workspace listing:
|
||||
|
||||
~~~go
|
||||
type WorkspaceIdentity struct {
|
||||
WorkspaceID string `json:"workspaceId"`
|
||||
RootPath string `json:"rootPath"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
const workspaceIdentityRelativePath = ".verstak/workspace.json"
|
||||
|
||||
func (m *Manager) EnsureWorkspaceIdentity(name string) (WorkspaceIdentity, error) {
|
||||
// Validate the folder, read the in-folder marker, and atomically create
|
||||
// a UUID-v4 marker only when absent and writable.
|
||||
}
|
||||
~~~
|
||||
|
||||
Write the same ID to central metadata only as an index. Never recover an ID from
|
||||
stale path-keyed metadata when the in-folder marker is absent.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace -count=1
|
||||
git add internal/core/workspace/manager.go internal/core/workspace/manager_test.go
|
||||
git commit -m "feat: add durable workspace identities"
|
||||
~~~
|
||||
|
||||
Expected: tests pass and the commit contains only workspace identity changes.
|
||||
|
||||
### Task 2: Expose identity and lifecycle events through Desktop
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/App.svelte
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/wailsjs/go/api/App.d.ts
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/wailsjs/go/models.ts
|
||||
|
||||
**Interfaces:**
|
||||
- Produces ListWorkspaceIdentities() ([]workspace.WorkspaceIdentity, string).
|
||||
- Lifecycle payloads include workspaceId and workspaceRootPath; Trash also includes trashId.
|
||||
- Produces workspace.restored and workspace.purged events.
|
||||
|
||||
- [ ] **Step 1: Add failing API tests**
|
||||
|
||||
Extend TestWorkspaceAPIPublishesLifecycleEvents:
|
||||
|
||||
~~~go
|
||||
if got := received["workspace.created"]["workspaceId"]; got == "" {
|
||||
t.Fatal("workspace.created must include workspaceId")
|
||||
}
|
||||
if got := received["workspace.renamed"]["workspaceId"]; got != createdID {
|
||||
t.Fatalf("rename ID = %v, want %s", got, createdID)
|
||||
}
|
||||
~~~
|
||||
|
||||
Add restore and purge tests around RestoreVaultTrash and DeleteVaultTrash that
|
||||
assert the original UUID and trash ID are emitted.
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/api -run 'TestWorkspaceAPI.*Lifecycle' -count=1
|
||||
~~~
|
||||
|
||||
Expected: FAIL because lifecycle payloads only identify a path.
|
||||
|
||||
- [ ] **Step 3: Publish complete identity lifecycle**
|
||||
|
||||
Resolve the marker before publishing create, rename, selected, and trashed
|
||||
events. Detect workspace Trash metadata in RestoreVaultTrash and
|
||||
DeleteVaultTrash, then publish:
|
||||
|
||||
~~~go
|
||||
map[string]interface{}{
|
||||
"operation": "restore",
|
||||
"workspaceId": identity.WorkspaceID,
|
||||
"workspaceRootPath": restoredRoot,
|
||||
"trashId": trashID,
|
||||
}
|
||||
~~~
|
||||
|
||||
Change App.svelte workspace-node conversion to preserve workspace.id while using
|
||||
rootPath only for selection and display.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/api ./internal/core/workspace -count=1
|
||||
git add internal/api/app.go internal/api/app_test.go frontend/src/App.svelte frontend/wailsjs/go/api/App.d.ts frontend/wailsjs/go/models.ts
|
||||
git commit -m "feat: publish workspace identity lifecycle"
|
||||
~~~
|
||||
|
||||
Expected: focused API and workspace tests pass.
|
||||
|
||||
### Task 3: Migrate legacy references and repair duplicate IDs
|
||||
|
||||
**Files:**
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/workspace/manager_test.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
|
||||
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
|
||||
|
||||
**Interfaces:**
|
||||
- Produces RepairWorkspaceIdentity(keepRootPath, regenerateRootPath string) string.
|
||||
- Identity states are active, unavailable, or duplicate.
|
||||
- Later Inbox and Activity plans consume workspaceId through ListWorkspaceIdentities.
|
||||
|
||||
- [ ] **Step 1: Write failing duplicate and legacy tests**
|
||||
|
||||
Create two folders with copied marker data and assert neither is an active
|
||||
relation target. Create a legacy path-keyed capture fixture and assert a
|
||||
resolved path gets an ID while a missing path becomes unavailable:
|
||||
|
||||
~~~go
|
||||
if errStr := app.RepairWorkspaceIdentity("Original", "Copied"); errStr != "" {
|
||||
t.Fatal(errStr)
|
||||
}
|
||||
if originalID == copiedID { t.Fatal("repair must issue a new ID") }
|
||||
~~~
|
||||
|
||||
- [ ] **Step 2: Run red**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace ./internal/api -run 'Test.*(Duplicate|Legacy).*Workspace' -count=1
|
||||
~~~
|
||||
|
||||
Expected: FAIL because copied markers are accepted as ordinary folders.
|
||||
|
||||
- [ ] **Step 3: Implement deterministic repair**
|
||||
|
||||
Group active roots by marker UUID during identity listing. Mark a group with
|
||||
more than one root duplicate and exclude it from assignment targets.
|
||||
RepairWorkspaceIdentity verifies the duplicate, keeps the first marker, creates
|
||||
a new marker for the second root, and moves no relation data.
|
||||
|
||||
- [ ] **Step 4: Run green and commit**
|
||||
|
||||
Run:
|
||||
|
||||
~~~bash
|
||||
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/workspace ./internal/api -count=1
|
||||
git add internal/core/workspace/manager.go internal/core/workspace/manager_test.go internal/api/app.go internal/api/app_test.go
|
||||
git commit -m "feat: repair duplicate workspace identities"
|
||||
~~~
|
||||
|
||||
Expected: legacy, path-reuse, and duplicate-ID tests pass.
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,384 +0,0 @@
|
|||
# Platform Localization Design
|
||||
|
||||
## Context
|
||||
|
||||
Verstak v2 currently renders most desktop shell and official plugin text in
|
||||
English. Application settings do not store a language, the frontend plugin API
|
||||
has no localization contract, and plugin manifests expose only literal English
|
||||
metadata. The sync server already has separate Russian and English catalogs and
|
||||
is outside the scope of this desktop milestone. The browser extension also has
|
||||
its own lifecycle and is explicitly deferred to a later milestone.
|
||||
|
||||
The first localization milestone covers:
|
||||
|
||||
- the `verstak-desktop` shell and core-owned screens;
|
||||
- every plugin in `verstak-official-plugins`, including `platform-test`;
|
||||
- the public SDK and manifest contract required by future third-party plugins;
|
||||
- Russian and English;
|
||||
- a persisted language selector in the desktop settings menu.
|
||||
|
||||
The existing `~/git/verstak` project remains a visual reference only. Its code
|
||||
and localization architecture are not inputs to this implementation.
|
||||
|
||||
## Product Decisions
|
||||
|
||||
- The stored language preference is `system`, `ru`, or `en`.
|
||||
- A new installation defaults to `system`.
|
||||
- System locales beginning with `ru` resolve to Russian; all other system
|
||||
locales resolve to English.
|
||||
- The settings menu offers System, Russian, and English choices.
|
||||
- Changing the preference updates the running UI without restarting the app.
|
||||
- The preference is installation-local and is not stored in or synchronized
|
||||
with a vault.
|
||||
- Browser extension localization is a separate follow-up.
|
||||
- Sync server localization remains independent from the desktop preference.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Recommended: platform contract with plugin-owned catalogs
|
||||
|
||||
The shell owns shell translations. Each plugin owns its catalogs and declares
|
||||
them in its manifest. The public plugin API exposes the resolved locale,
|
||||
translation, and locale-change subscription.
|
||||
|
||||
Trade-offs:
|
||||
|
||||
- preserves repository and plugin ownership boundaries;
|
||||
- gives third-party plugins the same mechanism as official plugins;
|
||||
- requires coordinated SDK, desktop, manifest, and plugin changes;
|
||||
- avoids teaching desktop core the UI vocabulary of official plugins.
|
||||
|
||||
### One desktop-wide catalog
|
||||
|
||||
Desktop would contain translations for shell and all official plugins.
|
||||
|
||||
Trade-offs:
|
||||
|
||||
- initially faster;
|
||||
- couples core releases to plugin text and IDs;
|
||||
- prevents independently distributed plugins from owning their localization;
|
||||
- contradicts the dynamic plugin architecture.
|
||||
|
||||
### Independent dictionaries without a platform API
|
||||
|
||||
Every plugin would read a global value or DOM event and implement translation
|
||||
on its own.
|
||||
|
||||
Trade-offs:
|
||||
|
||||
- small initial runtime change;
|
||||
- inconsistent fallback and interpolation behavior;
|
||||
- no stable SDK contract for third-party plugins;
|
||||
- makes live locale changes and manifest metadata unreliable.
|
||||
|
||||
## Chosen Architecture
|
||||
|
||||
Use the platform contract with plugin-owned catalogs.
|
||||
|
||||
Localization is a UI platform service, not a user feature plugin. Desktop owns
|
||||
language preference resolution, catalog loading, shell translation, and the
|
||||
generic runtime bridge. It does not own plugin message content.
|
||||
|
||||
The data flow is:
|
||||
|
||||
```text
|
||||
app config language preference
|
||||
|
|
||||
v
|
||||
desktop locale store -----> shell catalogs
|
||||
|
|
||||
+------------------> localized manifest contributions
|
||||
|
|
||||
+------------------> api.i18n for each plugin
|
||||
|
|
||||
v
|
||||
plugin-owned catalogs
|
||||
```
|
||||
|
||||
## Application Setting
|
||||
|
||||
Add `Language string` to the desktop application config and expose it as
|
||||
`language` through `GetAppSettings` and `UpdateAppSettings`.
|
||||
|
||||
Accepted persisted values:
|
||||
|
||||
```text
|
||||
system
|
||||
ru
|
||||
en
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- missing or empty values load as `system`;
|
||||
- unknown values are rejected by the update API and never persisted;
|
||||
- an existing config requires no schema migration beyond applying the missing
|
||||
default;
|
||||
- updating language must not reset `devMode`, theme, workbench preferences, or
|
||||
other unrelated settings;
|
||||
- `system` is resolved in the frontend from `navigator.languages` or
|
||||
`navigator.language`, with `en` as the safe fallback.
|
||||
|
||||
The settings menu renders language names in their own language so the user can
|
||||
recover from an accidental switch:
|
||||
|
||||
```text
|
||||
System / Системный
|
||||
English
|
||||
Русский
|
||||
```
|
||||
|
||||
The active option is visibly marked and uses menu radio semantics.
|
||||
|
||||
## Desktop Localization Service
|
||||
|
||||
Add one small frontend localization module that owns:
|
||||
|
||||
- the stored preference;
|
||||
- the resolved locale (`ru` or `en`);
|
||||
- the English and Russian shell catalogs;
|
||||
- loaded plugin catalogs keyed by plugin ID and locale;
|
||||
- a subscription API for Svelte shell components and plugin bridges;
|
||||
- named parameter interpolation.
|
||||
|
||||
The shell-facing API is synchronous after initialization:
|
||||
|
||||
```js
|
||||
getLanguagePreference()
|
||||
getLocale()
|
||||
t(key, params?, fallback?)
|
||||
setLanguagePreference(preference)
|
||||
subscribe(listener)
|
||||
loadPluginCatalog(pluginId, localizationConfig)
|
||||
translatePlugin(pluginId, key, params?, fallback?)
|
||||
```
|
||||
|
||||
Application startup loads the persisted preference before the main interactive
|
||||
shell is shown. Catalog-loading failures do not prevent startup.
|
||||
|
||||
Parameter interpolation supports named placeholders such as `{count}`. Missing
|
||||
parameters leave the placeholder visible so catalog mistakes are detectable.
|
||||
Catalog strings are always assigned through existing safe text rendering APIs;
|
||||
translation values are not treated as HTML.
|
||||
|
||||
## Plugin Manifest Contract
|
||||
|
||||
Add an optional top-level manifest field:
|
||||
|
||||
```json
|
||||
{
|
||||
"localization": {
|
||||
"defaultLocale": "en",
|
||||
"locales": {
|
||||
"en": "locales/en.json",
|
||||
"ru": "locales/ru.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The field belongs at the top level because manifest metadata and contributions
|
||||
can be localized even when a plugin has no frontend bundle.
|
||||
|
||||
Validation rules:
|
||||
|
||||
- `defaultLocale` and locale map keys use lower-case supported locale tags;
|
||||
- the default locale must have a declared catalog;
|
||||
- catalog paths must be plugin-relative safe paths;
|
||||
- absolute paths, traversal, backslashes, and paths outside the plugin root are
|
||||
rejected;
|
||||
- localization is optional, preserving compatibility with existing and
|
||||
third-party plugins.
|
||||
|
||||
Catalog files are flat JSON string maps. Official plugins provide both `en`
|
||||
and `ru` catalogs. The English manifest literals stay in place as readable
|
||||
fallbacks and for compatibility with hosts that do not implement localization.
|
||||
|
||||
## Manifest And Contribution Keys
|
||||
|
||||
Desktop derives stable keys rather than embedding translation tokens in fields.
|
||||
|
||||
Reserved keys:
|
||||
|
||||
```text
|
||||
manifest.name
|
||||
manifest.description
|
||||
contributions.views.<id>.title
|
||||
contributions.commands.<id>.title
|
||||
contributions.settingsPanels.<id>.title
|
||||
contributions.sidebarItems.<id>.title
|
||||
contributions.fileActions.<id>.title
|
||||
contributions.noteActions.<id>.title
|
||||
contributions.contextMenuEntries.<id>.title
|
||||
contributions.searchProviders.<id>.label
|
||||
contributions.statusBarItems.<id>.label
|
||||
contributions.workspaceItems.<id>.title
|
||||
```
|
||||
|
||||
The complete set follows contribution fields defined by the SDK schema. IDs
|
||||
are used verbatim after the contribution type prefix. Missing keys retain the
|
||||
literal manifest value.
|
||||
|
||||
The desktop localizes copies returned to frontend presentation code; it does
|
||||
not mutate the backend registry or change IDs, handlers, capabilities, or
|
||||
permission logic.
|
||||
|
||||
## Plugin Runtime API
|
||||
|
||||
Extend `VerstakPluginAPI` with:
|
||||
|
||||
```ts
|
||||
i18n: {
|
||||
getLocale(): 'ru' | 'en';
|
||||
t(key: string, params?: Record<string, string | number>, fallback?: string): string;
|
||||
onDidChangeLocale(listener: (locale: 'ru' | 'en') => void): Unsubscribe;
|
||||
}
|
||||
```
|
||||
|
||||
The desktop preloads a plugin catalog before mounting its component. `t` is
|
||||
therefore synchronous during rendering. The API automatically disposes locale
|
||||
subscriptions with the rest of the plugin API.
|
||||
|
||||
Official plugin components subscribe once when mounted and rerender only their
|
||||
text when the locale changes. Locale changes must not clear form values,
|
||||
selection, dirty editor state, loaded data, or navigation state. The host does
|
||||
not solve localization by unmounting and remounting plugin components.
|
||||
|
||||
Plugins without localization metadata continue to work. Their `t` calls return
|
||||
the supplied fallback or key, and their literal manifest metadata remains
|
||||
visible.
|
||||
|
||||
## Catalog Loading Boundary
|
||||
|
||||
Desktop exposes a dedicated read-only backend method for a declared plugin
|
||||
catalog. It locates the enabled/discovered plugin, selects only a path declared
|
||||
by its localization manifest, validates containment within the plugin root, and
|
||||
returns a parsed string map or an error.
|
||||
|
||||
Do not repurpose arbitrary plugin settings or make localization catalogs
|
||||
writable at runtime. Do not give plugins access to another plugin's catalog.
|
||||
|
||||
Catalog errors are isolated:
|
||||
|
||||
- malformed selected-locale catalog: fall back to default locale;
|
||||
- missing default catalog: use manifest literals and key/fallback values;
|
||||
- one broken plugin catalog: report a diagnostic without affecting other
|
||||
plugins or the shell.
|
||||
|
||||
## Fallback Rules
|
||||
|
||||
Shell translation fallback:
|
||||
|
||||
```text
|
||||
resolved locale -> English catalog -> explicit fallback -> key
|
||||
```
|
||||
|
||||
Plugin translation fallback:
|
||||
|
||||
```text
|
||||
resolved locale -> plugin default locale -> explicit fallback -> key
|
||||
```
|
||||
|
||||
Manifest/contribution fallback:
|
||||
|
||||
```text
|
||||
resolved locale -> plugin default locale -> original manifest literal
|
||||
```
|
||||
|
||||
Russian and English catalogs must have the same keys in CI for the shell and
|
||||
official plugins. Third-party plugins may provide only their default locale.
|
||||
|
||||
## Scope Of Translated UI
|
||||
|
||||
The first milestone translates:
|
||||
|
||||
- vault onboarding and shell navigation;
|
||||
- status bar, settings menu, command palette, global search chrome, dialogs,
|
||||
plugin host states, and Plugin Manager;
|
||||
- all normal, empty, loading, confirmation, warning, and known validation
|
||||
states in official plugins;
|
||||
- official plugin names, descriptions, contribution titles, and labels;
|
||||
- accessibility labels and tooltips owned by shell or official plugins.
|
||||
|
||||
Arbitrary backend, operating-system, filesystem, network, and third-party error
|
||||
messages remain in their source language. The surrounding user-facing prefix
|
||||
and recovery instruction are translated. Stable known error codes may receive
|
||||
localized messages in later milestones.
|
||||
|
||||
User data, filenames, workspace names, provider values, logs, identifiers, API
|
||||
names, and developer diagnostics are never translated.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Use TDD for each production change.
|
||||
|
||||
### SDK
|
||||
|
||||
- manifest schema accepts valid localization declarations;
|
||||
- schema rejects unsafe or incomplete declarations;
|
||||
- TypeScript types expose localization metadata and `api.i18n`;
|
||||
- mock API implements locale reads, translations, and subscriptions;
|
||||
- interpolation and fallback behavior are covered.
|
||||
|
||||
### Desktop backend
|
||||
|
||||
- missing language defaults to `system`;
|
||||
- all three accepted values persist and reload;
|
||||
- invalid values are rejected;
|
||||
- language updates preserve unrelated settings;
|
||||
- catalog reads accept only declared safe paths;
|
||||
- malformed, missing, and traversal catalog cases fail safely.
|
||||
|
||||
### Desktop frontend
|
||||
|
||||
- system locale resolution maps `ru-*` to `ru` and others to `en`;
|
||||
- shell fallback and interpolation are deterministic;
|
||||
- language menu shows and persists the selected option;
|
||||
- changing language updates visible shell text without reload;
|
||||
- plugin API receives the locale and a working catalog;
|
||||
- contribution labels change without changing contribution identity;
|
||||
- missing plugin catalogs preserve English manifest literals.
|
||||
|
||||
### Official plugins
|
||||
|
||||
- every official manifest declares English and Russian catalogs;
|
||||
- catalog key parity is checked;
|
||||
- every manifest/contribution localization key is present;
|
||||
- focused smoke tests exercise English and Russian rendering;
|
||||
- a mounted component reacts to a locale change without losing its state.
|
||||
|
||||
### End-to-end
|
||||
|
||||
- first launch with a mocked Russian system locale renders Russian;
|
||||
- first launch with another locale renders English;
|
||||
- English -> Russian -> System selection persists through reload;
|
||||
- shell and representative plain-JS and Svelte plugins switch together;
|
||||
- plugin enable/disable and failed-plugin isolation still work.
|
||||
|
||||
## Rollout Order
|
||||
|
||||
1. SDK manifest/types/mock contract.
|
||||
2. Desktop setting and catalog security boundary.
|
||||
3. Desktop localization service and language menu.
|
||||
4. Desktop shell catalog migration.
|
||||
5. Official plugin manifests, catalogs, and runtime UI migration.
|
||||
6. Full unit, smoke, build, and mocked-Wails E2E verification.
|
||||
7. Real Wails/WebKit GUI smoke when the native environment is available.
|
||||
|
||||
The work remains split into small commits per repository. Existing unrelated
|
||||
changes, including generated Wails bindings already present in the desktop
|
||||
worktree, must not be included.
|
||||
|
||||
## Follow-up Work
|
||||
|
||||
After this localization milestone:
|
||||
|
||||
1. add browser-extension `_locales` catalogs and its independent language
|
||||
behavior;
|
||||
2. introduce stable workspace IDs so renames do not orphan plugin scopes;
|
||||
3. scope sync blobs by tenant/vault and make operation-sequence writes
|
||||
transactional;
|
||||
4. make concurrent settings updates lossless;
|
||||
5. remove remaining hard-coded official plugin IDs and schemas from shell;
|
||||
6. reduce drift between E2E mock bundles and real plugin packages;
|
||||
7. include whole-workspace entries in global Trash where appropriate.
|
||||
|
|
@ -1,510 +0,0 @@
|
|||
# First Alpha Product UX Design
|
||||
|
||||
## Status and scope
|
||||
|
||||
This is the approved product-UX tranche for the first public alpha. It covers
|
||||
the Desktop application, official Activity, Journal and Browser Inbox plugins,
|
||||
and the browser extension. Release packaging, public repository documentation,
|
||||
licensing, and sync-server hardening are specified separately in the
|
||||
`alpha-release` tranche.
|
||||
|
||||
The user-visible Russian term is **Дело**. Existing platform and storage names
|
||||
such as `workspaceRootPath` remain internal compatibility details in this
|
||||
tranche.
|
||||
|
||||
## Durable identity of a Дело
|
||||
|
||||
Every managed Дело has an immutable UUID v4 `workspaceId`. It is the identity for
|
||||
relations; `workspaceRootPath` is only the current or historical filesystem
|
||||
address and presentation label. Inbox assignments, domain bindings, Activity
|
||||
events/sessions/candidates, Journal source references, and Overview state store
|
||||
`workspaceId` as their primary relation key and retain a path only as a cached
|
||||
display value.
|
||||
|
||||
The UUID lives in a small immutable marker inside the case folder,
|
||||
`<Дело>/.verstak/workspace.json`, and is also indexed in Desktop metadata. The
|
||||
inside-folder marker survives a Desktop rename, Trash move, restore, and an
|
||||
external filesystem rename. It prevents a newly created folder with the same
|
||||
path from inheriting old links.
|
||||
|
||||
On first alpha startup, each writable legacy or externally created top-level
|
||||
case without a marker receives a new UUID and its path-keyed relation data is
|
||||
migrated to that UUID. If a folder is not writable, it remains viewable but
|
||||
cannot be selected as a durable relation target until its marker can be
|
||||
created. If two active folders contain the same UUID (for example after a
|
||||
filesystem copy), Desktop shows an identity-repair action and does not
|
||||
automatically attach Inbox, binding, or Activity data to either duplicate.
|
||||
The repair UI asks which folder retains the existing identity; it generates a
|
||||
new marker UUID for the other folder and leaves old relations with the retained
|
||||
identity. It never silently merges the two folders' histories.
|
||||
|
||||
Workspace lifecycle events carry both `workspaceId` and current path. A rename
|
||||
updates only the path cache. Trash and restore retain the same ID and use the
|
||||
trash ID only to match the particular trash operation. When a folder is removed
|
||||
outside Desktop, its relations retain the UUID and become unavailable; a new
|
||||
folder at the old path has a newly generated UUID and cannot take them over.
|
||||
|
||||
This document supersedes conflicting decisions in the following older narrow
|
||||
designs:
|
||||
|
||||
- `2026-06-29-activity-worklog-suggestions-design.md` for candidate lifecycle
|
||||
and background availability;
|
||||
- `2026-06-29-browser-inbox-domain-binding-design.md` for the relation between
|
||||
bindings, global Inbox, and browser activity;
|
||||
- `2026-07-11-platform-localization-design.md` only where it would leave
|
||||
developer-facing plugin IDs visible in the normal user interface.
|
||||
|
||||
## Product decisions
|
||||
|
||||
- A new Дело is created **only** by an explicit user action. Browser Inbox,
|
||||
Activity, and Journal never create a case, candidate case, or implicit case.
|
||||
- A manual extension send is a Browser Inbox capture. Passive browsing activity
|
||||
is never an Inbox capture.
|
||||
- Passive browser activity records only a normalized hostname and measured
|
||||
duration. It never records a page URL, title, selection, page content,
|
||||
keystrokes, or browsing history.
|
||||
- Passive browser tracking is disabled on a new installation. It starts only
|
||||
after explicit, informed consent in extension settings.
|
||||
- Passive browser activity counts only the active tab in a focused browser
|
||||
window. Background tabs and unfocused browser windows contribute no time.
|
||||
- Activity can suggest a Journal record, but the user reviews and saves it.
|
||||
Journal entries are never created automatically.
|
||||
- An unassigned activity can be saved only after the user picks an existing
|
||||
Дело. Picking it for a single Journal entry does not silently create a domain
|
||||
binding.
|
||||
- The first alpha retains one optional Дело assignment per Browser Inbox
|
||||
capture, matching current product semantics. It does not introduce sharing a
|
||||
capture between several cases.
|
||||
|
||||
## Browser extension: manual capture and passive activity
|
||||
|
||||
### Manual capture
|
||||
|
||||
The existing explicit popup/context-menu actions remain the only way to create
|
||||
`browser.capture.*` events and Browser Inbox records. Their protocol and
|
||||
retry queue remain separate from passive activity data.
|
||||
|
||||
### Domain activity tracker
|
||||
|
||||
Passive tracking is an opt-in extension setting, `passiveActivityEnabled`, with
|
||||
a default of `false`. The first extension settings view includes a concise
|
||||
consent card and an unchecked switch. It states that the feature sends only a
|
||||
normalized hostname and duration, and explicitly states that it does **not**
|
||||
collect or send URLs, titles, page content, selections, keystrokes, or browser
|
||||
history. The same explanation remains next to the switch after onboarding.
|
||||
|
||||
Until the user enables that switch, the extension does not subscribe to
|
||||
tracking events, create activity state, or send activity records. Disabling it
|
||||
stops tracking immediately, clears only the mutable unflushed accumulator, and
|
||||
leaves already acknowledged Desktop activity unchanged. The user can either
|
||||
retry or discard already-created pending batches through an explicit settings
|
||||
action; disabling tracking never silently loses them.
|
||||
|
||||
When enabled, the extension uses a persisted tracker:
|
||||
|
||||
1. When an HTTP(S) page becomes the active tab of a focused browser window,
|
||||
start timing its normalized lowercase hostname.
|
||||
2. On tab activation, hostname change, window focus loss, browser idle/lock,
|
||||
or a five-minute alarm, write a checkpoint and calculate elapsed time. The
|
||||
idle detection threshold is explicitly ten minutes; a locked state pauses
|
||||
immediately.
|
||||
3. Ignore browser-internal pages, invalid URLs, and excluded hostnames. An
|
||||
exclusion `youtube.com` matches that hostname and every subdomain; the same
|
||||
rule applies to `x.com`.
|
||||
4. On a flush, freeze one record per hostname as a `browser.activity.domain`
|
||||
batch. The payload contains a schema version, idempotency ID, observed
|
||||
period bounds, hostname, and `durationSeconds`; it contains no URL-like
|
||||
field.
|
||||
|
||||
The persisted state has three distinct parts:
|
||||
|
||||
```text
|
||||
activeAccumulator: mutable, unsent duration and bounds by hostname
|
||||
pendingBatches: immutable payloads, keyed by idempotency ID
|
||||
acknowledgedIds: bounded recent acknowledgement IDs
|
||||
```
|
||||
|
||||
Flushing copies a hostname's current accumulator into a new immutable pending
|
||||
batch with a newly generated ID, then clears only that copied accumulator. New
|
||||
time for the same hostname accumulates in `activeAccumulator` and can become a
|
||||
later batch. It never mutates an already sent batch. Retries send the stored
|
||||
payload byte-for-byte. A successful acknowledgement removes only the matching
|
||||
pending batch and records its ID; it cannot remove newer time. Pending batches
|
||||
are sent oldest first. `acknowledgedIds` is a 30-day bounded LRU set used to
|
||||
handle replayed acknowledgement messages safely; Desktop maintains its own
|
||||
idempotency store as the authority.
|
||||
|
||||
The tracker uses timestamp arithmetic but applies a conservative ambiguity
|
||||
limit. A checkpoint contributes elapsed time only if wall-clock time is
|
||||
monotonic and the gap from the previous trustworthy checkpoint is no more than
|
||||
ten minutes. A negative clock delta, a gap over ten minutes, an idle/locked
|
||||
state, or a browser startup after a crash discards the ambiguous interval and
|
||||
establishes a fresh baseline. WebExtensions have no portable system
|
||||
suspend/resume event, so the first post-suspend observation is deliberately
|
||||
handled by this gap rule; platform idle/lock events add an earlier pause where
|
||||
available. This can undercount ambiguous work but cannot turn an overnight
|
||||
sleep or a clock change into working time. A browser startup never carries an
|
||||
active interval across process death; pending batches and accumulated completed
|
||||
intervals are retained.
|
||||
|
||||
The extension persists timestamps and the three state parts in local extension
|
||||
storage, so a Manifest V3 service-worker restart can continue safely. It sets
|
||||
the `idle` API detection interval to ten minutes. Its manifest gains the
|
||||
`windows`, `alarms`, and `idle` permissions needed for this flow; existing tab
|
||||
access is retained. Firefox uses the equivalent WebExtension events when
|
||||
available and otherwise applies the same ten-minute checkpoint limit.
|
||||
|
||||
The settings page gets an **Excluded domains** list. It accepts one hostname per
|
||||
item through the canonical normalization below, and explains that a hostname
|
||||
excludes its subdomains. The default list is empty. Adding an exclusion stops
|
||||
the matching active measurement immediately and discards only its mutable
|
||||
unflushed time; immutable pending batches remain available for the user's
|
||||
explicit retry or discard decision.
|
||||
|
||||
### Canonical hostname normalization
|
||||
|
||||
Every extension event, exclusion, and Desktop domain binding uses
|
||||
`hostname-normalization-v1`. It is a normative shared contract, not an
|
||||
implementation detail of one component:
|
||||
|
||||
1. An activity source must be an HTTP(S) URL. Its port, user info, path, query,
|
||||
and fragment are discarded before hostname normalization. A binding or
|
||||
exclusion must be a bare hostname; schemes, paths, queries, fragments, and
|
||||
ports are rejected.
|
||||
2. Trim surrounding whitespace, lowercase, and remove exactly one DNS root
|
||||
trailing dot. `example.com.` therefore becomes `example.com`.
|
||||
3. Convert DNS names using non-transitional UTS #46 / IDNA lookup processing to
|
||||
an ASCII A-label. The canonical stored and compared form of `пример.рф` is
|
||||
its punycode A-label; the settings UI may render the Unicode display form.
|
||||
4. IPv4 addresses are accepted in canonical dotted-decimal form. IPv6 literals
|
||||
are accepted (bracketed only at URL/settings input), stored without brackets
|
||||
in canonical lower-case RFC 5952 form, and never include a port. `localhost`
|
||||
and syntactically valid single-label internal names are also accepted.
|
||||
5. Reject empty values, malformed IP literals, invalid labels, empty labels,
|
||||
labels over 63 ASCII bytes, DNS names over 253 ASCII bytes, and any input
|
||||
that fails URL/IDNA parsing. Invalid activity is not counted; invalid
|
||||
settings input gets an inline validation error and is not saved.
|
||||
|
||||
The SDK owns a versioned `hostname-normalization-v1` test-vector corpus. Desktop
|
||||
and the browser extension vendor the byte-identical corpus in their tests; the
|
||||
coordinated `build-all` verification checks its hash. Go and JavaScript have
|
||||
separate implementations, but both must pass every vector, including trailing
|
||||
dots, Unicode/punycode equivalence, IPv4, IPv6, localhost, internal names,
|
||||
ports, malformed input, and excessive lengths.
|
||||
|
||||
### Desktop receiver
|
||||
|
||||
Desktop exposes an authenticated activity receiver separate from the capture
|
||||
receiver. It normalizes and validates a hostname with
|
||||
`hostname-normalization-v1`, validates a positive bounded duration, ISO time
|
||||
fields, schema version, and idempotency ID before it publishes
|
||||
`browser.activity.domain`. Invalid and duplicate records do not enter Activity.
|
||||
The existing local pairing token gates the endpoint; the token is never placed
|
||||
in Activity storage or UI.
|
||||
|
||||
The receiver annotates a valid activity with an existing exact hostname-to-Дело
|
||||
binding when one exists. A binding stores `workspaceId` as identity and the
|
||||
current root path as display cache. Bindings remain explicit:
|
||||
`client.example.com` does not imply `example.com` or the reverse. This is
|
||||
deliberately different from the exclusion-list suffix rule. Unbound activity is
|
||||
stored with the explicit `unassigned` session scope.
|
||||
|
||||
## Activity and Journal
|
||||
|
||||
### Background processing
|
||||
|
||||
Official plugins gain a lifecycle-safe background-service contribution. It is
|
||||
loaded when the plugin host starts, not only while the plugin view is mounted.
|
||||
The Activity service subscribes once to public events, normalizes and persists
|
||||
them, rebuilds candidates, and releases subscriptions on host teardown.
|
||||
|
||||
The platform provides unsubscribe-capable event subscriptions. Command
|
||||
registration is owned by the background service so commands remain available
|
||||
without opening the Activity view.
|
||||
|
||||
Raw Activity data is not held in `settings.json`. The Desktop storage layer
|
||||
provides a lifecycle-safe, plugin-scoped append-only event log for
|
||||
`verstak.activity` under plugin data. Appending one event does not rewrite the
|
||||
whole log. Plugin settings retain only compact preferences; plugin data retains
|
||||
candidate watermarks and indexes. Appends and compaction are serialized by the
|
||||
storage layer, preventing lost updates.
|
||||
|
||||
### Candidate rules
|
||||
|
||||
Activity presents chronological sessions rather than a raw log by default. A
|
||||
browser activity session displays, for example, `admin.client-site.ru · 1 ч
|
||||
32 мин`; it exposes no hidden URL/title data.
|
||||
|
||||
A logical session has one explicit scope:
|
||||
|
||||
```text
|
||||
{ kind: "workspace", workspaceId, workspaceRootPath } | { kind: "unassigned" }
|
||||
```
|
||||
|
||||
An unassigned session is a normal temporal scope, not an exception. It is shown
|
||||
in Activity and may open Journal review, where the user must choose an existing
|
||||
active Дело. That one-time choice does not create a binding. A workspace session
|
||||
is split only by a different workspace ID, a transition to/from unassigned, a
|
||||
20-minute idle gap, or 120 minutes of total session span. It is not split
|
||||
merely because midnight passes. A session is ready when either:
|
||||
|
||||
- meaningful events in its session cover at least ten minutes and include at
|
||||
least two events; or
|
||||
- it contains one or more browser-domain records totaling at least ten minutes.
|
||||
|
||||
`workspace.selected`, `file.opened`, and `note.opened` are diagnostic context,
|
||||
not meaningful work by themselves.
|
||||
|
||||
Duration is normative rather than the wall-clock span from first to last event.
|
||||
Sort meaningful events by `{occurredAt, activityId}` within a session:
|
||||
|
||||
- A browser-domain record contributes its explicit validated
|
||||
`durationSeconds`.
|
||||
- A file/note event has no inherent duration. For each adjacent pair of
|
||||
zero-duration meaningful events, add `min(time difference, 10 minutes)` only
|
||||
when no explicit-duration browser record lies between that pair in the
|
||||
ordered session.
|
||||
- A gap over 20 minutes has already split the session, so it contributes no
|
||||
implicit duration. The first and final standalone point event each contribute
|
||||
zero seconds.
|
||||
- Candidate duration is the sum of explicit browser duration and these implicit
|
||||
point-event intervals, capped at the 120-minute session maximum. It is never
|
||||
inferred from the overall first-to-last span.
|
||||
|
||||
Thus a note saved at 10:00 and a file changed at 10:09 estimate nine minutes;
|
||||
the same events at 10:00 and 10:50 form separate zero-duration sessions rather
|
||||
than a fictional 50-minute block.
|
||||
|
||||
Each logical session receives a newly generated immutable UUID `sessionId` at
|
||||
creation and stores an immutable anchor `{scope, firstSeenAt, firstActivityId}`.
|
||||
The service stores that ID on each appended event and preserves the anchor
|
||||
through log compaction. A late event may join a same-scope session only when it
|
||||
falls within that session's 20-minute boundary; otherwise it starts a new
|
||||
session. It never recomputes an existing session ID or anchor. Each session
|
||||
persists an ordered handled watermark
|
||||
`{ occurredAt, activityId }` and optional state for the latest reviewed slice.
|
||||
A candidate contains only source events after that watermark:
|
||||
|
||||
- **Accepting** a candidate stores its `sessionId` and handled watermark on the
|
||||
Journal entry. Later activity can create a candidate only for the additional
|
||||
interval after that watermark and only when that new interval reaches the
|
||||
normal threshold.
|
||||
- **Dismissing** consumes the current candidate slice through its watermark.
|
||||
`A+B` therefore cannot reappear as `A+B+C`; only qualifying new work after
|
||||
`B` may later be suggested.
|
||||
- A dismissal is not a permanent ban on future work in the same logical
|
||||
session. At least ten new meaningful minutes are required before it can be
|
||||
suggested again.
|
||||
|
||||
An event arriving late at or before a handled watermark remains in the raw
|
||||
diagnostic log but never reopens an accepted or dismissed candidate slice.
|
||||
|
||||
An across-midnight logical session remains one candidate. The review displays
|
||||
its time apportioned by local date, preselects the date containing the largest
|
||||
share of the duration (ties use the start date), and lets the user choose the
|
||||
Journal date. This preserves a 23:50–00:30 session instead of losing its first
|
||||
ten minutes to an artificial threshold.
|
||||
|
||||
The Journal review action opens the existing journal editor with candidate
|
||||
duration, date, and a concise domain/event summary. The user may edit all
|
||||
content and must explicitly save. For global/unassigned activity, the review
|
||||
requires choosing one existing Дело first. Missing or disabled Journal support
|
||||
produces a visible, actionable message rather than failing silently.
|
||||
|
||||
Activity has two clear views:
|
||||
|
||||
- **Сессии** — default, with candidate cards and activity summaries;
|
||||
- **События** — a secondary diagnostic stream for technical event inspection.
|
||||
|
||||
### Retention and deletion
|
||||
|
||||
The alpha retains raw Activity events for at most 60 days, 10,000 events, or
|
||||
8 MiB of log data, whichever limit is reached first. On append and at service
|
||||
startup, bounded compaction removes the oldest entries until all limits hold;
|
||||
it does not block the UI. Candidate state is pruned once its session and every
|
||||
related Journal source watermark are older than 60 days.
|
||||
|
||||
Saving a Journal entry does not delete its source Activity events. The Journal
|
||||
entry retains the compact source session/watermark reference after raw-event
|
||||
retention removes those events. **Clear activity for this Дело** removes only
|
||||
that case's raw events, sessions, and candidate state after confirmation.
|
||||
**Clear all activity** is a separate global confirmed operation.
|
||||
|
||||
## Browser Inbox lifecycle
|
||||
|
||||
### Record state
|
||||
|
||||
A capture remains one canonical record with independent fields:
|
||||
|
||||
```text
|
||||
globalState: active | archived
|
||||
workspaceId: optional immutable UUID of assigned Дело
|
||||
workspaceState: unassigned | active | trashed | unavailable | orphaned
|
||||
workspaceRootPath: optional current or historical Дело path cache
|
||||
workspaceTrashId: optional stable trash identity
|
||||
```
|
||||
|
||||
Existing records migrate to `globalState: active`. An assignment whose current
|
||||
path resolves to an active marker receives that marker's `workspaceId`; an
|
||||
unresolved legacy path becomes `unavailable` rather than attaching to a future
|
||||
folder of the same name. The current test vault may be discarded, but the
|
||||
migration is non-destructive for an alpha user vault.
|
||||
|
||||
### Actions
|
||||
|
||||
- **Assign to Дело** retains the capture in global Inbox and shows it in the
|
||||
selected Дело's Inbox.
|
||||
- **Убрать из общих входящих** changes `globalState` to `archived`. It never
|
||||
removes the assignment, so an assigned capture remains available from its
|
||||
Дело.
|
||||
- **Открепить от дела** clears `workspaceId`, `workspaceRootPath`, and
|
||||
`workspaceTrashId`, and sets `workspaceState` to `unassigned`. If the capture is
|
||||
still globally active it returns to the global Inbox; otherwise it remains in
|
||||
archive.
|
||||
- **Удалить везде** removes the canonical capture and its assignment. It is a
|
||||
separate destructive action and requires confirmation that names the affected
|
||||
Дело when assigned.
|
||||
- **Сохранить ссылку в деле** is available only for a valid HTTP(S) capture URL.
|
||||
It opens a small save dialog with an editable proposed filename derived from
|
||||
the capture title, then its hostname, then `Link`. The stem is sanitized for
|
||||
cross-platform forbidden characters and control characters, limited to 96
|
||||
characters, and receives `.url`. The file uses InternetShortcut format and
|
||||
is created through the plugin `files.write` capability under the case's
|
||||
`Links/` directory, creating that directory first. A name collision never
|
||||
overwrites silently: the dialog offers an explicit `name (2).url` alternative
|
||||
or cancellation. Read-only/error cases leave the capture unchanged and show
|
||||
the failure. Success publishes the normal safe file/link Activity event; the
|
||||
link is opened through the dedicated URL-opening behaviour below, not by
|
||||
relying on a Linux `.url` file association.
|
||||
|
||||
Bulk actions operate only on the visibly filtered capture set, show the count,
|
||||
and use archive rather than permanent deletion. Every permanent deletion and
|
||||
Journal deletion has a cancellation-safe confirmation dialog.
|
||||
|
||||
When a Дело is renamed, the Inbox service updates the path cache of captures
|
||||
and exact domain bindings with that `workspaceId`; no relation is keyed by the
|
||||
old root path. It does not update a binding that has been manually changed to a
|
||||
different workspace ID during that operation.
|
||||
|
||||
When a Дело goes to Trash, assignments and bindings for its `workspaceId`
|
||||
become `trashed` with the Desktop trash ID. They remain visible as unavailable
|
||||
historical context but are not used for automatic routing. A restore event
|
||||
carrying that trash ID restores the same ID's assignment/binding, including a
|
||||
restored path changed by a collision. A permanent trash purge changes captures
|
||||
to unassigned and changes bindings to a visible `orphaned` state that the user
|
||||
can reassign or remove. Activity remains historical and labels the case as
|
||||
deleted. If a case disappears by external filesystem change, active references
|
||||
become `unavailable`, automatic routing is disabled, and the user must
|
||||
explicitly reassign or remove them; a newly created folder with the same name
|
||||
has a different `workspaceId` and never steals those references.
|
||||
|
||||
### Archive and filters
|
||||
|
||||
Global Browser Inbox has **Active**, **Archive**, and **All** status filters;
|
||||
Active is the default. Search applies within the chosen filter, so Archive is
|
||||
searchable deliberately through Archive or All rather than unexpectedly
|
||||
appearing in the normal queue. **Restore to Inbox** changes `globalState` back
|
||||
to `active`. Archive supports a visible filtered bulk restore with a count and
|
||||
confirmation. A capture assigned to a Дело remains visible in that Дело's Inbox
|
||||
regardless of its global archive state, with an Archive badge.
|
||||
|
||||
### Opening saved links on Linux
|
||||
|
||||
The platform adds a user-initiated `urls.openExternal` capability and API. It
|
||||
accepts only a validated HTTP(S) URL and opens that URL through the system
|
||||
browser opener (`xdg-open` in the Linux alpha); it never passes a `.url` file
|
||||
path to the opener. Browser Inbox uses this capability for **Open link** and
|
||||
for a saved `.url` after parsing and validating its `URL=` value. The Files
|
||||
surface recognizes a valid `.url` file and uses the same URL-opening path. This
|
||||
does not depend on desktop file-association support for InternetShortcut files.
|
||||
|
||||
## Alpha interface
|
||||
|
||||
- Use Russian product labels consistently: **Дела**, **Входящие**,
|
||||
**Активности**, and **Журнал**. User-facing dates use the local time zone.
|
||||
- With a selected Дело, the overview reads only records explicitly scoped to
|
||||
that Дело; an unscoped global event never leaks into every case. With no
|
||||
selected Дело, it shows a short prompt to select/create one plus up to five
|
||||
active unassigned Inbox records; it does not fabricate a blended continuation
|
||||
feed.
|
||||
- **Needs attention** shows at most five entries: ready Journal candidates,
|
||||
active unprocessed Inbox records, and existing urgent Todos, in that order
|
||||
within their respective priority. An Inbox record is new/needs attention
|
||||
while it is active and unprocessed, without an arbitrary age cutoff.
|
||||
- Todo rows are included only when a loaded plugin exposes the `todo.workspace`
|
||||
capability. A missing or disabled Todo plugin is not an Overview error and
|
||||
contributes no placeholder rows.
|
||||
- **Continue work** shows at most four distinct case-scoped entities from the
|
||||
last 14 days: unfinished Todo, unprocessed capture, and the most recent
|
||||
note/file/Journal entity. Every item carries `lastMeaningfulAt`; items sort
|
||||
descending by that value, with an unprocessed capture, Todo, Journal, note,
|
||||
then file as deterministic ties. Multiple `file.changed` events for the same
|
||||
entity collapse to one item. Opening an item does not mark it complete. Its
|
||||
overflow action **Hide recommendation** stores a non-destructive
|
||||
entity/event dismissal; a later meaningful change to that entity makes it
|
||||
eligible again.
|
||||
- **Recent changes** shows at most eight distinct case-scoped records from the
|
||||
last seven days. Included events are note save/create, file create/rename or
|
||||
last change per file, saved browser link, and Journal create. Technical
|
||||
selection/open events are excluded. The empty state says that there were no
|
||||
changes in that period.
|
||||
- Empty states give a next action, not an empty pane.
|
||||
- Clear, delete, and archive actions name their scope and consequences.
|
||||
|
||||
Plugin IDs are diagnostic information. Normal tabs display the manifest's
|
||||
human title, such as `Заметки` or `Изображение`, never
|
||||
`verstak.default-editor.notes-markdown` or `verstak.file-preview.image`.
|
||||
|
||||
Add a persisted **Settings → Debug → Show plugin IDs** preference, defaulting
|
||||
to false. The Desktop application-specific `--debug` command-line argument
|
||||
enables the same display only for that run. When either is active, show the ID
|
||||
adjacent to the human title and in diagnostic errors. Neither mode alters data,
|
||||
plugin permissions, or release behaviour.
|
||||
|
||||
## Error handling and privacy
|
||||
|
||||
- A failed extension delivery keeps the accumulated hostname time locally and
|
||||
reports the pending-batch count and a non-blocking retry state in extension
|
||||
settings; it never falls back to Browser Inbox capture.
|
||||
- Receiver authentication/validation errors provide a safe status to the
|
||||
extension without echoing the pairing token or untrusted payload.
|
||||
- Activity never manufactures a case from missing assignment data.
|
||||
- User-visible errors explain the failed action and offer the next safe action.
|
||||
|
||||
## Verification
|
||||
|
||||
Automated checks must cover:
|
||||
|
||||
- browser tracker explicit consent/disable behaviour, focused-tab-only
|
||||
accounting, exclusions, canonical hostname vectors, immutable pending
|
||||
batches, acknowledgement-only reset, retry persistence, payload privacy,
|
||||
negative clock changes, long gaps, restart, lock, and suspend/resume;
|
||||
- Desktop activity receiver authentication, canonical normalization,
|
||||
validation, idempotency, binding, and event publication;
|
||||
- append-only Activity log retention/compaction, background subscription
|
||||
lifecycle, UUID workspace/unassigned session scopes, point-event duration
|
||||
calculation, immutable session IDs/late events, handled watermarks,
|
||||
across-midnight review, Journal handoff, local dates, case-scoped clear, and
|
||||
missing-Journal feedback;
|
||||
- assigning, archiving, restoring, unlinking, permanent deletion, `.url`
|
||||
naming/collision/readonly behaviour, filtered bulk operations, rename, trash
|
||||
restore/purge, external-workspace unavailability, duplicate-workspace-ID
|
||||
repair, and direct Linux URL opening without `.url` association;
|
||||
- normal and debug-mode plugin tab labels;
|
||||
- the corrected frontend Wails mock, deterministic Overview limits/scoping,
|
||||
plus the end-to-end flows activity-to-Journal and Inbox-to-Дело.
|
||||
|
||||
Manual GUI smoke testing verifies Russian normal-mode labels, hidden plugin
|
||||
IDs, candidate review, Inbox preservation, and extension domain exclusions in
|
||||
the Linux Desktop build.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- automatic creation of a Дело;
|
||||
- background-tab or browser-history tracking;
|
||||
- URL/content/title collection for passive activity;
|
||||
- shared/multi-case captures;
|
||||
- automated Journal saving, billing, or time-sheet generation;
|
||||
- browser-extension localization and a general analytics product;
|
||||
- release packaging, licensing, public GitHub README, and sync-server release
|
||||
security, which belong to the next alpha-release design.
|
||||
|
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 |