verstak-desktop/frontend/src/lib/test/wails-mock.js

4237 lines
236 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

/**
* Wails Mock Bridge — эмулирует window['go']['api']['App'] для тестового окружения.
*
* Каждый метод возвращает Promise с данными, совместимыми с Wails-контрактом.
* Состояние мутабельно — тесты могут менять его между сценариями.
*/
(function () {
if (window.__wailsMockReady) return;
// ── Mutable state ──────────────────────────────────────────────────
var pluginStates = {
'verstak.platform-test': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.platform-test',
name: 'Platform Test',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Runtime test plugin for verifying the Verstak platform.',
source: 'official',
icon: '🧪',
provides: ['verstak/platform-test/v1', 'verstak/diagnostics/v1'],
requires: ['verstak/core/plugin-manager/v1', 'verstak/core/capability-registry/v1'],
optionalRequires: ['verstak/core/vault/v1', 'verstak/core/sync/v1', 'verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['vault.read', 'events.publish', 'events.subscribe', 'ui.register', 'commands.register', 'storage.namespace', 'files.read', 'files.write', 'files.delete', 'files.openExternal', 'workbench.open'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [
{ id: 'verstak.platform-test.diagnostics', title: 'Platform Diagnostics', icon: '🧪', component: 'DiagnosticsPanel' }
],
commands: [
{ id: 'verstak.platform-test.run-tests', title: 'Run Platform Tests', handler: 'runAllTests' },
{ id: 'verstak.platform-test.show-version', title: 'Show Version Info', handler: 'showVersion' }
],
sidebarItems: [
{ id: 'verstak.platform-test.sidebar', title: 'Platform Test', icon: '🧪', view: 'verstak.platform-test.diagnostics', position: 100 }
],
statusBarItems: [
{ id: 'verstak.platform-test.status', label: '🧪 All Tests Pass', position: 'right', handler: 'openDiagnostics' }
],
settingsPanels: [
{ id: 'verstak.platform-test.settings', title: 'Platform Test Settings', icon: '🧪', component: 'PlatformTestSettings' }
],
openProviders: [
{
id: 'verstak.platform-test.markdown-diagnostic',
title: 'Platform Test Markdown Diagnostic',
priority: 10,
component: 'MarkdownDiagnosticProvider',
supports: [
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] }
]
}
]
}
},
rootPath: '/tmp/verstak-test/plugins/platform-test',
error: ''
},
'verstak.default-editor': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.default-editor',
name: 'Default Editor',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Built-in text and markdown editor/viewer.',
source: 'official',
icon: 'edit',
provides: ['verstak/default-editor/v1'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'files.write', 'workbench.open'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
openProviders: [
{
id: 'verstak.default-editor.text',
title: 'Default Text Editor',
priority: 50,
component: 'DefaultEditor',
supports: [
{ kind: 'vault-file', extensions: ['.txt', '.log', '.conf', '.ini', '.toml', '.yaml', '.yml', '.json', '.csv'], mime: ['text/plain', 'application/json'], contexts: ['generic-text'] }
]
},
{
id: 'verstak.default-editor.markdown',
title: 'Default Markdown Editor',
priority: 50,
component: 'DefaultEditor',
supports: [
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown'] }
]
},
{
id: 'verstak.default-editor.notes-markdown',
title: 'Default Notes Markdown Editor',
priority: 50,
component: 'DefaultEditor',
supports: [
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['notes-markdown'] }
]
}
]
}
},
rootPath: '/tmp/verstak-test/plugins/default-editor',
error: ''
},
'verstak.files': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.files',
name: 'Files',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Minimal vault file navigator.',
source: 'official',
icon: 'folder',
provides: ['verstak/files/v1'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'files.write', 'files.delete', 'files.openExternal', 'workbench.open', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.files.view', title: 'Files', icon: 'folder', component: 'FilesView' }],
workspaceItems: [{ id: 'verstak.files.workspace', title: 'Files', icon: 'folder', component: 'FilesView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/files',
error: ''
},
'verstak.trash': makeTrashPluginState(),
'verstak.notes': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.notes',
name: 'Notes',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped notes manager.',
source: 'official',
icon: 'edit',
provides: ['verstak/notes/v1'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'files.write', 'files.delete', 'events.subscribe', 'workbench.open', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
workspaceItems: [{ id: 'verstak.notes.workspace', title: 'Notes', icon: 'edit', component: 'NotesView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/notes',
error: ''
},
'verstak.sync': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.sync',
name: 'Sync',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Synchronize vault data across devices.',
source: 'official',
icon: 'refresh-cw',
provides: ['verstak/sync/v1', 'verstak/sync.status/v1'],
requires: ['verstak/core/files/v1'],
permissions: ['files.read', 'files.write', 'network.remote', 'storage.namespace', 'sync.participate', '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', handler: 'SyncStatusBar' }]
}
},
rootPath: '/tmp/verstak-test/plugins/sync',
error: ''
},
'verstak.activity': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.activity',
name: 'Activity',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped activity log for public plugin events.',
source: 'official',
icon: 'activity',
provides: ['activity.log', 'activity.provider', 'activity.reconstruction'],
permissions: ['events.subscribe', 'storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.activity.view', title: 'Activity', icon: 'activity', component: 'ActivityView' }],
sidebarItems: [{ id: 'verstak.activity.sidebar', title: 'Activity', icon: 'activity', view: 'verstak.activity.view', position: 20 }],
workspaceItems: [{ id: 'verstak.activity.workspace', title: 'Activity', icon: 'activity', component: 'ActivityView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/activity',
error: ''
},
'verstak.journal': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.journal',
name: 'Journal',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped journal with user-authored entries and optional Activity links.',
source: 'official',
icon: 'book-open',
provides: ['worklog', 'journal', 'report.worklog'],
permissions: ['storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.journal.view', title: 'Journal', icon: 'book-open', component: 'JournalView' }],
sidebarItems: [{ id: 'verstak.journal.sidebar', title: 'Journal', icon: 'book-open', view: 'verstak.journal.view', position: 30 }],
workspaceItems: [{ id: 'verstak.journal.workspace', title: 'Journal', icon: 'book-open', component: 'JournalView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/journal',
error: ''
},
'verstak.browser-inbox': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.browser-inbox',
name: 'Browser Inbox',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Global browser capture queue with explicit workspace assignment.',
source: 'official',
icon: 'inbox',
provides: ['browser.inbox'],
permissions: ['events.subscribe', 'files.read', 'storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.browser-inbox.view', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }],
sidebarItems: [{ id: 'verstak.browser-inbox.sidebar', title: 'Browser Inbox', icon: 'inbox', view: 'verstak.browser-inbox.view', position: 30 }],
workspaceItems: [{ id: 'verstak.browser-inbox.workspace', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/browser-inbox',
error: ''
},
'verstak.todo': makeTodoPluginState(),
'verstak.secrets': makeSecretsPluginState(),
'verstak.search': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.search',
name: 'Search',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped vault text search provider.',
source: 'official',
icon: 'search',
provides: ['verstak/search/v1', 'search.provider'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'workbench.open', 'storage.namespace', 'ui.register', 'events.subscribe', 'commands.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
workspaceItems: [{ id: 'verstak.search.workspace', title: 'Search', icon: 'search', component: 'SearchView' }],
commands: [{ id: 'verstak.search.searchVaultText', title: 'Search Vault Text', handler: 'verstak.search.searchVaultText' }],
searchProviders: [{ id: 'verstak.search.vault-text', label: 'Vault Text Search', handler: 'verstak.search.searchVaultText' }]
}
},
rootPath: '/tmp/verstak-test/plugins/search',
error: ''
}
};
var russianPluginNames = {
'verstak.platform-test': 'Тест платформы',
'verstak.default-editor': 'Стандартный редактор',
'verstak.files': 'Файлы',
'verstak.notes': 'Заметки',
'verstak.sync': 'Синхронизация',
'verstak.activity': 'Активность',
'verstak.journal': 'Журнал',
'verstak.browser-inbox': 'Входящие из браузера',
'verstak.search': 'Поиск',
'verstak.trash': 'Корзина',
'verstak.todo': 'Задачи',
'verstak.secrets': 'Секреты'
};
var russianContributionLabels = {
'verstak.platform-test.diagnostics': 'Диагностика платформы',
'verstak.platform-test.run-tests': 'Запустить тесты платформы',
'verstak.platform-test.show-version': 'Показать сведения о версии',
'verstak.platform-test.sidebar': 'Тест платформы',
'verstak.platform-test.status': '[OK] Все тесты пройдены',
'verstak.platform-test.settings': 'Настройки теста платформы',
'verstak.platform-test.markdown-diagnostic': 'Диагностика Markdown платформы',
'verstak.search.searchVaultText': 'Искать текст в хранилище',
'verstak.search.vault-text': 'Поиск по тексту хранилища'
};
Object.keys(pluginStates).forEach(function (pluginId) {
var manifest = pluginStates[pluginId].manifest;
manifest.localization = {
defaultLocale: 'en',
locales: { en: 'locales/en.json', ru: 'locales/ru.json' }
};
});
function mockPluginCatalog(pluginId, locale) {
var state = pluginStates[pluginId];
if (!state || !state.manifest) return {};
var manifest = state.manifest;
var translatedName = locale === 'ru' ? (russianPluginNames[pluginId] || manifest.name) : manifest.name;
var catalog = {
'manifest.name': translatedName,
'manifest.description': manifest.description || ''
};
var contributionFields = {
views: 'title', commands: 'title', settingsPanels: 'title', sidebarItems: 'title',
fileActions: 'label', noteActions: 'label', contextMenuEntries: 'label',
searchProviders: 'label', statusBarItems: 'label', openProviders: 'title', workspaceItems: 'title'
};
Object.keys(contributionFields).forEach(function (point) {
var field = contributionFields[point];
((manifest.contributes || {})[point] || []).forEach(function (item) {
catalog['contributions.' + point + '.' + item.id + '.' + field] = locale === 'ru'
? (russianContributionLabels[item.id] || translatedName)
: item[field];
});
});
return catalog;
}
var vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' };
var vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files', 'verstak.notes', 'verstak.sync', 'verstak.activity', 'verstak.journal', 'verstak.browser-inbox', 'verstak.search'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }, { id: 'verstak.notes', version: '0.1.0', source: 'official' }, { id: 'verstak.sync', version: '0.1.0', source: 'official' }, { id: 'verstak.activity', version: '0.1.0', source: 'official' }, { id: 'verstak.journal', version: '0.1.0', source: 'official' }, { id: 'verstak.browser-inbox', version: '0.1.0', source: 'official' }, { id: 'verstak.search', version: '0.1.0', source: 'official' }] };
vaultPluginState.enabledPlugins.push('verstak.trash');
vaultPluginState.desiredPlugins.push({ id: 'verstak.trash', version: '0.1.0', source: 'official' });
vaultPluginState.enabledPlugins.push('verstak.todo');
vaultPluginState.desiredPlugins.push({ id: 'verstak.todo', version: '0.1.0', source: 'official' });
vaultPluginState.enabledPlugins.push('verstak.secrets');
vaultPluginState.desiredPlugins.push({ id: 'verstak.secrets', version: '0.1.0', source: 'official' });
var appSettings = {
currentVaultPath: '/tmp/verstak-test/vault',
recentVaults: [],
language: localStorage.getItem('verstak-test-language') || 'system'
};
var workbenchPreferences = {};
var openedResources = [];
var pluginSettings = {
'verstak.platform-test': { savedText: 'initial value' }
};
var pluginData = {};
var vaultFiles = makeDefaultVaultFiles();
var externalOpens = [];
var trashEntries = [];
var trashPayloads = {};
window.__wailsMockExternalOpens = [];
var workspaceTree = makeDefaultWorkspaceTree();
var workspaceMetadata = {};
var reloadResponseMode = 'tuple';
var syncState = makeDefaultSyncState();
// ── Helpers ────────────────────────────────────────────────────────
function makeDefaultWorkspaceTree() {
return {
status: 'initialized',
currentNodeId: 'Project',
nodes: [
{ id: 'Project', parentId: '', type: 'space', title: 'Project', name: 'Project', rootPath: 'Project', status: 'active', order: 1 },
{ id: 'Test', parentId: '', type: 'space', title: 'Test', name: 'Test', rootPath: 'Test', status: 'active', order: 2 }
]
};
}
function cloneWorkspaceTree() {
return {
status: workspaceTree.status,
currentNodeId: workspaceTree.currentNodeId,
nodes: workspaceTree.nodes.map(function (n) { return Object.assign({}, n); })
};
}
function listWorkspacesFromTree() {
return workspaceTree.nodes
.filter(function (n) { return !n.parentId; })
.map(function (n) { return { name: n.name || n.id, rootPath: n.rootPath || n.name || n.id }; });
}
function makeWorkspaceNode(name, order) {
return { id: name, parentId: '', type: 'space', title: name, name: name, rootPath: name, status: 'active', order: order };
}
function cloneJson(value) {
return JSON.parse(JSON.stringify(value));
}
function builtInWorkspaceTemplates() {
return [
{
id: 'default',
name: 'General',
description: 'Everyday workspace with notes, files, journal, activity, and browser captures.',
version: 2,
workspaceTools: ['verstak.notes', 'verstak.files', 'verstak.journal', 'verstak.activity', 'verstak.browser-inbox'],
folders: ['Notes', 'Files'],
features: { files: true, notes: true, activity: true, journal: true, 'browser-inbox': true },
},
{
id: 'project',
name: 'Project',
description: 'Project planning with todos, journal, activity, and browser captures.',
version: 1,
workspaceTools: ['verstak.notes', 'verstak.files', 'verstak.todo', 'verstak.journal', 'verstak.activity', 'verstak.browser-inbox'],
folders: ['Notes', 'Files'],
features: { files: true, notes: true, todo: true, journal: true, activity: true, 'browser-inbox': true },
},
{
id: 'writing',
name: 'Writing',
description: 'Focused notes, files, and journal workspace for documentation and writing.',
version: 1,
workspaceTools: ['verstak.notes', 'verstak.files', 'verstak.journal'],
folders: ['Notes', 'Files'],
features: { files: true, notes: true, journal: true },
},
{
id: 'admin',
name: 'Admin',
description: 'Infrastructure workspace with secrets, todos, and journal.',
version: 1,
workspaceTools: ['verstak.notes', 'verstak.files', 'verstak.secrets', 'verstak.todo', 'verstak.journal'],
folders: ['Notes', 'Files', 'Secrets'],
features: { files: true, notes: true, secrets: true, todo: true, journal: true },
},
{
id: 'minimal',
name: 'Minimal',
description: 'Only notes and files for a lightweight workspace.',
version: 1,
workspaceTools: ['verstak.notes', 'verstak.files'],
folders: ['Notes', 'Files'],
features: { files: true, notes: true },
},
];
}
function workspaceTemplateByID(templateID) {
var id = String(templateID || 'default');
return builtInWorkspaceTemplates().find(function (template) { return template.id === id; }) || null;
}
function metadataForTemplate(name, template) {
var now = new Date().toISOString();
var folders = { notes: 'Notes', files: 'Files' };
if (template.features.secrets) folders.secrets = 'Secrets';
return {
workspaceName: name,
createdFromTemplate: {
templateId: template.id,
templateName: template.name,
templateVersion: template.version,
appliedAt: now,
workspaceTools: template.workspaceTools.slice(),
},
features: Object.assign({}, template.features),
folders: folders,
workspaceTools: template.workspaceTools.slice(),
updatedAt: now,
};
}
function genericWorkspaceMetadata(name) {
return {
workspaceName: name,
features: { files: true },
folders: { notes: 'Notes', files: 'Files' },
};
}
function makeDefaultVaultFiles() {
return {
'': { type: 'folder', modifiedAt: new Date().toISOString() },
'Docs': { type: 'folder', modifiedAt: new Date().toISOString() },
'Docs/todo.txt': { type: 'file', content: 'Buy groceries\nWrite tests', modifiedAt: new Date().toISOString() },
'Docs/readme.md': { type: 'file', content: '# Hello World\n\nThis is a **test** document.\n\n- item 1\n- item 2', modifiedAt: new Date().toISOString() },
'Notes': { type: 'folder', modifiedAt: new Date().toISOString() },
'Notes/Overview.md': { type: 'file', content: '# Notes Overview\n\nMy notes content here.', modifiedAt: new Date().toISOString() },
'Project': { type: 'folder', modifiedAt: new Date().toISOString() },
'Project/Notes': { type: 'folder', modifiedAt: new Date().toISOString() },
'Project/Notes/Overview.md': { type: 'file', content: '# Project Overview\n', modifiedAt: new Date().toISOString() },
'Project/project-only.txt': { type: 'file', content: 'project file', modifiedAt: new Date().toISOString() },
'Test': { type: 'folder', modifiedAt: new Date().toISOString() },
'Test/test-only.txt': { type: 'file', content: 'test file', modifiedAt: new Date().toISOString() }
};
}
function makeTrashPluginState() {
return {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.trash',
name: 'Trash',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Global vault trash manager for deleted files and folders.',
source: 'official',
icon: 'trash',
provides: ['trash.management'],
requires: ['verstak/core/files/v1'],
permissions: ['files.delete', 'files.write', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.trash.view', title: 'Trash', icon: 'trash', component: 'TrashView' }],
sidebarItems: [{ id: 'verstak.trash.sidebar', title: 'Trash', icon: 'trash', view: 'verstak.trash.view', position: 40 }]
}
},
rootPath: '/tmp/verstak-test/plugins/trash',
error: ''
};
}
function makeTodoPluginState() {
return {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.todo',
name: 'Todos',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Global and workspace todo tracking with due and reminder metadata.',
source: 'official',
icon: 'list-todo',
provides: ['todo.list', 'todo.workspace'],
permissions: ['files.read', 'storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.todo.view', title: 'Todos', icon: 'list-todo', component: 'TodoView' }],
sidebarItems: [{ id: 'verstak.todo.sidebar', title: 'Todos', icon: 'list-todo', view: 'verstak.todo.view', position: 35 }],
workspaceItems: [{ id: 'verstak.todo.workspace', title: 'Todos', icon: 'list-todo', component: 'TodoView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/todo',
error: ''
};
}
function makeSecretsPluginState() {
return {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.secrets',
name: 'Secrets',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Encrypted global and workspace-scoped secret manager.',
source: 'official',
icon: 'key-round',
provides: ['secret-store', 'secrets.read-ui', 'secrets.write-ui'],
permissions: ['secrets.read', 'secrets.write', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
workspaceItems: [{ id: 'verstak.secrets.workspace', title: 'Secrets', icon: 'key-round', component: 'SecretsView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/secrets',
error: ''
};
}
function makeDefaultSyncState() {
return {
configured: false,
serverUrl: '',
deviceId: 'mock-device',
deviceName: '',
connected: false,
revoked: false,
tokenStored: false,
unpushedOps: 0,
lastSyncAt: '',
syncInterval: 0,
lastError: '',
statusLabel: 'disabled',
serverSequence: 0
};
}
function normalizeVaultPath(relativePath, allowRoot) {
var p = String(relativePath || '');
if (p.indexOf('\x00') !== -1) return { error: 'invalid-path: null-byte' };
if (p.indexOf('\\') !== -1) return { error: 'invalid-path: backslash not allowed' };
if (p.indexOf('./') === 0) p = p.slice(2);
if (!allowRoot && !p) return { error: 'invalid-path: empty path' };
if (p.charAt(0) === '/' || /^[A-Za-z]:/.test(p)) return { error: 'invalid-path: absolute path rejected' };
var parts = p.split('/').filter(Boolean);
if (parts.indexOf('..') !== -1) return { error: 'invalid-path: path-traversal' };
if (parts[0] && parts[0].toLowerCase() === '.verstak') return { error: 'reserved-path: .verstak is internal' };
return { path: parts.join('/') };
}
function parentPath(path) {
var idx = path.lastIndexOf('/');
return idx === -1 ? '' : path.slice(0, idx);
}
function baseName(path) {
var idx = path.lastIndexOf('/');
return idx === -1 ? path : path.slice(idx + 1);
}
function fileEntry(path, node) {
var name = path ? baseName(path) : '';
var ext = '';
var dot = name.lastIndexOf('.');
if (dot > 0) ext = name.slice(dot + 1);
return {
name: name,
relativePath: path,
type: node.type,
size: node.type === 'file' ? (node.content || '').length : 0,
modifiedAt: node.modifiedAt || new Date().toISOString(),
extension: ext,
isHidden: name.charAt(0) === '.',
isReserved: false,
canRead: node.type === 'file' || node.type === 'folder',
canWrite: node.type === 'file' || node.type === 'folder'
};
}
function requirePluginPermission(pluginId, permission) {
var s = pluginStates[pluginId];
if (!s || !s.enabled || (s.status !== 'loaded' && s.status !== 'degraded')) {
return 'plugin not enabled and loaded';
}
if (!s.manifest.permissions || s.manifest.permissions.indexOf(permission) === -1) {
return 'plugin lacks required permission ' + permission;
}
if (vaultStatus.status !== 'open') return 'vault-not-open';
return '';
}
function makePlugin(id) {
var s = pluginStates[id];
if (!s) return null;
return {
manifest: s.manifest,
status: s.status,
enabled: s.enabled,
rootPath: s.rootPath,
error: s.error
};
}
function allPlugins() {
return Object.keys(pluginStates).map(makePlugin).filter(Boolean);
}
function allCapabilities() {
var caps = [];
caps.push({ name: 'verstak/core/plugin-manager/v1', description: 'Plugin management', pluginId: 'verstak-desktop', status: 'stable' });
caps.push({ name: 'verstak/core/capability-registry/v1', description: 'Capability registry', pluginId: 'verstak-desktop', status: 'stable' });
caps.push({ name: 'verstak/core/files/v1', description: 'Files API', pluginId: 'verstak-desktop', status: 'stable' });
caps.push({ name: 'verstak/core/workbench/v1', description: 'Workbench routing', pluginId: 'verstak-desktop', status: 'stable' });
caps.push({ name: 'verstak/core/sync/v1', description: 'Sync API', pluginId: 'verstak-desktop', status: 'stable' });
for (var id in pluginStates) {
var s = pluginStates[id];
if (s.status === 'loaded' && s.enabled && s.manifest && s.manifest.provides) {
s.manifest.provides.forEach(function (p) {
caps.push({ name: p, description: '', pluginId: id, status: 'stable' });
});
}
}
return caps;
}
function allPermissions() {
return [
{ name: 'vault.read', description: 'Read vault data', dangerous: false },
{ name: 'events.publish', description: 'Publish events', dangerous: false },
{ name: 'events.subscribe', description: 'Subscribe to events', dangerous: false },
{ name: 'ui.register', description: 'Register UI contributions', dangerous: false },
{ name: 'commands.register', description: 'Register commands', dangerous: false },
{ name: 'storage.namespace', description: 'Access plugin storage', dangerous: false },
{ name: 'files.read', description: 'Read vault files', dangerous: false },
{ name: 'files.write', description: 'Write vault files', dangerous: true },
{ name: 'files.delete', description: 'Trash vault files', dangerous: true },
{ name: 'files.openExternal', description: 'Open vault files and folders externally', dangerous: true },
{ name: 'workbench.open', description: 'Request Workbench open/edit routing', dangerous: false },
{ name: 'network.remote', description: 'Connect to remote network services', dangerous: true },
{ name: 'sync.participate', description: 'Participate in vault sync', dangerous: true }
];
}
function syncStatusDTO() {
return {
configured: syncState.configured,
serverUrl: syncState.serverUrl,
deviceId: syncState.deviceId,
deviceName: syncState.deviceName,
connected: syncState.connected,
revoked: syncState.revoked,
tokenStored: syncState.tokenStored,
unpushedOps: syncState.unpushedOps,
lastSyncAt: syncState.lastSyncAt,
syncInterval: syncState.syncInterval,
lastError: syncState.lastError,
statusLabel: syncState.statusLabel
};
}
function requirePluginSyncPermission(pluginId, remote) {
var err = requirePluginPermission(pluginId, 'sync.participate');
if (err) return err;
if (remote) {
err = requirePluginPermission(pluginId, 'network.remote');
if (err) return err;
}
return '';
}
function allContributions() {
var views = [], commands = [], searchProviders = [], sidebarItems = [], statusBarItems = [], settingsPanels = [], openProviders = [], workspaceItems = [];
for (var id in pluginStates) {
var s = pluginStates[id];
var c = (s.manifest && s.manifest.contributes) || {};
if (c.views) c.views.forEach(function (v) { views.push(Object.assign({}, v, { pluginId: id })); });
if (c.commands) c.commands.forEach(function (cmd) { commands.push(Object.assign({}, cmd, { pluginId: id })); });
if (c.searchProviders) c.searchProviders.forEach(function (sp) { searchProviders.push(Object.assign({}, sp, { pluginId: id })); });
if (c.sidebarItems) c.sidebarItems.forEach(function (sb) { sidebarItems.push(Object.assign({}, sb, { pluginId: id })); });
if (c.statusBarItems) c.statusBarItems.forEach(function (st) { statusBarItems.push(Object.assign({}, st, { pluginId: id })); });
if (c.settingsPanels) c.settingsPanels.forEach(function (sp) { settingsPanels.push(Object.assign({}, sp, { pluginId: id })); });
if (c.openProviders) c.openProviders.forEach(function (op) { openProviders.push(Object.assign({}, op, { pluginId: id })); });
if (c.workspaceItems) c.workspaceItems.forEach(function (wi) { workspaceItems.push(Object.assign({}, wi, { pluginId: id })); });
}
return { views: views, commands: commands, searchProviders: searchProviders, sidebarItems: sidebarItems, statusBarItems: statusBarItems, settingsPanels: settingsPanels, openProviders: openProviders, workspaceItems: workspaceItems };
}
function requestExtension(request) {
if (request && request.extension) {
var explicit = String(request.extension).toLowerCase();
return explicit.charAt(0) === '.' ? explicit : '.' + explicit;
}
var p = String((request && request.path) || '').toLowerCase();
var slash = p.lastIndexOf('/');
var name = slash === -1 ? p : p.slice(slash + 1);
var dot = name.lastIndexOf('.');
return dot > 0 ? name.slice(dot) : '';
}
function requestContextName(request) {
var ctx = (request && request.context) || {};
if (ctx.notesMode || ctx.isInsideNotesFolder || ctx.sourceView === 'notes') return 'notes-markdown';
var ext = requestExtension(request);
if (ext === '.md' || ext === '.markdown') return 'generic-markdown';
return 'generic-text';
}
function providerSupports(provider, request) {
var ext = requestExtension(request);
var contextName = requestContextName(request);
var mode = String((request && request.mode) || 'view').toLowerCase();
return (provider.supports || []).some(function (support) {
if (support.kind && support.kind !== request.kind) return false;
if (support.modes && support.modes.length && support.modes.map(function (m) { return String(m).toLowerCase(); }).indexOf(mode) === -1) return false;
if (support.extensions && support.extensions.length && support.extensions.map(function (e) { return String(e).toLowerCase(); }).indexOf(ext) === -1) return false;
if (support.contexts && support.contexts.length && support.contexts.indexOf(contextName) === -1) return false;
return true;
});
}
function selectOpenProvider(request) {
var providers = allContributions().openProviders.filter(function (provider) {
var s = pluginStates[provider.pluginId];
return s && s.enabled && (s.status === 'loaded' || s.status === 'degraded') && providerSupports(provider, request);
});
providers.sort(function (a, b) {
var byPriority = (b.priority || 0) - (a.priority || 0);
if (byPriority) return byPriority;
return String(a.id).localeCompare(String(b.id));
});
return providers[0] || null;
}
function openWorkbenchResource(pluginId, request, forcedMode) {
var s = pluginStates[pluginId];
if (!s || !s.enabled || (s.status !== 'loaded' && s.status !== 'degraded')) {
return Promise.resolve([{}, 'plugin not enabled and loaded']);
}
if (!s.manifest.permissions || s.manifest.permissions.indexOf('workbench.open') === -1) {
return Promise.resolve([{}, 'plugin lacks required permission workbench.open']);
}
var normalized = Object.assign({}, request || {});
normalized.kind = normalized.kind || 'vault-file';
normalized.mode = forcedMode || normalized.mode || 'view';
normalized.extension = requestExtension(normalized);
normalized.context = Object.assign({}, normalized.context || {}, { sourcePluginId: pluginId });
var provider = selectOpenProvider(normalized);
if (!provider) {
return Promise.resolve([{
status: 'no-provider',
request: normalized,
message: 'no open provider for resource'
}, '']);
}
var result = {
status: 'opened',
providerId: provider.id,
providerPluginId: provider.pluginId,
providerComponent: provider.component,
request: normalized
};
openedResources.push(Object.assign({ id: provider.id + ':' + openedResources.length, openedAt: new Date().toISOString() }, result));
return Promise.resolve([result, '']);
}
function defaultEditorBundle() {
return '(' + function () {
function e(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (key === 'className') node.className = attrs[key];
else if (key.indexOf('on') === 0) node.addEventListener(key.slice(2).toLowerCase(), attrs[key]);
else node.setAttribute(key, attrs[key]);
});
(children || []).forEach(function (child) { node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child); });
return node;
}
function esc(s) { return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
function renderMarkdown(text) {
return String(text || '').split(/\n/).map(function (line) {
if (/^#\s+/.test(line)) return '<h1>' + esc(line.replace(/^#\s+/, '')) + '</h1>';
if (/^-\s+\[[ x]\]\s+/i.test(line)) return '<ul><li><input type="checkbox" disabled> ' + esc(line.replace(/^-\s+\[[ x]\]\s+/i, '')) + '</li></ul>';
if (/^-\s+/.test(line)) return '<ul><li>' + esc(line.replace(/^-\s+/, '')) + '</li></ul>';
return line ? '<p>' + esc(line).replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') + '</p>' : '';
}).join('');
}
function insertAround(ta, before, after, fallback) {
var start = ta.selectionStart;
var end = ta.selectionEnd;
var text = ta.value.slice(start, end) || fallback || '';
ta.value = ta.value.slice(0, start) + before + text + after + ta.value.slice(end);
ta.selectionStart = start + before.length;
ta.selectionEnd = start + before.length + text.length;
ta.dispatchEvent(new Event('input', { bubbles: true }));
}
var DefaultEditor = {
mount: function (c, p, api) {
if (!document.getElementById('mock-default-editor-styles')) {
var style = document.createElement('style');
style.id = 'mock-default-editor-styles';
style.textContent = '.de-root{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.de-toolbar,.de-md-toolbar{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-bottom:1px solid #16213e;background:#12122a;flex-wrap:wrap}.de-toolbar-mode{font-size:.75rem;color:#4ecca3;padding:.15rem .5rem;border-radius:3px;background:#1a2a3a}.de-toolbar-context{font-size:.75rem;color:#8b8ba8}.de-toolbar-spacer{flex:1}.de-toolbar-btn,.de-md-btn{font-size:.75rem;padding:.25rem .6rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ccc}.de-toolbar-btn.active{border-color:#4ecca3;color:#4ecca3}.de-status.dirty{color:#f39c12}.de-status.saved{color:#4ecca3}.de-editor-wrap{flex:1;display:flex;min-height:0;overflow:hidden}.de-pane{flex:1;display:flex;min-width:0}.de-pane+.de-pane{border-left:1px solid #16213e}.de-lines{padding:.75rem .4rem;background:#0a0a15;color:#555;font-family:monospace;line-height:1.6;white-space:pre}.de-textarea{flex:1;height:100%;resize:none;border:0;outline:0;padding:.75rem;font-family:monospace;font-size:.85rem;line-height:1.6;background:#0d0d1a;color:#e0e0e0}.de-preview{flex:1;padding:1rem;overflow:auto;background:#0d0d1a;color:#ddd}.de-notes-badge{font-size:.65rem;padding:.1rem .4rem;border-radius:3px;background:#2a1a3a;color:#b388ff}';
document.head.appendChild(style);
}
c.innerHTML = '';
c.className = 'de-root';
var req = p.request || {};
var path = req.path || '';
var ctx = req.context || {};
var isNotes = ctx.notesMode || ctx.isInsideNotesFolder;
var ext = (req.extension || '').toLowerCase();
var isMd = ext === '.md' || ext === '.markdown';
var editorMode = isNotes ? 'notes-markdown' : isMd ? 'generic-markdown' : 'text';
var viewMode = isMd && req.mode !== 'edit' ? 'preview' : 'edit';
var current = '';
var saved = '';
var dirty = false;
var ta = null;
var preview = null;
var status = e('span', { className: 'de-status', 'data-save-state': '' }, []);
c.setAttribute('data-editor-mode', editorMode);
c.setAttribute('data-resource-path', path);
c.setAttribute('data-request-mode', req.mode || 'view');
var toolbar = e('div', { className: 'de-toolbar' }, [e('span', { className: 'de-toolbar-mode' }, [editorMode]), e('span', { className: 'de-toolbar-context' }, [path])]);
if (isNotes) toolbar.appendChild(e('span', { className: 'de-notes-badge', 'data-notes-badge': '' }, ['notes context']));
toolbar.appendChild(e('span', { className: 'de-toolbar-spacer' }, []));
['edit', 'preview', 'split'].forEach(function (mode) {
if (!isMd) return;
toolbar.appendChild(e('button', { className: 'de-toolbar-btn', 'data-editor-mode-button': mode, onClick: function () { viewMode = mode; rebuild(); } }, [mode[0].toUpperCase() + mode.slice(1)]));
});
toolbar.appendChild(e('button', { className: 'de-toolbar-btn', 'data-editor-action': 'reload', onClick: reload }, ['Reload']));
toolbar.appendChild(e('button', { className: 'de-toolbar-btn', onClick: save }, ['Save']));
toolbar.appendChild(status);
c.appendChild(toolbar);
if (isMd) {
var md = e('div', { className: 'de-md-toolbar' }, []);
[['heading', 'H'], ['bold', 'B'], ['italic', 'I'], ['link', 'Link'], ['code', 'Code'], ['code-block', '```'], ['bullet', 'List'], ['numbered', '1.'], ['quote', 'Quote'], ['task', 'Task']].forEach(function (item) {
md.appendChild(e('button', { className: 'de-md-btn', 'data-md-action': item[0], onClick: function () { mdAction(item[0]); } }, [item[1]]));
});
c.appendChild(md);
}
var wrap = e('div', { className: 'de-editor-wrap' }, []);
c.appendChild(wrap);
function setStatus(text, cls) { status.textContent = text; status.className = 'de-status ' + (cls || ''); }
function update() { dirty = current !== saved; setStatus(dirty ? 'Modified' : 'Saved', dirty ? 'dirty' : 'saved'); if (preview) preview.innerHTML = renderMarkdown(current); }
function makeEditor() {
var pane = e('div', { className: 'de-pane' }, []);
var lines = e('div', { className: 'de-lines' }, []);
ta = e('textarea', { className: 'de-textarea', 'data-editor-textarea': '', spellcheck: 'false' }, []);
ta.value = current;
function renumber() { lines.textContent = Array.from({ length: ta.value.split('\n').length }, function (_, i) { return i + 1; }).join('\n'); }
ta.addEventListener('input', function () { current = ta.value; renumber(); update(); });
ta.addEventListener('keydown', function (ev) { if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 's') { ev.preventDefault(); save(); } if (ev.key === 'Tab') { ev.preventDefault(); insertAround(ta, ' ', '', ''); } });
renumber();
pane.appendChild(lines);
pane.appendChild(ta);
return pane;
}
function makePreview() { preview = e('div', { className: 'de-preview', 'data-preview': '' }, []); preview.innerHTML = renderMarkdown(current); return e('div', { className: 'de-pane' }, [preview]); }
function rebuild() {
wrap.innerHTML = '';
ta = null;
preview = null;
if (!isMd || viewMode === 'edit' || viewMode === 'split') wrap.appendChild(makeEditor());
if (isMd && (viewMode === 'preview' || viewMode === 'split')) wrap.appendChild(makePreview());
Array.from(toolbar.querySelectorAll('[data-editor-mode-button]')).forEach(function (btn) { btn.className = 'de-toolbar-btn' + (btn.getAttribute('data-editor-mode-button') === viewMode ? ' active' : ''); });
update();
}
function save() {
return api.files.writeText(path, current, { createIfMissing: false, overwrite: true }).then(function () { saved = current; dirty = false; setStatus('Saved', 'saved'); });
}
function reload() {
if (dirty && !window.confirm('Discard unsaved changes and reload from disk?')) return;
api.files.readText(path).then(function (text) { current = text || ''; saved = current; dirty = false; rebuild(); });
}
function mdAction(action) {
if (!ta) { viewMode = 'edit'; rebuild(); }
if (action === 'heading') insertAround(ta, '# ', '', '');
else if (action === 'bold') insertAround(ta, '**', '**', 'bold text');
else if (action === 'italic') insertAround(ta, '*', '*', 'italic text');
else if (action === 'link') insertAround(ta, '[', '](https://)', 'link text');
else if (action === 'code') insertAround(ta, '`', '`', 'code');
else if (action === 'code-block') insertAround(ta, '```\n', '\n```', 'code');
else if (action === 'bullet') insertAround(ta, '- ', '', 'item');
else if (action === 'numbered') insertAround(ta, '1. ', '', 'item');
else if (action === 'quote') insertAround(ta, '> ', '', 'quote');
else if (action === 'task') insertAround(ta, '- [ ] ', '', 'task');
}
reload();
},
unmount: function (c) { c.innerHTML = ''; }
};
window.VerstakPluginRegister('verstak.default-editor', { components: { DefaultEditor: DefaultEditor } });
}.toString() + ')();';
}
function filesPluginBundle() {
return '(' + function () {
var SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M6 2h9l5 5v15H6V2Zm8 1.5V8h4.5L14 3.5Z"/></svg>';
var FOLDER_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M3 5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v1H3V5Zm0 6h18v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7Z"/></svg>';
var FILE_SVGS = {
folder: FOLDER_SVG,
generic: SVG,
markdown: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M5 3h10l4 4v14H5V3Zm9 1.5V8h3.5L14 4.5ZM8 11h8v2H8v-2Zm0 4h8v2H8v-2Z"/></svg>',
text: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M6 2h9l5 5v15H6V2Zm8 1.5V8h4.5L14 3.5ZM8 12h8v1.5H8V12Zm0 3h8v1.5H8V15Zm0 3h5v1.5H8V18Z"/></svg>',
image: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
pdf: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 7.5c0 .83-.67 1.5-1.5 1.5H9v2H7.5V7H10c.83 0 1.5.67 1.5 1.5v1zm5 2c0 .83-.67 1.5-1.5 1.5h-2.5V7H15c.83 0 1.5.67 1.5 1.5v3zm4-3H19v1h1.5V11H19v2h-1.5V7h3v1.5zM9 9.5h1v-1H9v1zm6 2h1v-3h-1v3z"/></svg>',
code: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M22 21H2V3h20v18zM4 5v14h16V5H4zm3 4h10v2H7V9zm0 4h6v2H7v-2z"/></svg>'
};
var ACTION_ICONS = {
back: 'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z',
forward: 'M4 13h12.17l-5.59 5.59L12 20l8-8-8-8-1.42 1.41L16.17 11H4v2z',
up: 'M4 12l1.41 1.41L11 7.83V20h2V7.83l5.59 5.58L20 12 12 4l-8 8z',
refresh: 'M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z',
folderAdd: 'M20 6h-8.17l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-1 8h-3v3h-2v-3h-3v-2h3V9h2v3h3v2z',
markdownAdd: 'M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 8V4l5 5h-5zm-6 6h2v-3l2 3h1l2-3v3h2v-6h-2l-2.5 3.5L9 10H7v6z',
textAdd: 'M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 8V4l5 5h-5zM8 13h8v2H8v-2zm0 4h8v2H8v-2z',
open: 'M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7zM5 5h6V3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-6h-2v6H5V5z',
rename: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
trash: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5-1-1h-5l-1 1H5v2h14V4z',
trashView: 'M4 4h16v2H4V4zm2 4h12v12H6V8zm2 2v8h8v-8H8zm2 1.5h4V13h-4v-1.5zm0 3h4V16h-4v-1.5zM9 1h6l1 2H8l1-2z',
external: 'M14 3h7v7h-2V6.41l-9.83 9.83-1.41-1.41L17.59 5H14V3zM5 5h6v2H7v10h10v-4h2v6H5V5z',
explorer: 'M3 5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v1H3V5Zm0 6h18v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7Z',
duplicate: 'M5 3h10v2H5v10H3V5c0-1.1.9-2 2-2zm4 4h10c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H9c-1.1 0-2-.9-2-2V9c0-1.1.9-2 2-2zm1 2v10h9V9h-9zm3 3h3v2h2v3h-2v2h-3v-2h-2v-3h2v-2z',
restore: 'M13 3a9 9 0 1 1-8.95 8H2l3-3 3 3H6.06A7 7 0 1 0 13 5V3zm-1 5h2v5h4v2h-6V8z',
cut: 'M9.64 7.64c.23-.5.36-1.05.36-1.64 0-2.21-1.79-4-4-4S2 3.79 2 6s1.79 4 4 4c.59 0 1.14-.13 1.64-.36L10 12l-2.36 2.36C7.14 14.13 6.59 14 6 14c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4c0-.59-.13-1.14-.36-1.64L12 14l7 7h3L9.64 7.64zM6 8c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm0 12c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm6-8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM19 3l-6 6 2 2 7-8h-3z',
copy: 'M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z',
paste: 'M19 2h-4.18C14.4.84 13.3 0 12 0S9.6.84 9.18 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1zm7 18H5V4h2v3h10V4h2v16z'
};
function actionSvg(iconKey) {
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="' + (ACTION_ICONS[iconKey] || ACTION_ICONS.open) + '"/></svg>';
}
var ICON_EXTENSIONS = {
md: 'markdown', markdown: 'markdown',
txt: 'text', text: 'text', log: 'text', rtf: 'text',
jpg: 'image', jpeg: 'image', png: 'image', gif: 'image', webp: 'image', svg: 'image',
pdf: 'pdf',
js: 'code', jsx: 'code', mjs: 'code', cjs: 'code', ts: 'code', tsx: 'code',
py: 'code', go: 'code', rs: 'code', css: 'code', html: 'code', json: 'code'
};
function fileIconCategory(item) {
if (item.type === 'folder') return 'folder';
return ICON_EXTENSIONS[(item.extension || ext(item.name)).toLowerCase()] || 'generic';
}
function fileIconLabel(category) {
return {
folder: 'Folder',
markdown: 'Markdown file',
text: 'Text file',
image: 'Image file',
pdf: 'PDF file',
code: 'Code file',
generic: 'File'
}[category] || 'File';
}
function fileIcon(item) {
return FILE_SVGS[fileIconCategory(item)] || FILE_SVGS.generic;
}
function e(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (key === 'className') node.className = attrs[key];
else if (key.indexOf('on') === 0) node.addEventListener(key.slice(2).toLowerCase(), attrs[key]);
else if (key === 'innerHTML') node.innerHTML = attrs[key];
else if (key === 'style' && typeof attrs[key] === 'object') Object.assign(node.style, attrs[key]);
else node.setAttribute(key, attrs[key]);
});
(children || []).forEach(function (child) { if (child) node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child); });
return node;
}
function clean(path) { return String(path || '').split('/').filter(Boolean).join('/'); }
function parent(path) { path = clean(path); var i = path.lastIndexOf('/'); return i < 0 ? '' : path.slice(0, i); }
function ext(name) { var i = String(name || '').lastIndexOf('.'); return i > 0 ? name.slice(i + 1).toLowerCase() : ''; }
function base(path) { path = clean(path); var i = path.lastIndexOf('/'); return i < 0 ? path : path.slice(i + 1); }
function isConflictError(err) {
var message = (err && err.message) ? err.message : String(err || '');
return /conflict|already exists|exists/i.test(message);
}
function formatDate(value) {
if (!value) return '';
var date = new Date(value);
if (Number.isNaN(date.getTime())) return String(value);
return new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }).format(date);
}
var FilesView = {
mount: function (c, p, api) {
if (!document.getElementById('mock-files-styles')) {
var style = document.createElement('style');
style.id = 'mock-files-styles';
style.textContent = '.files-root{display:flex;flex-direction:column;height:100%;min-height:0;background:#0d0d1a;color:#e0e0e0;outline:0}.files-toolbar{display:flex;align-items:center;gap:.4rem;padding:.5rem .75rem;background:#12122a;border-bottom:1px solid #16213e;flex-wrap:wrap}.files-toolbar-btn,.files-row-btn{display:inline-flex;align-items:center;justify-content:center;gap:.3rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ccc;cursor:pointer;white-space:nowrap}.files-toolbar-btn{min-height:2rem;padding:.35rem .55rem}.files-row-btn{min-height:1.75rem;padding:.25rem .45rem;font-size:.72rem}.files-toolbar-btn svg,.files-row-btn svg{width:15px;height:15px;flex-shrink:0}.files-btn-label{line-height:1}.files-breadcrumb{flex:1;min-width:150px;color:#8b8ba8}.files-breadcrumb-item{color:#4ecca3;cursor:pointer}.files-breadcrumb-current{color:#ddd}.files-filter,.files-sort,.files-create-input,.files-rename-input{font-size:.78rem;padding:.32rem .5rem;border:1px solid #333;border-radius:4px;background:#0d0d1a;color:#e0e0e0}.files-sort{appearance:none;background-color:#0d0d1a;padding-right:1rem}.files-list{flex:1;overflow:auto}.files-header,.files-item{display:grid;grid-template-columns:minmax(180px,1fr) 90px 80px 150px 210px;align-items:center;gap:.5rem;padding:.38rem .75rem;border-bottom:1px solid rgba(22,33,62,.55)}.files-header{background:#101028;color:#8b8ba8;font-size:.7rem;text-transform:uppercase}.files-item:hover{background:#17172d}.files-item.selected{background:#1a2a3a}.files-namecell{display:flex;align-items:center;gap:.5rem;min-width:0}.files-item-icon{width:1.25rem;color:#8b8ba8}.files-item-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.files-item-meta{font-size:.74rem;color:#777;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.files-row-actions{display:flex;justify-content:flex-end;gap:.35rem}.files-panel{display:flex;gap:.5rem;padding:.5rem .75rem;border-top:1px solid #16213e;background:#12122a}.files-create-input,.files-rename-input{flex:1}.files-ctx-menu{position:fixed;z-index:9999;min-width:170px;background:#1a1a2e;border:1px solid #333;border-radius:6px;padding:6px 0;box-shadow:0 8px 24px rgba(0,0,0,.5);font-size:.84rem;color:#e0e0e0}.files-ctx-menu-item{display:flex;align-items:center;gap:.5rem;padding:6px 16px;cursor:pointer}.files-ctx-menu-item:hover{background:#2a2a4e}.files-ctx-menu-item svg{width:14px;height:14px}.files-ctx-menu-sep{height:1px;background:#333;margin:4px 8px}.files-drag-over{outline:2px dashed #4ecca3;outline-offset:-2px}';
style.textContent += '.files-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;color:#8b8ba8;font-size:.9rem;padding:2rem;text-align:center}.files-empty-actions{display:flex;align-items:center;justify-content:center;gap:.5rem;flex-wrap:wrap}.files-empty-btn{display:inline-flex;align-items:center;justify-content:center;gap:.35rem;min-height:2rem;padding:.35rem .6rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ccc;cursor:pointer;font-size:.78rem}.files-empty-btn:hover{background:#2a2a4e;border-color:#4ecca3}.files-empty-btn svg{width:15px;height:15px;flex-shrink:0}';
document.head.appendChild(style);
}
c.innerHTML = '';
c.className = 'files-root';
c.setAttribute('tabindex', '0');
c.setAttribute('data-plugin-id', 'verstak.files');
var n = p && p.workspaceNode;
var root = clean((p && (p.workspaceRootPath || (n && (n.rootPath || n.name || n.id)))) || '');
var workspaceName = root || 'Workspace';
window.__filesHistoryByWorkspace = window.__filesHistoryByWorkspace || {};
var historyKey = root || workspaceName;
var savedHistory = window.__filesHistoryByWorkspace[historyKey] || { stack: [''], index: 0, currentPath: '' };
var current = clean(savedHistory.currentPath || '');
var history = savedHistory.stack && savedHistory.stack.length ? savedHistory.stack.map(clean) : [current];
var historyIndex = Math.max(0, Math.min(savedHistory.index || 0, history.length - 1));
var entries = [];
var selected = {};
var lastClicked = '';
var filter = '';
var sort = 'folder-name';
var createMode = '';
var renaming = null;
function scoped(local) { local = clean(local); return root ? (local ? root + '/' + local : root) : local; }
function local(full) { full = clean(full); return root && full.indexOf(root + '/') === 0 ? full.slice(root.length + 1) : full === root ? '' : full; }
function saveHistory() { window.__filesHistoryByWorkspace[historyKey] = { stack: history.slice(), index: historyIndex, currentPath: current }; }
var toolbar = e('div', { className: 'files-toolbar' }, []);
var breadcrumb = e('div', { className: 'files-breadcrumb' }, []);
function btn(title, action, fn, iconKey) {
iconKey = iconKey || action;
return e('button', { className: 'files-toolbar-btn', 'data-files-action': action, 'data-files-icon': iconKey, title: title, 'aria-label': title, innerHTML: actionSvg(iconKey), onClick: fn }, []);
}
function rowBtn(title, action, fn, iconKey) {
iconKey = iconKey || action;
return e('button', { className: 'files-row-btn', 'data-files-action': action, 'data-files-icon': iconKey, title: title, 'aria-label': title, innerHTML: actionSvg(iconKey), onClick: fn }, []);
}
function emptyBtn(title, action, mode, iconKey) {
return e('button', {
className: 'files-empty-btn',
'data-files-empty-action': action,
'data-files-icon': iconKey,
title: title,
'aria-label': title,
innerHTML: actionSvg(iconKey) + '<span>' + title + '</span>',
onClick: function () { startCreate(mode); }
}, []);
}
function emptyFolderState() {
return e('div', { className: 'files-empty' }, [
e('div', {}, ['Empty folder']),
e('div', { className: 'files-empty-actions' }, [
emptyBtn('New folder', 'new-folder', 'folder', 'folderAdd'),
emptyBtn('New markdown file', 'new-markdown', 'markdown', 'markdownAdd'),
emptyBtn('New text file', 'new-text', 'text', 'textAdd')
])
]);
}
function noMatchesState() {
return e('div', { className: 'files-empty' }, [
e('div', {}, ['No matches']),
e('div', { className: 'files-empty-actions' }, [
e('button', {
className: 'files-empty-btn',
'data-files-empty-action': 'clear-filter',
'data-files-icon': 'refresh',
title: 'Clear filter',
'aria-label': 'Clear filter',
innerHTML: actionSvg('refresh') + '<span>Clear filter</span>',
onClick: function () {
filter = '';
filterInput.value = '';
render();
list.focus();
}
}, [])
])
]);
}
var backButton = btn('Back', 'back', goBack, 'back');
var forwardButton = btn('Forward', 'forward', goForward, 'forward');
var upButton = btn('Up', 'up', function () { if (current) nav(parent(current)); }, 'up');
var refreshButton = btn('Refresh', 'refresh', load, 'refresh');
var newFolderButton = btn('New folder', 'new-folder', function () { startCreate('folder'); }, 'folderAdd');
var newMarkdownButton = btn('New markdown file', 'new-markdown', function () { startCreate('markdown'); }, 'markdownAdd');
var newTextButton = btn('New text file', 'new-text', function () { startCreate('text'); }, 'textAdd');
var openButton = btn('Open', 'open', function () { open(firstSelected()); }, 'open');
var renameButton = btn('Rename', 'rename', function () { startRename(firstSelected()); }, 'rename');
var trashButton = btn('Move to trash', 'trash', function () { trashSelection(); }, 'trash');
var cutButton = btn('Cut', 'cut', cutSelection, 'cut');
var copyButton = btn('Copy', 'copy', copySelection, 'copy');
var pasteButton = btn('Paste', 'paste', paste, 'paste');
toolbar.appendChild(breadcrumb);
[backButton, forwardButton, upButton, refreshButton, newFolderButton, newMarkdownButton, newTextButton, openButton, renameButton, trashButton, cutButton, copyButton, pasteButton].forEach(function (button) { toolbar.appendChild(button); });
var filterInput = e('input', { className: 'files-filter', 'data-files-filter': '', placeholder: 'Filter current folder' }, []);
filterInput.addEventListener('input', function () { filter = filterInput.value.toLowerCase(); render(); });
toolbar.appendChild(filterInput);
var sortSelect = e('select', { className: 'files-sort', 'data-files-sort': '' }, [
e('option', { value: 'folder-name' }, ['Folders + name']),
e('option', { value: 'name-asc' }, ['Name']),
e('option', { value: 'type' }, ['Type']),
e('option', { value: 'modified-desc' }, ['Modified']),
e('option', { value: 'size-desc' }, ['Size'])
]);
sortSelect.addEventListener('change', function () { sort = sortSelect.value; render(); });
toolbar.appendChild(sortSelect);
c.appendChild(toolbar);
var list = e('div', { className: 'files-list', 'data-files-list': '' }, []);
c.appendChild(list);
var createPanel = e('div', { className: 'files-panel', style: 'display:none' }, []);
var createField = e('div', { style: { display: 'flex', flex: '1', flexDirection: 'column', gap: '.25rem' } }, []);
var createInput = e('input', { className: 'files-create-input', 'data-files-create-input': '' }, []);
var createError = e('div', { 'data-files-create-error': '', role: 'alert', style: { display: 'none', color: '#ff8a8a', fontSize: '.72rem', lineHeight: '1.2' } }, []);
createField.appendChild(createInput);
createField.appendChild(createError);
createPanel.appendChild(createField);
createPanel.appendChild(e('button', { className: 'files-toolbar-btn', 'data-files-create-confirm': '', onClick: confirmCreate }, ['Create']));
createPanel.appendChild(e('button', { className: 'files-toolbar-btn', onClick: cancelCreate }, ['Cancel']));
c.appendChild(createPanel);
var renamePanel = e('div', { className: 'files-panel', style: 'display:none' }, []);
var renameField = e('div', { style: { display: 'flex', flex: '1', flexDirection: 'column', gap: '.25rem' } }, []);
var renameInput = e('input', { className: 'files-rename-input', 'data-files-rename-input': '' }, []);
var renameError = e('div', { 'data-files-rename-error': '', role: 'alert', style: { display: 'none', color: '#ff8a8a', fontSize: '.72rem', lineHeight: '1.2' } }, []);
renameField.appendChild(renameInput);
renameField.appendChild(renameError);
renamePanel.appendChild(renameField);
renamePanel.appendChild(e('button', { className: 'files-toolbar-btn', 'data-files-rename-confirm': '', onClick: confirmRename }, ['Rename']));
renamePanel.appendChild(e('button', { className: 'files-toolbar-btn', onClick: cancelRename }, ['Cancel']));
c.appendChild(renamePanel);
function entryByPath(path) { return entries.find(function (item) { return item.relativePath === path; }) || null; }
function selectedEntries() { return Object.keys(selected).map(entryByPath).filter(Boolean); }
function firstSelected() { return selectedEntries()[0] || null; }
function updateBreadcrumb() {
breadcrumb.innerHTML = '';
breadcrumb.appendChild(e('span', { className: current ? 'files-breadcrumb-item' : 'files-breadcrumb-current', onClick: function () { nav(''); } }, [workspaceName]));
if (!current) return;
var parts = current.split('/');
var path = '';
parts.forEach(function (part, index) {
path = path ? path + '/' + part : part;
breadcrumb.appendChild(e('span', { className: 'files-breadcrumb-sep' }, [' / ']));
var target = path;
var isCurrent = index === parts.length - 1;
breadcrumb.appendChild(e('span', {
className: isCurrent ? 'files-breadcrumb-current' : 'files-breadcrumb-item',
onClick: function () { if (!isCurrent) nav(target); }
}, [part]));
});
}
function visible() {
return entries.filter(function (item) { return !item.isHidden && !item.isReserved && (!filter || item.name.toLowerCase().indexOf(filter) !== -1); }).sort(function (a, b) {
if (sort === 'folder-name') { if (a.type === 'folder' && b.type !== 'folder') return -1; if (a.type !== 'folder' && b.type === 'folder') return 1; }
if (sort === 'modified-desc') return new Date(b.modifiedAt || 0) - new Date(a.modifiedAt || 0) || a.name.localeCompare(b.name);
if (sort === 'size-desc') return (b.size || 0) - (a.size || 0) || a.name.localeCompare(b.name);
if (sort === 'type') return (a.type + (a.extension || '')).localeCompare(b.type + (b.extension || '')) || a.name.localeCompare(b.name);
return a.name.localeCompare(b.name);
});
}
function render() {
updateBreadcrumb();
list.innerHTML = '';
list.appendChild(e('div', { className: 'files-header' }, [e('span', {}, ['Name']), e('span', {}, ['Type']), e('span', {}, ['Size']), e('span', {}, ['Modified']), e('span', {}, ['Actions'])]));
var shown = visible();
if (!shown.length) {
list.appendChild(filter ? noMatchesState() : emptyFolderState());
return;
}
shown.forEach(function (item) {
var iconCategory = fileIconCategory(item);
var iconLabel = fileIconLabel(iconCategory);
var row = e('div', {
className: 'files-item' + (selected[item.relativePath] ? ' selected' : ''),
'data-file-name': item.name,
'data-file-type': item.type,
'data-file-path': item.relativePath,
draggable: 'true',
onClick: function (ev) { select(item, ev); },
onDblclick: function () { open(item); },
onDragstart: function (ev) {
if (!selected[item.relativePath]) { selected = {}; selected[item.relativePath] = true; }
ev.dataTransfer.setData('application/files-paths', JSON.stringify(Object.keys(selected)));
ev.dataTransfer.effectAllowed = 'move';
}
}, []);
row.appendChild(e('span', { className: 'files-namecell' }, [e('span', { className: 'files-item-icon', 'data-file-icon': iconCategory, title: iconLabel, 'aria-label': iconLabel, innerHTML: fileIcon(item) }, []), e('span', { className: 'files-item-name' }, [item.name])]));
row.appendChild(e('span', { className: 'files-item-meta' }, [item.type === 'folder' ? 'folder' : (item.extension || ext(item.name) || 'file')]));
row.appendChild(e('span', { className: 'files-item-meta' }, [item.size ? String(item.size) : '']));
row.appendChild(e('span', { className: 'files-item-meta' }, [formatDate(item.modifiedAt)]));
row.appendChild(e('span', { className: 'files-row-actions' }, [rowBtn('Open', 'row-open', function (ev) { ev.stopPropagation(); open(item); }, 'open'), rowBtn('Rename', 'row-rename', function (ev) { ev.stopPropagation(); startRename(item); }, 'rename'), rowBtn('Move to trash', 'row-trash', function (ev) { ev.stopPropagation(); trash(item); }, 'trash')]));
list.appendChild(row);
});
}
function select(item, ev) {
if (ev && (ev.ctrlKey || ev.metaKey)) {
if (selected[item.relativePath]) delete selected[item.relativePath]; else selected[item.relativePath] = true;
} else if (ev && ev.shiftKey && lastClicked) {
var shown = visible();
var a = shown.findIndex(function (x) { return x.relativePath === lastClicked; });
var b = shown.findIndex(function (x) { return x.relativePath === item.relativePath; });
if (a >= 0 && b >= 0) {
selected = {};
for (var i = Math.min(a, b); i <= Math.max(a, b); i++) selected[shown[i].relativePath] = true;
}
} else {
selected = {}; selected[item.relativePath] = true;
}
lastClicked = item.relativePath;
render();
}
function load() { selected = {}; api.files.list(scoped(current)).then(function (result) { entries = result || []; render(); }).catch(function (err) { list.textContent = 'Error: ' + (err.message || err); }); }
function nav(path, push) {
current = clean(path);
if (push !== false) {
if (historyIndex < history.length - 1) history = history.slice(0, historyIndex + 1);
if (history[history.length - 1] !== current) { history.push(current); historyIndex = history.length - 1; }
}
saveHistory();
load();
}
function goBack() { if (historyIndex <= 0) return; historyIndex -= 1; current = history[historyIndex]; saveHistory(); load(); }
function goForward() { if (historyIndex >= history.length - 1) return; historyIndex += 1; current = history[historyIndex]; saveHistory(); load(); }
function open(item) {
if (!item) return;
if (item.type === 'folder') { nav(local(item.relativePath)); return; }
var itemExt = item.extension ? '.' + item.extension : (ext(item.name) ? '.' + ext(item.name) : '');
var ctx = { sourcePluginId: 'verstak.files', sourceView: 'files' };
if ((itemExt === '.md' || itemExt === '.markdown') && local(item.relativePath).split('/')[0] === 'Notes') { ctx.isInsideNotesFolder = true; ctx.notesMode = true; }
api.workbench.openResource({ kind: 'vault-file', path: item.relativePath, mode: 'view', extension: itemExt, context: ctx });
}
function startCreate(mode) { createMode = mode; createInput.value = ''; setCreateError(''); createPanel.style.display = 'flex'; createInput.focus(); }
function setCreateError(message) {
createError.textContent = message || '';
createError.style.display = message ? 'block' : 'none';
createInput.setAttribute('aria-invalid', message ? 'true' : 'false');
}
function validateCreateName(name) {
if (!name) return 'Name is required';
if (/[\\/:*?"<>|\x00-\x1f]/.test(name)) return 'Invalid characters in name';
if (name === '.' || name === '..' || name[0] === ' ' || name[name.length - 1] === ' ' || name[name.length - 1] === '.') return 'Invalid name';
return '';
}
function cancelCreate() { createMode = ''; setCreateError(''); createPanel.style.display = 'none'; }
function confirmCreate() {
var name = createInput.value.trim();
var mode = createMode;
var validationError = validateCreateName(name);
if (validationError) { setCreateError(validationError); return; }
if (mode === 'markdown' && !/\.(md|markdown)$/i.test(name)) name += '.md';
if (mode === 'text' && !/\.[^/.]+$/.test(name)) name += '.txt';
var path = scoped(current ? current + '/' + name : name);
(mode === 'folder' ? api.files.createFolder(path) : api.files.writeText(path, '', { createIfMissing: true, overwrite: false })).then(function () { cancelCreate(); load(); }).catch(function (err) { setCreateError('Error: ' + ((err && err.message) ? err.message : String(err))); });
}
function setRenameError(message) {
renameError.textContent = message || '';
renameError.style.display = message ? 'block' : 'none';
renameInput.setAttribute('aria-invalid', message ? 'true' : 'false');
}
function cancelRename() { renaming = null; setRenameError(''); renamePanel.style.display = 'none'; }
function startRename(item) { if (!item) return; renaming = item; renameInput.value = item.name; setRenameError(''); renamePanel.style.display = 'flex'; renameInput.focus(); renameInput.select(); }
function confirmRename() {
if (!renaming) return;
var newName = renameInput.value.trim();
if (!newName || newName === renaming.name) { cancelRename(); return; }
if (/[\\/:*?"<>|\x00-\x1f]/.test(newName)) { setRenameError('Invalid characters in name'); return; }
if (newName === '.' || newName === '..' || newName[0] === ' ' || newName[newName.length - 1] === ' ' || newName[newName.length - 1] === '.') { setRenameError('Invalid name'); return; }
var to = parent(renaming.relativePath);
to = to ? to + '/' + newName : newName;
api.files.move(renaming.relativePath, to, { overwrite: false }).then(function () { cancelRename(); load(); }).catch(function (err) {
if (isConflictError(err)) { setRenameError('A file with that name already exists'); return; }
setRenameError('Error: ' + ((err && err.message) ? err.message : String(err)));
});
}
function confirmModal(message, options) {
options = options || {};
return new Promise(function (resolve) {
var overlay = e('div', {
className: 'files-modal-overlay',
style: {
position: 'fixed',
inset: '0',
background: 'rgba(0,0,0,.6)',
zIndex: '10000',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, []);
var modal = e('div', {
className: 'files-modal',
style: {
width: '400px',
maxWidth: '90vw',
padding: '24px',
background: '#1a1a2e',
border: '1px solid #333',
borderRadius: '8px',
color: '#e0e0e0',
boxShadow: '0 12px 40px rgba(0,0,0,.5)'
}
}, []);
var title = e('div', { className: 'files-modal-title', style: { marginBottom: '20px', lineHeight: '1.5' } }, [message]);
var actions = e('div', { className: 'files-modal-actions', style: { display: 'flex', justifyContent: 'flex-end', gap: '8px' } }, []);
function cleanup(value) {
document.removeEventListener('keydown', onKeydown);
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
resolve(value);
}
function modalButton(label, className, value) {
return e('button', {
className: className,
style: {
padding: '.4rem 1rem',
border: '1px solid #333',
borderRadius: '6px',
background: className.indexOf('danger') !== -1 ? '#e74c3c' : '#2a2a4e',
color: className.indexOf('danger') !== -1 ? '#fff' : '#ccc',
cursor: 'pointer'
},
onClick: function () { cleanup(value); }
}, [label]);
}
function onKeydown(ev) {
if (ev.key === 'Escape') cleanup(false);
}
actions.appendChild(modalButton(options.cancelText || 'Cancel', 'files-modal-btn cancel', false));
actions.appendChild(modalButton(options.confirmText || 'Confirm', 'files-modal-btn confirm' + (options.danger ? ' danger' : ''), true));
modal.appendChild(title);
modal.appendChild(actions);
overlay.appendChild(modal);
document.body.appendChild(overlay);
document.addEventListener('keydown', onKeydown);
var first = overlay.querySelector('.files-modal-btn');
if (first) first.focus();
});
}
function trash(item) {
if (!item) return;
confirmModal('Move "' + item.name + '" to trash?', { danger: true, confirmText: 'Move to trash' }).then(function (ok) {
if (ok) api.files.trash(item.relativePath).then(load);
});
}
function trashSelection() {
var items = selectedEntries();
if (items.length === 1) return trash(items[0]);
if (!items.length) return;
confirmModal('Move ' + items.length + ' items to trash?', { danger: true, confirmText: 'Move to trash' }).then(function (ok) {
if (ok) Promise.all(items.map(function (item) { return api.files.trash(item.relativePath); })).then(load);
});
}
function setClipboard(action, items) { if (!items.length) return; window.__filesClipboard = { action: action, workspaceRoot: root, items: items.map(function (item) { return { path: item.relativePath, name: item.name, type: item.type }; }) }; }
function cutSelection() { setClipboard('cut', selectedEntries()); }
function copySelection() { setClipboard('copy', selectedEntries().filter(function (item) { return item.type !== 'folder'; })); }
function uniqueName(name, occupied) { if (!occupied[name]) return name; var dot = name.lastIndexOf('.'); var b = dot > 0 ? name.slice(0, dot) : name; var x = dot > 0 ? name.slice(dot) : ''; for (var i = 2; i < 100; i++) { var c = b + ' (' + i + ')' + x; if (!occupied[c]) return c; } return b + ' (' + Date.now() + ')' + x; }
function duplicate(item) {
if (!item || item.type === 'folder') return;
var dot = item.name.lastIndexOf('.');
var stem = dot > 0 ? item.name.slice(0, dot) : item.name;
var extension = dot > 0 ? item.name.slice(dot) : '';
var folder = parent(item.relativePath);
var maxAttempts = 100;
function candidatePath(n) {
var name = n === 1 ? stem + ' (copy)' + extension : stem + ' (copy ' + n + ')' + extension;
return folder ? folder + '/' + name : name;
}
function tryDuplicate(n) {
var target = candidatePath(n);
return api.files.metadata(target).then(function () {
if (n >= maxAttempts) return Promise.reject(new Error('all duplicate names are taken'));
return tryDuplicate(n + 1);
}, function () {
return api.files.readText(item.relativePath).then(function (text) {
return api.files.writeText(target, text, { createIfMissing: true, overwrite: false });
});
});
}
tryDuplicate(1).then(load).catch(function (err) { console.error('[files] Duplicate failed:', err); });
}
function paste() {
var clip = window.__filesClipboard;
if (!clip || !clip.items || !clip.items.length) return;
var dest = scoped(current);
var occupied = {};
entries.forEach(function (item) { occupied[item.name] = true; });
Promise.all(clip.items.map(function (item) {
var name = uniqueName(item.name, occupied);
occupied[name] = true;
var to = dest ? dest + '/' + name : name;
if (clip.action === 'cut') return api.files.move(item.path, to, { overwrite: false });
return api.files.readText(item.path).then(function (text) { return api.files.writeText(to, text, { createIfMissing: true, overwrite: false }); });
})).then(function () { if (clip.action === 'cut') window.__filesClipboard = null; load(); });
}
var menu = e('div', { className: 'files-ctx-menu', style: { display: 'none' } }, []);
document.body.appendChild(menu);
function menuItem(label, action, fn, iconKey) {
iconKey = iconKey || action;
return e('div', {
className: 'files-ctx-menu-item',
'data-files-menu-action': action,
'data-files-menu-icon': iconKey,
onClick: function (ev) { ev.stopPropagation(); menu.style.display = 'none'; fn(); }
}, [e('span', { innerHTML: actionSvg(iconKey) }, []), label]);
}
function showMenu(x, y, item) {
menu.innerHTML = '';
if (item) {
if (!selected[item.relativePath]) { selected = {}; selected[item.relativePath] = true; render(); }
menu.appendChild(menuItem('Open', 'open', function () { open(item); }, 'open'));
menu.appendChild(menuItem('Rename', 'rename', function () { startRename(item); }, 'rename'));
if (item.type !== 'folder') menu.appendChild(menuItem('Duplicate', 'duplicate', function () { duplicate(item); }, 'duplicate'));
menu.appendChild(menuItem('Cut', 'cut', cutSelection, 'cut'));
menu.appendChild(menuItem('Copy', 'copy', copySelection, 'copy'));
menu.appendChild(menuItem('Trash', 'trash', trashSelection, 'trash'));
} else {
menu.appendChild(menuItem('New Folder', 'new-folder', function () { startCreate('folder'); }, 'folderAdd'));
menu.appendChild(menuItem('New Markdown', 'new-markdown', function () { startCreate('markdown'); }, 'markdownAdd'));
menu.appendChild(menuItem('New Text', 'new-text', function () { startCreate('text'); }, 'textAdd'));
if (window.__filesClipboard && window.__filesClipboard.items && window.__filesClipboard.items.length) menu.appendChild(menuItem('Paste', 'paste', paste, 'paste'));
}
menu.style.display = 'block'; menu.style.left = x + 'px'; menu.style.top = y + 'px';
}
createInput.addEventListener('input', function () { setCreateError(''); });
createInput.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') confirmCreate(); if (ev.key === 'Escape') cancelCreate(); });
renameInput.addEventListener('input', function () { setRenameError(''); });
renameInput.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') confirmRename(); if (ev.key === 'Escape') cancelRename(); });
list.addEventListener('contextmenu', function (ev) { ev.preventDefault(); var row = ev.target.closest('.files-item'); showMenu(ev.clientX, ev.clientY, row ? entryByPath(row.getAttribute('data-file-path')) : null); });
list.addEventListener('dragover', function (ev) { ev.preventDefault(); var row = ev.target.closest('.files-item'); if (row) row.classList.add('files-drag-over'); });
list.addEventListener('dragleave', function (ev) { var row = ev.target.closest('.files-item'); if (row) row.classList.remove('files-drag-over'); });
list.addEventListener('drop', function (ev) {
ev.preventDefault();
Array.from(list.querySelectorAll('.files-drag-over')).forEach(function (row) { row.classList.remove('files-drag-over'); });
var raw = ev.dataTransfer.getData('application/files-paths');
if (!raw) return;
var paths = JSON.parse(raw);
var row = ev.target.closest('.files-item');
var target = row && row.getAttribute('data-file-type') === 'folder' ? row.getAttribute('data-file-path') : scoped(current);
Promise.all(paths.map(function (path) { return api.files.move(path, target + '/' + base(path), { overwrite: false }); })).then(load);
});
var lastMouseHistoryAt = 0;
var lastMouseHistoryButton = 0;
function mouseHistoryButton(ev) {
if (ev.button === 3 || ev.button === 8 || ev.buttons === 8 || ev.buttons === 128 || ev.which === 8) return 'back';
if (ev.button === 4 || ev.button === 9 || ev.buttons === 16 || ev.buttons === 256 || ev.which === 9) return 'forward';
return '';
}
function mouseHistory(ev) {
var button = mouseHistoryButton(ev);
if (!button) return;
ev.preventDefault();
ev.stopPropagation();
var now = Date.now();
if (button === lastMouseHistoryButton && now - lastMouseHistoryAt < 120) return;
lastMouseHistoryButton = button;
lastMouseHistoryAt = now;
if (button === 'back') goBack();
else goForward();
}
function keyHistory(ev) {
if (ev.defaultPrevented) return;
if (ev.target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].indexOf(ev.target.tagName) !== -1) return;
var key = ev.key || '';
var ctrl = ev.ctrlKey || ev.metaKey;
var direction = '';
if (key === 'ArrowLeft' && ev.altKey) direction = 'back';
else if (key === 'ArrowRight' && ev.altKey) direction = 'forward';
else if (key === '[' && ctrl) direction = 'back';
else if (key === ']' && ctrl) direction = 'forward';
else if (key === 'BrowserBack' || key === 'XF86Back' || ev.keyCode === 166) direction = 'back';
else if (key === 'BrowserForward' || key === 'XF86Forward' || ev.keyCode === 167) direction = 'forward';
if (!direction) return;
ev.preventDefault();
ev.stopPropagation();
if (direction === 'back') goBack();
else goForward();
}
c.addEventListener('mousedown', mouseHistory, true);
c.addEventListener('pointerdown', mouseHistory, true);
window.addEventListener('pointerdown', mouseHistory, true);
document.addEventListener('pointerdown', mouseHistory, true);
window.addEventListener('mousedown', mouseHistory, true);
document.addEventListener('mousedown', mouseHistory, true);
window.addEventListener('mouseup', mouseHistory, true);
window.addEventListener('auxclick', mouseHistory, true);
window.addEventListener('keydown', keyHistory);
c.addEventListener('keydown', function (ev) {
if (ev.target && ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].indexOf(ev.target.tagName) !== -1) return;
var ctrl = ev.ctrlKey || ev.metaKey;
var key = ev.key || '';
function currentIndex(shown) {
if (lastClicked) {
for (var i = 0; i < shown.length; i++) {
if (shown[i].relativePath === lastClicked) return i;
}
}
var keys = Object.keys(selected);
if (keys.length) {
for (var j = 0; j < shown.length; j++) {
if (selected[shown[j].relativePath]) return j;
}
}
return 0;
}
function moveSelection(delta) {
var shown = visible();
if (!shown.length) return;
var index = currentIndex(shown) + delta;
if (index < 0) index = 0;
if (index >= shown.length) index = shown.length - 1;
selected = {};
selected[shown[index].relativePath] = true;
lastClicked = shown[index].relativePath;
render();
var rows = list.querySelectorAll('.files-item');
if (rows[index]) rows[index].scrollIntoView({ block: 'nearest' });
}
if (key === 'Escape') {
ev.preventDefault();
selected = {};
lastClicked = '';
render();
return;
}
if (key === 'ArrowDown' || key === 'ArrowUp') {
ev.preventDefault();
moveSelection(key === 'ArrowDown' ? 1 : -1);
return;
}
if (ctrl && key.toLowerCase() === 'a') { ev.preventDefault(); selected = {}; visible().forEach(function (item) { selected[item.relativePath] = true; }); render(); }
if (ctrl && key.toLowerCase() === 'x') { ev.preventDefault(); cutSelection(); }
if (ctrl && key.toLowerCase() === 'c') { ev.preventDefault(); copySelection(); }
if (ctrl && key.toLowerCase() === 'v') { ev.preventDefault(); paste(); }
});
c.__filesCleanup = function () {
window.removeEventListener('mousedown', mouseHistory, true);
window.removeEventListener('pointerdown', mouseHistory, true);
document.removeEventListener('pointerdown', mouseHistory, true);
c.removeEventListener('pointerdown', mouseHistory, true);
document.removeEventListener('mousedown', mouseHistory, true);
window.removeEventListener('mouseup', mouseHistory, true);
window.removeEventListener('auxclick', mouseHistory, true);
window.removeEventListener('keydown', keyHistory);
if (menu.parentNode) menu.parentNode.removeChild(menu);
};
load();
},
unmount: function (c) { if (c.__filesCleanup) c.__filesCleanup(); c.innerHTML = ''; }
};
window.VerstakPluginRegister('verstak.files', { components: { FilesView: FilesView } });
}.toString() + ')();';
}
function trashPluginBundle() {
return '(' + function () {
var PLUGIN_ID = 'verstak.trash';
function el(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
var value = attrs[key];
if (value == null) return;
if (key === 'className') node.className = value;
else if (key === 'textContent') node.textContent = value;
else if (key.indexOf('on') === 0) node.addEventListener(key.slice(2).toLowerCase(), value);
else if (key === 'value') node.value = value;
else if (key === 'disabled') node.disabled = !!value;
else node.setAttribute(key, value);
});
(children || []).forEach(function (child) {
if (child == null) return;
node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
});
return node;
}
function text(value) { return String(value == null ? '' : value); }
function cleanPath(value) { return text(value).split('/').filter(Boolean).join('/'); }
function nameFor(entry) {
var path = cleanPath(entry && entry.originalPath);
return text(entry && entry.basename).trim() || path.split('/').pop() || 'Untitled item';
}
function workspaceFor(entry) { return cleanPath(entry && entry.originalPath).split('/')[0] || 'Vault root'; }
function typeFor(entry) { return entry && entry.originalType === 'folder' ? 'Folder' : 'File'; }
function dateFor(entry) { return text(entry && entry.deletedAt); }
function errorText(error) { return error && error.message ? error.message : text(error); }
function isConflict(error) { return /conflict:/i.test(errorText(error)); }
var TrashView = {
mount: function (containerEl, props, api) {
var state = {
entries: [], workspace: '', query: '', sort: 'date-desc', loading: true,
busyId: '', confirmingId: '', status: '', statusError: false, disposed: false
};
function workspaces() {
var values = {};
state.entries.forEach(function (entry) { values[workspaceFor(entry)] = true; });
return Object.keys(values).sort();
}
function visible() {
var query = state.query.toLowerCase();
return state.entries.filter(function (entry) {
if (state.workspace && workspaceFor(entry) !== state.workspace) return false;
return !query || (nameFor(entry) + ' ' + text(entry.originalPath) + ' ' + workspaceFor(entry)).toLowerCase().indexOf(query) !== -1;
}).sort(function (left, right) {
if (state.sort === 'date-asc') return dateFor(left).localeCompare(dateFor(right));
if (state.sort === 'name-asc') return nameFor(left).localeCompare(nameFor(right));
return dateFor(right).localeCompare(dateFor(left));
});
}
function render() {
var rows = visible();
containerEl.innerHTML = '';
containerEl.className = 'trash-root';
containerEl.setAttribute('data-plugin-id', PLUGIN_ID);
var workspaceSelect = el('select', {
'data-trash-filter-workspace': '', value: state.workspace,
onChange: function (event) { state.workspace = event.target.value; render(); }
}, [el('option', { value: '' }, ['All workspaces'])]);
workspaces().forEach(function (workspace) {
workspaceSelect.appendChild(el('option', { value: workspace }, [workspace]));
});
var search = el('input', {
type: 'search', value: state.query, placeholder: 'Filter name or path',
'data-trash-filter-search': '',
onInput: function (event) { state.query = event.target.value; render(); }
}, []);
var sort = el('select', {
'data-trash-sort': '', value: state.sort,
onChange: function (event) { state.sort = event.target.value; render(); }
}, [
el('option', { value: 'date-desc' }, ['Deleted: newest']),
el('option', { value: 'date-asc' }, ['Deleted: oldest']),
el('option', { value: 'name-asc' }, ['Name'])
]);
containerEl.appendChild(el('div', { className: 'trash-toolbar' }, [
el('strong', {}, ['Trash']), search, workspaceSelect, sort,
el('button', { type: 'button', onClick: load }, ['Refresh'])
]));
containerEl.appendChild(el('div', {
className: 'trash-status' + (state.statusError ? ' error' : ''),
'data-trash-status': ''
}, [state.loading ? 'Loading deleted items...' : (state.status || rows.length + ' deleted items')]));
var list = el('div', { className: 'trash-list', 'data-trash-list': '' }, []);
if (state.loading) {
list.appendChild(el('div', { className: 'trash-empty' }, ['Loading deleted items...']));
} else if (!rows.length) {
list.appendChild(el('div', { className: 'trash-empty' }, [state.entries.length ? 'No deleted items match the current filters.' : 'Trash is empty.']));
} else {
rows.forEach(function (entry) {
list.appendChild(el('div', {
className: 'trash-row', 'data-trash-row': entry.trashId, 'data-trash-workspace': workspaceFor(entry)
}, [
el('span', { className: 'trash-name' }, [nameFor(entry)]),
el('span', { className: 'trash-workspace' }, [workspaceFor(entry)]),
el('span', { className: 'trash-path' }, [entry.originalPath || '']),
el('span', { className: 'trash-meta' }, [dateFor(entry)]),
el('span', { className: 'trash-meta' }, [typeFor(entry)]),
el('button', {
type: 'button', disabled: state.busyId === entry.trashId,
'data-trash-restore': entry.trashId,
onClick: function () { restore(entry); }
}, [state.busyId === entry.trashId ? 'Restoring...' : 'Restore']),
el('button', {
type: 'button', disabled: state.busyId === entry.trashId,
'data-trash-delete': entry.trashId,
onClick: function () { state.confirmingId = entry.trashId; render(); }
}, ['Delete permanently'])
]));
});
}
containerEl.appendChild(list);
var entry = state.entries.find(function (item) { return item.trashId === state.confirmingId; });
if (entry) {
containerEl.appendChild(el('div', { className: 'trash-confirm', 'data-trash-confirm': entry.trashId }, [
el('p', {}, ['Delete permanently?']),
el('span', {}, [entry.originalPath || nameFor(entry)]),
el('button', {
type: 'button', 'data-trash-confirm-cancel': entry.trashId,
onClick: function () { state.confirmingId = ''; render(); }
}, ['Cancel']),
el('button', {
type: 'button', disabled: state.busyId === entry.trashId,
'data-trash-confirm-delete': entry.trashId,
onClick: function () { deletePermanently(entry); }
}, ['Delete permanently'])
]));
}
}
function restore(entry) {
state.busyId = entry.trashId;
state.status = '';
state.statusError = false;
render();
api.files.restoreTrash(entry.trashId, { overwrite: false }).then(function () {
if (state.disposed) return;
state.entries = state.entries.filter(function (item) { return item.trashId !== entry.trashId; });
state.busyId = '';
state.status = 'Restored ' + nameFor(entry) + '.';
render();
}).catch(function (error) {
if (state.disposed) return;
state.busyId = '';
state.statusError = true;
state.status = isConflict(error)
? 'Restore blocked: an item already exists at the original path. Nothing was overwritten.'
: 'Restore failed: ' + errorText(error);
render();
});
}
function deletePermanently(entry) {
state.busyId = entry.trashId;
state.status = '';
state.statusError = false;
render();
api.files.deleteTrash(entry.trashId).then(function () {
if (state.disposed) return;
state.entries = state.entries.filter(function (item) { return item.trashId !== entry.trashId; });
state.busyId = '';
state.confirmingId = '';
state.status = 'Permanently deleted ' + nameFor(entry) + '.';
render();
}).catch(function (error) {
if (state.disposed) return;
state.busyId = '';
state.statusError = true;
state.status = 'Permanent delete failed: ' + errorText(error);
render();
});
}
function load() {
state.loading = true;
state.status = '';
state.statusError = false;
render();
api.files.listTrash().then(function (entries) {
if (state.disposed) return;
state.entries = Array.isArray(entries) ? entries : [];
state.loading = false;
render();
}).catch(function (error) {
if (state.disposed) return;
state.entries = [];
state.loading = false;
state.statusError = true;
state.status = 'Could not load Trash: ' + errorText(error);
render();
});
}
containerEl.__trashCleanup = function () { state.disposed = true; containerEl.innerHTML = ''; };
load();
},
unmount: function (containerEl) { if (containerEl.__trashCleanup) containerEl.__trashCleanup(); }
};
window.VerstakPluginRegister(PLUGIN_ID, { components: { TrashView: TrashView } });
}.toString() + ')();';
}
function simplePluginBundle(pluginId, componentName, rootClass, title) {
var markup = '<div class="' + rootClass + '"><h2>' + title + '</h2></div>';
return '(function(){var Component={mount:function(containerEl){containerEl.innerHTML=' + JSON.stringify(markup) + ';},unmount:function(containerEl){containerEl.innerHTML="";}};window.VerstakPluginRegister(' + JSON.stringify(pluginId) + ',{components:{' + componentName + ':Component}});})();';
}
function activityBundle() {
return '(' + function () {
var PLUGIN_ID = 'verstak.activity';
var LOW_VALUE_EVENT_TYPES = {
'workspace.selected': true,
'case.selected': true,
'file.selected': true,
'file.opened': true,
'note.opened': true
};
function injectStyles() {
if (document.getElementById('mock-activity-style')) return;
var style = document.createElement('style');
style.id = 'mock-activity-style';
style.textContent = [
'.activity-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#e0e0f0}',
'.activity-toolbar{display:flex;align-items:center;gap:.5rem;padding:.55rem .75rem;border-bottom:1px solid #16213e;background:#12122a;flex-wrap:wrap}',
'.activity-title{font-size:.86rem;font-weight:600;color:#f0f0ff}.activity-count,.activity-status{font-size:.74rem;color:#8b8ba8}.activity-spacer{flex:1}',
'.activity-btn{min-height:1.85rem;padding:.3rem .65rem;border:1px solid #1a3a5c;border-radius:4px;background:#0f3460;color:#e0e0f0;font-size:.76rem;cursor:pointer}.activity-btn:hover{background:#1a4a7a}.activity-btn:disabled{opacity:.45;cursor:default}.activity-btn.danger{border-color:#633;color:#ffb0b0}',
'.activity-candidates{display:grid;gap:.5rem;padding:.65rem .75rem;border-bottom:1px solid rgba(22,33,62,.75);background:#111126}.activity-candidates-title{font-size:.76rem;font-weight:600;color:#8b8ba8;text-transform:uppercase}.activity-candidate{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.65rem;padding:.55rem .65rem;border:1px solid rgba(78,204,163,.32);border-radius:4px;background:#14142c}.activity-candidate-title{font-size:.84rem;font-weight:600;color:#f4f7fb}.activity-candidate-facts{margin-top:.22rem;display:grid;gap:.14rem;color:#aaa;font-size:.76rem;line-height:1.4}.activity-candidate-actions{display:flex;gap:.35rem;align-items:flex-start;flex-wrap:wrap}.activity-candidate-minutes{color:#4ecca3;font-size:.76rem;white-space:nowrap}',
'.activity-list{flex:1;min-height:0;overflow:auto;background:#101020}.activity-empty{height:100%;display:flex;align-items:center;justify-content:center;padding:1.5rem;color:#8b8ba8;font-size:.84rem;line-height:1.45;text-align:center}.activity-row{display:grid;grid-template-columns:9.5rem minmax(0,1fr);gap:.75rem;padding:.72rem .85rem;border-bottom:1px solid rgba(22,33,62,.7)}.activity-time{font-size:.72rem;color:#777;white-space:nowrap}.activity-main{min-width:0}.activity-row-head{display:flex;align-items:center;gap:.45rem;min-width:0}.activity-type{font-size:.68rem;color:#4ecca3;text-transform:uppercase;letter-spacing:.04em}.activity-title-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#e0e0f0;font-size:.86rem}.activity-summary{margin-top:.25rem;color:#aaa;font-size:.78rem;line-height:1.4}.activity-source{margin-top:.25rem;color:#777;font-size:.72rem}',
'@media(max-width:760px){.activity-row,.activity-candidate{grid-template-columns:1fr;gap:.25rem}.activity-status{width:100%}}'
].join('');
document.head.appendChild(style);
}
function el(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (key === 'textContent') node.textContent = attrs[key];
else if (key === 'className') node.className = attrs[key];
else if (key === 'disabled') node.disabled = !!attrs[key];
else if (key.indexOf('data-') === 0) node.setAttribute(key, attrs[key]);
else if (key === 'onClick') node.addEventListener('click', attrs[key]);
else node[key] = attrs[key];
});
(children || []).forEach(function (child) {
if (child) node.appendChild(child);
});
return node;
}
function workspaceRoot(props) {
return String(
props.workspaceRootPath ||
(props.workspaceNode && (props.workspaceNode.rootPath || props.workspaceNode.name || props.workspaceNode.id)) ||
props.workspaceName ||
''
).trim();
}
function workspaceKey(root) {
return 'events:workspace:' + encodeURIComponent(root || '');
}
function rows(value) {
return Array.isArray(value) ? value.filter(function (item) { return item && typeof item === 'object'; }) : [];
}
function eventTime(value) {
var date = new Date(value || 0);
return isNaN(date.getTime()) ? 0 : date.getTime();
}
function formatDate(value) {
var date = new Date(value || 0);
if (isNaN(date.getTime())) return '-';
return date.toLocaleString(undefined, { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function title(activity) {
return activity.title || activity.summary || activity.type || activity.activityId || 'Activity event';
}
function workSessionCandidates(events, root) {
var ordered = rows(events).filter(function (activity) {
return !LOW_VALUE_EVENT_TYPES[String(activity.type || '').toLowerCase()] && eventTime(activity.occurredAt || activity.receivedAt);
}).sort(function (a, b) {
return eventTime(a.occurredAt || a.receivedAt) - eventTime(b.occurredAt || b.receivedAt);
});
var candidates = [];
var current = null;
function addCurrent() {
if (!current || current.events.length < 2) return;
var first = current.events[0];
var last = current.events[current.events.length - 1];
var duration = Math.round((eventTime(last.occurredAt || last.receivedAt) - eventTime(first.occurredAt || first.receivedAt)) / 60000);
if (duration < 10) return;
candidates.push({
candidateId: 'work-session:' + encodeURIComponent(current.workspaceRootPath) + ':' + encodeURIComponent(first.activityId || '') + ':' + encodeURIComponent(last.activityId || ''),
workspaceRootPath: current.workspaceRootPath,
startedAt: new Date(eventTime(first.occurredAt || first.receivedAt)).toISOString(),
endedAt: new Date(eventTime(last.occurredAt || last.receivedAt)).toISOString(),
estimatedMinutes: duration,
activityCount: current.events.length,
activityIds: current.events.map(function (activity) { return activity.activityId; }),
activities: current.events.map(function (activity) {
return {
activityId: activity.activityId,
type: activity.type || 'activity.event',
occurredAt: new Date(eventTime(activity.occurredAt || activity.receivedAt)).toISOString(),
sourcePluginId: activity.sourcePluginId || '',
workspaceRootPath: activity.workspaceRootPath || root || ''
};
})
});
}
ordered.forEach(function (activity) {
var workspace = activity.workspaceRootPath || root || '';
var time = eventTime(activity.occurredAt || activity.receivedAt);
if (!workspace) return;
if (!current) {
current = { workspaceRootPath: workspace, events: [activity] };
return;
}
var firstTime = eventTime(current.events[0].occurredAt || current.events[0].receivedAt);
var lastTime = eventTime(current.events[current.events.length - 1].occurredAt || current.events[current.events.length - 1].receivedAt);
if (current.workspaceRootPath !== workspace || time - lastTime > 20 * 60 * 1000 || time - firstTime > 120 * 60 * 1000) {
addCurrent();
current = { workspaceRootPath: workspace, events: [activity] };
return;
}
current.events.push(activity);
});
addCurrent();
return candidates.sort(function (a, b) { return b.endedAt.localeCompare(a.endedAt); });
}
function renderActivity(containerEl, props, api) {
injectStyles();
var rootPath = workspaceRoot(props || {});
var key = workspaceKey(rootPath);
var events = [];
var dismissed = {};
var statusText = 'Listening for workspace activity';
containerEl.innerHTML = '';
var root = el('div', { className: 'activity-root', 'data-plugin-id': PLUGIN_ID });
var toolbar = el('div', { className: 'activity-toolbar' });
var titleEl = el('span', { className: 'activity-title', textContent: rootPath ? 'Activity · ' + rootPath : 'Activity' });
var countEl = el('span', { className: 'activity-count' });
var statusEl = el('span', { className: 'activity-status' });
var clearBtn = el('button', {
className: 'activity-btn danger',
'data-activity-action': 'clear',
textContent: 'Clear',
onClick: function () {
events = [];
persist().then(render);
}
});
var candidatesEl = el('div', { className: 'activity-candidates', 'data-activity-section': 'work-session-candidates' });
var listEl = el('div', { className: 'activity-list' });
toolbar.appendChild(titleEl);
toolbar.appendChild(countEl);
toolbar.appendChild(el('span', { className: 'activity-spacer' }));
toolbar.appendChild(statusEl);
toolbar.appendChild(clearBtn);
root.appendChild(toolbar);
root.appendChild(candidatesEl);
root.appendChild(listEl);
containerEl.appendChild(root);
function readSettings() {
if (!api || !api.settings || typeof api.settings.read !== 'function') return Promise.resolve({});
return api.settings.read().then(function (settings) { return settings || {}; }).catch(function () { return {}; });
}
function persist() {
if (!api || !api.settings || typeof api.settings.write !== 'function') return Promise.resolve();
return api.settings.write(key, events.slice()).catch(function (err) {
statusText = 'Could not save activity: ' + (err && err.message ? err.message : String(err));
});
}
function renderCandidates() {
candidatesEl.innerHTML = '';
var items = workSessionCandidates(events, rootPath).filter(function (item) { return !dismissed[item.candidateId]; });
if (!items.length) return;
candidatesEl.appendChild(el('div', { className: 'activity-candidates-title', textContent: 'Possible journal entries' }));
items.forEach(function (item) {
candidatesEl.appendChild(el('div', { className: 'activity-candidate', 'data-work-session-candidate': item.candidateId }, [
el('div', {}, [
el('div', { className: 'activity-candidate-title', textContent: 'Possible journal entry' }),
el('div', { className: 'activity-candidate-facts' }, [
el('div', { textContent: 'Workspace: ' + item.workspaceRootPath }),
el('div', { textContent: 'Estimated duration: ' + item.estimatedMinutes + ' min' }),
el('div', { textContent: 'Activities: ' + item.activityCount })
])
]),
el('div', { className: 'activity-candidate-actions' }, [
el('div', { className: 'activity-candidate-minutes', textContent: item.estimatedMinutes + ' min' }),
el('button', { className: 'activity-btn', type: 'button', 'data-work-session-action': 'review', textContent: 'Review', onClick: function () {
window.dispatchEvent(new CustomEvent('verstak:workspace-open-tool', { detail: { kind: 'journal', toolRequest: { type: 'work-session-candidate', candidate: item } } }));
} }),
el('button', { className: 'activity-btn', type: 'button', 'data-work-session-action': 'dismiss', textContent: 'Dismiss', onClick: function () {
dismissed[item.candidateId] = true;
render();
} })
])
]));
});
}
function renderList() {
listEl.innerHTML = '';
if (!events.length) {
listEl.appendChild(el('div', {
className: 'activity-empty',
textContent: 'No activity events yet. File changes, browser captures, and conversions will appear here.'
}));
return;
}
events.slice().sort(function (a, b) { return eventTime(b.occurredAt || b.receivedAt) - eventTime(a.occurredAt || a.receivedAt); }).forEach(function (activity) {
listEl.appendChild(el('div', { className: 'activity-row', 'data-activity-id': activity.activityId }, [
el('div', { className: 'activity-time', textContent: formatDate(activity.occurredAt || activity.receivedAt) }),
el('div', { className: 'activity-main' }, [
el('div', { className: 'activity-row-head' }, [
el('span', { className: 'activity-type', textContent: activity.type || 'activity.event' }),
el('span', { className: 'activity-title-text', textContent: title(activity) })
]),
activity.summary ? el('div', { className: 'activity-summary', textContent: activity.summary }) : null,
activity.sourcePluginId ? el('div', { className: 'activity-source', textContent: activity.sourcePluginId }) : null
])
]));
});
}
function render() {
countEl.textContent = events.length + ' event' + (events.length === 1 ? '' : 's');
clearBtn.disabled = events.length === 0;
statusEl.textContent = statusText;
renderCandidates();
renderList();
}
readSettings().then(function (settings) {
events = rows(settings[key]);
render();
});
render();
}
var ActivityView = {
mount: renderActivity,
unmount: function (containerEl) { containerEl.innerHTML = ''; }
};
window.VerstakPluginRegister('verstak.activity', { components: { ActivityView: ActivityView } });
}.toString() + ')();';
}
function journalBundle() {
return '(' + function () {
var PLUGIN_ID = 'verstak.journal';
function injectStyles() {
if (document.getElementById('mock-journal-style')) return;
var style = document.createElement('style');
style.id = 'mock-journal-style';
style.textContent = [
'.journal-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#e0e0f0}',
'.journal-toolbar{display:flex;align-items:center;gap:.5rem;padding:.55rem .75rem;border-bottom:1px solid #16213e;background:#12122a}.journal-title{font-size:.86rem;font-weight:600;color:#f0f0ff}.journal-count,.journal-status{font-size:.74rem;color:#8b8ba8}.journal-spacer{flex:1}',
'.journal-btn{min-height:1.85rem;padding:.3rem .65rem;border:1px solid #1a3a5c;border-radius:4px;background:#0f3460;color:#e0e0f0;font-size:.76rem;cursor:pointer}.journal-btn.primary{background:#4ecca3;border-color:#4ecca3;color:#102018}',
'.journal-list{flex:1;min-height:0;overflow:auto;padding:.5rem .75rem}.journal-empty{padding:1.5rem;color:#8b8ba8}.journal-row{display:grid;grid-template-columns:8rem minmax(0,1fr) auto;gap:.7rem;padding:.65rem 0;border-bottom:1px solid rgba(22,33,62,.75)}.journal-entry-title{font-weight:600}.journal-summary,.journal-meta{margin-top:.22rem;font-size:.76rem;color:#aaa}.journal-minutes{color:#4ecca3;font-size:.78rem;white-space:nowrap}',
'.journal-modal-host[hidden]{display:none}.journal-modal-overlay{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;padding:1rem;background:rgba(0,0,0,.58)}.journal-modal{width:520px;max-width:96vw;display:grid;gap:.75rem;padding:1rem;border:1px solid #2c456a;border-radius:8px;background:#15152c;box-shadow:0 18px 44px rgba(0,0,0,.38)}.journal-modal-title{font-size:.95rem;font-weight:600}.journal-modal-grid{display:grid;grid-template-columns:1fr 8rem;gap:.6rem}.journal-field{display:grid;gap:.3rem;font-size:.72rem;color:#8b8ba8}.journal-field.wide{grid-column:1/-1}.journal-input{min-width:0;padding:.38rem .5rem;border:1px solid #2c456a;border-radius:4px;background:#0f1424;color:#f4f7fb;font:inherit}.journal-input.textarea{min-height:6rem;resize:vertical}.journal-candidate-context{display:grid;gap:.2rem;padding:.65rem;border:1px solid rgba(78,204,163,.34);border-radius:6px;background:#111126;font-size:.76rem;color:#b7c0d4}.journal-candidate-activities{display:grid;gap:.35rem;margin:0;padding:.65rem;border:1px solid #202b46;border-radius:6px;font-size:.74rem;color:#b7c0d4}.journal-candidate-activity{display:flex;align-items:flex-start;gap:.45rem}.journal-modal-actions{display:flex;justify-content:flex-end;gap:.5rem}'
].join('');
document.head.appendChild(style);
}
function el(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (key === 'textContent') node.textContent = attrs[key];
else if (key === 'className') node.className = attrs[key];
else if (key === 'disabled') node.disabled = !!attrs[key];
else if (key === 'checked') node.checked = !!attrs[key];
else if (key === 'value') node.value = attrs[key];
else if (key.indexOf('data-') === 0) node.setAttribute(key, attrs[key]);
else if (key === 'onClick') node.addEventListener('click', attrs[key]);
else node[key] = attrs[key];
});
(children || []).forEach(function (child) {
if (child) node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
});
return node;
}
function workspaceRoot(props) {
return String(props.workspaceRootPath || (props.workspaceNode && (props.workspaceNode.rootPath || props.workspaceNode.name || props.workspaceNode.id)) || props.workspaceName || '').trim();
}
function workspaceKey(root) {
return 'worklog:workspace:' + encodeURIComponent(root || '');
}
function rows(value) {
return Array.isArray(value) ? value.filter(function (item) { return item && typeof item === 'object'; }) : [];
}
function candidateFromProps(props, root) {
var request = props && props.toolRequest;
var candidate = request && request.type === 'work-session-candidate' ? request.candidate : null;
if (!candidate || candidate.workspaceRootPath !== root || !candidate.candidateId) return null;
var activities = rows(candidate.activities).filter(function (activity) { return activity.activityId; });
return {
candidateId: String(candidate.candidateId),
workspaceRootPath: root,
startedAt: candidate.startedAt || '',
endedAt: candidate.endedAt || '',
estimatedMinutes: Number(candidate.estimatedMinutes || 0),
activityCount: Number(candidate.activityCount || activities.length),
activities: activities,
activityIds: Array.isArray(candidate.activityIds) ? candidate.activityIds : activities.map(function (activity) { return activity.activityId; })
};
}
function completedTodoFromProps(props, root) {
var request = props && props.toolRequest;
var todo = request && request.type === 'completed-todo' ? request.todo : null;
if (!todo || todo.workspaceRootPath !== root || !todo.id || !todo.title) return null;
return {
id: String(todo.id),
title: String(todo.title),
description: String(todo.description || todo.body || ''),
workspaceRootPath: root,
completedAt: String(todo.completedAt || '')
};
}
function candidateDate(value) {
var date = new Date(value || '');
return isNaN(date.getTime()) ? new Date().toISOString().slice(0, 10) : date.toISOString().slice(0, 10);
}
function candidateTime(value) {
var date = new Date(value || '');
return isNaN(date.getTime()) ? String(value || '') : date.toLocaleString(undefined, { month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function JournalView() {}
JournalView.mount = function (containerEl, props, api) {
injectStyles();
var rootPath = workspaceRoot(props || {});
var key = workspaceKey(rootPath);
var entries = [];
var modalHost = el('div', { className: 'journal-modal-host', hidden: true });
containerEl.innerHTML = '';
var root = el('div', { className: 'journal-root', 'data-plugin-id': PLUGIN_ID });
var toolbar = el('div', { className: 'journal-toolbar' });
var countEl = el('span', { className: 'journal-count' });
var addBtn = el('button', { className: 'journal-btn primary', 'data-journal-action': 'add', textContent: 'Add', onClick: function () { showEntryModal(null); } });
var listEl = el('div', { className: 'journal-list' });
toolbar.appendChild(el('span', { className: 'journal-title', textContent: rootPath ? 'Journal · ' + rootPath : 'Journal' }));
toolbar.appendChild(countEl);
toolbar.appendChild(el('span', { className: 'journal-spacer' }));
toolbar.appendChild(addBtn);
root.appendChild(toolbar);
root.appendChild(listEl);
root.appendChild(modalHost);
containerEl.appendChild(root);
function closeModal() {
modalHost.innerHTML = '';
modalHost.hidden = true;
}
function persist() {
return api.settings.write(key, entries);
}
function showEntryModal(candidate, completedTodo) {
var reviewing = !!candidate;
var reviewingTodo = !reviewing && !!completedTodo;
var titleInput = el('input', { className: 'journal-input', type: 'text', value: reviewingTodo ? completedTodo.title : '', 'data-journal-input': 'title' });
var summaryInput = el('textarea', { className: 'journal-input textarea', value: reviewingTodo ? completedTodo.description : '', 'data-journal-input': 'summary' });
var minutesInput = el('input', { className: 'journal-input', type: 'number', value: reviewing ? String(candidate.estimatedMinutes) : (reviewingTodo ? '0' : '30'), 'data-journal-input': 'minutes' });
var dateInput = el('input', { className: 'journal-input', type: 'date', value: reviewing ? candidateDate(candidate.startedAt) : (reviewingTodo ? candidateDate(completedTodo.completedAt) : new Date().toISOString().slice(0, 10)), 'data-journal-input': 'date' });
var billableInput = el('input', { type: 'checkbox', checked: false, 'data-journal-input': 'billable' });
var activityInputs = reviewing ? candidate.activities.map(function (activity) {
return { activity: activity, input: el('input', { type: 'checkbox', checked: true, 'data-journal-candidate-activity': activity.activityId }) };
}) : [];
var context = reviewing ? el('div', { className: 'journal-candidate-context', 'data-journal-candidate': candidate.candidateId }, [
el('strong', { textContent: 'Possible journal entry' }),
el('div', { textContent: 'Workspace: ' + candidate.workspaceRootPath }),
el('div', { textContent: 'Time: ' + candidateTime(candidate.startedAt) + ' - ' + candidateTime(candidate.endedAt) }),
el('div', { textContent: 'Estimated duration: ' + candidate.estimatedMinutes + ' min' }),
el('div', { textContent: 'Activities: ' + candidate.activityCount })
]) : null;
var todoContext = reviewingTodo ? el('div', { className: 'journal-candidate-context', 'data-journal-todo': completedTodo.id }, [
el('strong', { textContent: 'Completed todo' }),
el('div', { textContent: 'Workspace: ' + completedTodo.workspaceRootPath }),
completedTodo.completedAt ? el('div', { textContent: 'Completed: ' + candidateTime(completedTodo.completedAt) }) : null
]) : null;
var linked = reviewing ? el('fieldset', { className: 'journal-candidate-activities' }, [
el('legend', { textContent: 'Linked activities' })
].concat(activityInputs.map(function (item) {
return el('label', { className: 'journal-candidate-activity' }, [item.input, (item.activity.type || 'activity.event') + ' · ' + item.activity.activityId]);
}))) : null;
function save() {
var title = String(titleInput.value || '').trim();
if (!title) return;
if (reviewingTodo && entries.some(function (entry) { return entry.sourceTodoId === completedTodo.id; })) return;
var entry = {
entryId: 'journal:' + Date.now(),
workspaceRootPath: rootPath,
date: dateInput.value,
title: title,
summary: String(summaryInput.value || ''),
minutes: Number(minutesInput.value || 0),
billable: billableInput.checked === true,
sourceCandidateId: reviewing ? candidate.candidateId : '',
sourceTodoId: reviewingTodo ? completedTodo.id : '',
activityIds: reviewing ? activityInputs.filter(function (item) { return item.input.checked; }).map(function (item) { return item.activity.activityId; }) : []
};
entries = [entry].concat(entries);
closeModal();
persist().then(render);
}
modalHost.innerHTML = '';
modalHost.hidden = false;
modalHost.appendChild(el('div', { className: 'journal-modal-overlay' }, [
el('div', { className: 'journal-modal' }, [
el('div', { className: 'journal-modal-title', textContent: reviewing ? 'Review possible journal entry' : (reviewingTodo ? 'Create journal entry from completed todo' : 'Add journal entry') }),
context,
todoContext,
el('div', { className: 'journal-modal-grid' }, [
el('label', { className: 'journal-field' }, ['Date', dateInput]),
el('label', { className: 'journal-field' }, ['Minutes', minutesInput]),
el('label', { className: 'journal-field wide' }, ['Title', titleInput]),
el('label', { className: 'journal-field wide' }, ['Body', summaryInput]),
el('label', { className: 'journal-field wide' }, [billableInput, 'Billable'])
]),
linked,
el('div', { className: 'journal-modal-actions' }, [
el('button', { className: 'journal-btn', textContent: 'Cancel', onClick: closeModal }),
el('button', { className: 'journal-btn primary', 'data-journal-action': 'save-entry', textContent: 'Add entry', onClick: save })
])
])
]));
titleInput.focus();
}
function render() {
countEl.textContent = entries.length + ' entr' + (entries.length === 1 ? 'y' : 'ies');
listEl.innerHTML = '';
if (!entries.length) {
listEl.appendChild(el('div', { className: 'journal-empty', textContent: 'No journal entries yet.' }));
return;
}
entries.forEach(function (entry) {
var activityIds = Array.isArray(entry.activityIds) ? entry.activityIds : (Array.isArray(entry.eventIds) ? entry.eventIds : []);
listEl.appendChild(el('div', { className: 'journal-row', 'data-journal-entry': entry.entryId }, [
el('div', { textContent: entry.date }),
el('div', {}, [
el('div', { className: 'journal-entry-title', textContent: entry.title }),
entry.summary ? el('div', { className: 'journal-summary', textContent: entry.summary }) : null,
el('div', { className: 'journal-meta', textContent: entry.workspaceRootPath + (activityIds.length ? ' · ' + activityIds.length + ' linked activities' : '') + (entry.sourceTodoId ? ' · linked todo' : '') })
]),
el('div', { className: 'journal-minutes', textContent: entry.minutes + ' min' })
]));
});
}
api.settings.read(key).then(function (stored) {
entries = rows(stored);
render();
var candidate = candidateFromProps(props || {}, rootPath);
var completedTodo = completedTodoFromProps(props || {}, rootPath);
if (candidate) showEntryModal(candidate);
else if (completedTodo) showEntryModal(null, completedTodo);
}).catch(function () { render(); });
render();
};
JournalView.unmount = function (containerEl) { containerEl.innerHTML = ''; };
window.VerstakPluginRegister('verstak.journal', { components: { JournalView: JournalView } });
}.toString() + ')();';
}
function browserInboxBundle() {
return '(' + function () {
var PLUGIN_ID = 'verstak.browser-inbox';
var GLOBAL_KEY = 'captures:global';
var LEGACY_KEY = 'captures';
var WORKSPACE_PREFIX = 'captures:workspace:';
function injectStyles() {
if (document.getElementById('mock-browser-inbox-style')) return;
var style = document.createElement('style');
style.id = 'mock-browser-inbox-style';
style.textContent = [
'.browser-inbox-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#e0e0f0}',
'.browser-inbox-toolbar{display:flex;align-items:center;gap:.5rem;padding:.55rem .75rem;border-bottom:1px solid #16213e;background:#12122a;flex-wrap:wrap}',
'.browser-inbox-title{font-size:.86rem;font-weight:600;color:#f0f0ff}.browser-inbox-count,.browser-inbox-status{font-size:.74rem;color:#8b8ba8}.browser-inbox-spacer{flex:1}',
'.browser-inbox-filters{display:flex;align-items:center;gap:.35rem;flex:1;flex-wrap:wrap}.browser-inbox-select,.browser-inbox-input{min-height:1.85rem;max-width:12rem;border:1px solid #1a3a5c;border-radius:4px;background:#101020;color:#e0e0f0;padding:.25rem .4rem;font-size:.76rem}.browser-inbox-input{width:12rem}',
'.browser-inbox-btn{min-height:1.85rem;padding:.3rem .65rem;border:1px solid #1a3a5c;border-radius:4px;background:#0f3460;color:#e0e0f0;font-size:.76rem;cursor:pointer}.browser-inbox-btn:hover{background:#1a4a7a}.browser-inbox-btn:disabled{opacity:.45;cursor:default}.browser-inbox-btn.danger{border-color:#633;color:#ffb0b0}',
'.browser-inbox-body{flex:1;min-height:0;display:grid;grid-template-columns:minmax(260px,360px) minmax(0,1fr)}.browser-inbox-list{min-height:0;overflow:auto;border-right:1px solid #16213e;background:#101020}.browser-inbox-detail{min-width:0;min-height:0;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem}',
'.browser-inbox-empty,.browser-inbox-detail-empty{height:100%;display:flex;align-items:center;justify-content:center;padding:1.5rem;color:#8b8ba8;font-size:.84rem;line-height:1.45;text-align:center}.browser-inbox-detail-empty{height:auto;margin:auto}',
'.browser-inbox-row{display:grid;gap:.25rem;padding:.65rem .75rem;border-bottom:1px solid rgba(22,33,62,.75);cursor:pointer}.browser-inbox-row:hover{background:#17172d}.browser-inbox-row.selected{background:#1a2a3a}.browser-inbox-row-head{display:flex;align-items:center;gap:.45rem;min-width:0}.browser-inbox-kind{color:#4ecca3;font-size:.68rem;text-transform:uppercase}.browser-inbox-row-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.86rem}.browser-inbox-row-url,.browser-inbox-row-text{min-width:0;overflow:hidden;text-overflow:ellipsis;color:#8b8ba8;font-size:.74rem}',
'.browser-inbox-detail-title{font-size:1rem;font-weight:600;color:#f4f7fb;overflow-wrap:anywhere}.browser-inbox-meta{display:grid;grid-template-columns:7rem minmax(0,1fr);gap:.35rem .75rem;font-size:.78rem}.browser-inbox-meta-label{color:#777}.browser-inbox-meta-value{color:#ccc;overflow-wrap:anywhere}.browser-inbox-text{padding:.75rem;border:1px solid #24304f;border-radius:6px;background:#101020;color:#ddd;font-size:.84rem;line-height:1.5;white-space:pre-wrap}.browser-inbox-detail-actions{display:flex;gap:.5rem;flex-wrap:wrap}',
'@media(max-width:760px){.browser-inbox-body{grid-template-columns:1fr}.browser-inbox-list{border-right:0;border-bottom:1px solid #16213e;max-height:45vh}.browser-inbox-meta{grid-template-columns:1fr}}'
].join('');
document.head.appendChild(style);
}
function el(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (key === 'textContent') node.textContent = attrs[key];
else if (key === 'className') node.className = attrs[key];
else if (key === 'disabled') node.disabled = !!attrs[key];
else if (key === 'value') node.value = attrs[key];
else if (key.indexOf('data-') === 0) node.setAttribute(key, attrs[key]);
else if (key === 'onClick') node.addEventListener('click', attrs[key]);
else if (key === 'onChange') node.addEventListener('change', attrs[key]);
else if (key === 'onInput') node.addEventListener('input', attrs[key]);
else node[key] = attrs[key];
});
(children || []).forEach(function (child) {
if (child) node.appendChild(child);
});
return node;
}
function workspaceRoot(props) {
return String(
props.workspaceRootPath ||
(props.workspaceNode && (props.workspaceNode.rootPath || props.workspaceNode.name || props.workspaceNode.id)) ||
props.workspaceName ||
''
).trim();
}
function workspaceKey(root) {
return 'captures:workspace:' + encodeURIComponent(root || '');
}
function cleanWorkspace(value) {
return String(value == null ? '' : value).trim().replace(/^\/+|\/+$/g, '');
}
function workspaceFromKey(key) {
key = String(key || '');
if (key.indexOf(WORKSPACE_PREFIX) !== 0) return '';
try {
return cleanWorkspace(decodeURIComponent(key.slice(WORKSPACE_PREFIX.length)));
} catch (_) {
return '';
}
}
function rows(value) {
return Array.isArray(value) ? value.filter(function (item) { return item && typeof item === 'object'; }) : [];
}
function normalizeRows(value, storageKey) {
return rows(value).filter(function (item) { return item.captureId; }).map(function (item) {
var workspaceRootPath = cleanWorkspace(item.workspaceRootPath || item.workspaceName) || workspaceFromKey(storageKey);
return Object.assign({}, item, {
workspaceRootPath: workspaceRootPath,
workspaceName: cleanWorkspace(item.workspaceName || workspaceRootPath),
processed: item.processed === true
});
});
}
function sortCaptures(items) {
var seen = {};
return items.filter(function (item) {
if (!item || !item.captureId || seen[item.captureId]) return false;
seen[item.captureId] = true;
return true;
}).sort(function (a, b) {
return String(b.capturedAt || b.receivedAt || '').localeCompare(String(a.capturedAt || a.receivedAt || ''));
});
}
function title(capture) {
return capture.title || capture.fileName || capture.url || capture.captureId || 'Untitled capture';
}
function renderBrowserInbox(containerEl, props, api) {
injectStyles();
var rootPath = workspaceRoot(props || {});
var captures = [];
var selectedId = '';
var statusText = 'Ready for browser captures';
var workspaceOptions = [];
var statusFilter = 'all';
var workspaceFilter = '';
var searchQuery = '';
containerEl.innerHTML = '';
var root = el('div', { className: 'browser-inbox-root', 'data-plugin-id': PLUGIN_ID });
var toolbar = el('div', { className: 'browser-inbox-toolbar' });
var titleEl = el('span', { className: 'browser-inbox-title', textContent: rootPath ? 'Browser Inbox · ' + rootPath : 'Browser Inbox' });
var countEl = el('span', { className: 'browser-inbox-count' });
var statusEl = el('span', { className: 'browser-inbox-status' });
var filtersEl = el('div', { className: 'browser-inbox-filters' });
var statusFilterEl = el('select', {
className: 'browser-inbox-select',
'data-browser-inbox-filter': 'status',
onChange: function (event) {
statusFilter = String(event.target.value || 'all');
selectedId = '';
render();
}
}, [
el('option', { value: 'all', textContent: 'All captures' }),
el('option', { value: 'unassigned', textContent: 'Unassigned' }),
el('option', { value: 'unprocessed', textContent: 'Unprocessed' }),
el('option', { value: 'processed', textContent: 'Processed' })
]);
var workspaceFilterEl = el('select', {
className: 'browser-inbox-select',
'data-browser-inbox-filter': 'workspace',
onChange: function (event) {
workspaceFilter = cleanWorkspace(event.target.value);
selectedId = '';
render();
}
});
var searchInput = el('input', {
className: 'browser-inbox-input',
type: 'search',
placeholder: 'Search captures',
'data-browser-inbox-filter': 'search',
onInput: function (event) {
searchQuery = String(event.target.value || '').trim().toLowerCase();
selectedId = '';
renderList();
renderDetail();
renderCount();
}
});
var clearBtn = el('button', {
className: 'browser-inbox-btn danger',
'data-browser-inbox-action': 'clear',
textContent: 'Clear',
onClick: function () {
clearScope().then(render);
}
});
var body = el('div', { className: 'browser-inbox-body' });
var listEl = el('div', { className: 'browser-inbox-list' });
var detailEl = el('div', { className: 'browser-inbox-detail' });
toolbar.appendChild(titleEl);
toolbar.appendChild(countEl);
filtersEl.appendChild(statusFilterEl);
if (!rootPath) filtersEl.appendChild(workspaceFilterEl);
filtersEl.appendChild(searchInput);
toolbar.appendChild(filtersEl);
toolbar.appendChild(el('span', { className: 'browser-inbox-spacer' }));
toolbar.appendChild(statusEl);
toolbar.appendChild(clearBtn);
body.appendChild(listEl);
body.appendChild(detailEl);
root.appendChild(toolbar);
root.appendChild(body);
containerEl.appendChild(root);
function option(value, label) {
return el('option', { value: value, textContent: label });
}
function workspaceRoots() {
var roots = workspaceOptions.slice();
captures.forEach(function (capture) {
var workspace = cleanWorkspace(capture.workspaceRootPath);
if (workspace && roots.indexOf(workspace) === -1) roots.push(workspace);
});
if (rootPath && roots.indexOf(rootPath) === -1) roots.push(rootPath);
return roots.sort(function (a, b) { return a.localeCompare(b); });
}
function renderWorkspaceFilterOptions() {
if (rootPath) return;
workspaceFilterEl.innerHTML = '';
workspaceFilterEl.appendChild(option('', 'All workspaces'));
workspaceRoots().forEach(function (workspace) {
workspaceFilterEl.appendChild(option(workspace, workspace));
});
workspaceFilterEl.value = workspaceFilter;
}
function visibleCaptures() {
return captures.filter(function (capture) {
var workspace = cleanWorkspace(capture.workspaceRootPath);
if (rootPath && workspace !== rootPath) return false;
if (!rootPath && workspaceFilter && workspace !== workspaceFilter) return false;
if (statusFilter === 'unassigned' && workspace) return false;
if (statusFilter === 'unprocessed' && capture.processed === true) return false;
if (statusFilter === 'processed' && capture.processed !== true) return false;
if (!searchQuery) return true;
return [title(capture), capture.url, capture.domain, capture.text, workspace].join('\n').toLowerCase().indexOf(searchQuery) !== -1;
});
}
function readSettings() {
if (!api || !api.settings || typeof api.settings.read !== 'function') return Promise.resolve({});
return api.settings.read().then(function (settings) { return settings || {}; }).catch(function () { return {}; });
}
function capturesFromSettings(settings) {
var keys = [GLOBAL_KEY, LEGACY_KEY];
Object.keys(settings || {}).forEach(function (name) {
if (name.indexOf(WORKSPACE_PREFIX) === 0) keys.push(name);
});
var all = [];
keys.forEach(function (name) {
all = all.concat(normalizeRows((settings || {})[name], name));
});
return sortCaptures(all);
}
function loadWorkspaceOptions() {
if (!api || !api.files || typeof api.files.list !== 'function') return Promise.resolve();
return api.files.list('').then(function (entries) {
workspaceOptions = (Array.isArray(entries) ? entries : []).filter(function (entry) {
return String(entry && entry.type || '').toLowerCase() === 'folder';
}).map(function (entry) {
return cleanWorkspace(entry.relativePath || entry.name);
}).filter(function (workspace) {
return workspace && workspace.indexOf('/') === -1;
});
}).catch(function () {
workspaceOptions = [];
});
}
function persist() {
if (!api || !api.settings || typeof api.settings.write !== 'function') return Promise.resolve();
return api.settings.write(GLOBAL_KEY, sortCaptures(captures)).catch(function (err) {
statusText = 'Could not save inbox: ' + (err && err.message ? err.message : String(err));
});
}
function selectedCapture() {
var visible = visibleCaptures();
for (var i = 0; i < visible.length; i += 1) {
if (visible[i].captureId === selectedId) return visible[i];
}
return visible[0] || null;
}
function clearScope() {
var ids = (rootPath ? captures.filter(function (capture) {
return cleanWorkspace(capture.workspaceRootPath) === rootPath;
}) : captures).map(function (capture) { return capture.captureId; });
captures = captures.filter(function (capture) { return ids.indexOf(capture.captureId) === -1; });
selectedId = '';
statusText = rootPath ? 'Workspace captures cleared' : 'Inbox cleared';
return persist();
}
function removeCapture(captureId) {
captures = captures.filter(function (item) { return item.captureId !== captureId; });
selectedId = '';
statusText = 'Capture deleted';
return persist().then(render);
}
function assignWorkspace(captureId, workspace) {
workspace = cleanWorkspace(workspace);
captures = captures.map(function (capture) {
if (capture.captureId !== captureId) return capture;
return Object.assign({}, capture, { workspaceRootPath: workspace, workspaceName: workspace });
});
if (workspace && workspaceOptions.indexOf(workspace) === -1) workspaceOptions.push(workspace);
statusText = workspace ? 'Capture assigned to ' + workspace : 'Capture is unassigned';
return persist().then(render);
}
function setProcessed(captureId, processed) {
captures = captures.map(function (capture) {
return capture.captureId === captureId ? Object.assign({}, capture, { processed: processed === true }) : capture;
});
statusText = processed ? 'Capture marked processed' : 'Capture marked unprocessed';
return persist().then(render);
}
function conversionAction(kind, capture) {
statusText = 'Ready to create ' + kind + ': ' + title(capture);
render();
}
function renderList() {
listEl.innerHTML = '';
var visible = visibleCaptures();
if (visible.length === 0) {
listEl.appendChild(el('div', {
className: 'browser-inbox-empty',
textContent: captures.length === 0
? 'No browser captures yet. Keep this view open, then send a page, selection, link, or file from the browser extension.'
: 'No captures match the current filters.'
}));
return;
}
visible.forEach(function (capture) {
var workspace = cleanWorkspace(capture.workspaceRootPath);
var row = el('div', {
className: 'browser-inbox-row' + (capture.captureId === selectedId ? ' selected' : ''),
'data-browser-capture-id': capture.captureId,
onClick: function () {
selectedId = capture.captureId;
render();
}
}, [
el('div', { className: 'browser-inbox-row-head' }, [
el('span', { className: 'browser-inbox-kind', textContent: capture.kind || 'capture' }),
el('span', { className: 'browser-inbox-row-title', textContent: title(capture) })
]),
el('div', { className: 'browser-inbox-row-url', textContent: capture.url || capture.domain || capture.captureId || '' })
]);
row.appendChild(el('div', { className: 'browser-inbox-row-text', textContent: (workspace || 'Unassigned') + ' · ' + (capture.processed ? 'Processed' : 'Unprocessed') }));
if (capture.text) row.appendChild(el('div', { className: 'browser-inbox-row-text', textContent: capture.text }));
listEl.appendChild(row);
});
}
function renderDetail() {
detailEl.innerHTML = '';
var capture = selectedCapture();
if (!capture) {
detailEl.appendChild(el('div', { className: 'browser-inbox-detail-empty', textContent: 'Select a capture to inspect it.' }));
return;
}
selectedId = capture.captureId;
detailEl.appendChild(el('div', { className: 'browser-inbox-detail-title', textContent: title(capture) }));
detailEl.appendChild(el('div', { className: 'browser-inbox-meta' }, [
el('div', { className: 'browser-inbox-meta-label', textContent: 'Kind' }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.kind || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'URL' }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.url || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'Domain' }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.domain || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'Browser' }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.browserName || capture.source || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'Workspace' }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.workspaceRootPath || 'Unassigned' }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'Status' }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.processed ? 'Processed' : 'Unprocessed' })
]));
var assignment = el('select', {
className: 'browser-inbox-select',
'data-browser-inbox-assignment': capture.captureId,
onChange: function (event) { assignWorkspace(capture.captureId, event.target.value); }
});
assignment.appendChild(option('', 'Unassigned'));
workspaceRoots().forEach(function (workspace) {
assignment.appendChild(option(workspace, workspace));
});
assignment.value = capture.workspaceRootPath || '';
var assignmentRow = el('div', { className: 'browser-inbox-detail-actions' }, [assignment]);
if (capture.workspaceRootPath) {
assignmentRow.appendChild(el('button', {
className: 'browser-inbox-btn',
'data-browser-inbox-action': 'clear-assignment',
textContent: 'Clear assignment',
onClick: function () { assignWorkspace(capture.captureId, ''); }
}));
}
detailEl.appendChild(assignmentRow);
if (capture.text) detailEl.appendChild(el('div', { className: 'browser-inbox-text', textContent: capture.text }));
if (capture.fileText) detailEl.appendChild(el('div', { className: 'browser-inbox-text', textContent: capture.fileText }));
var actionButtons = [
el('button', {
className: 'browser-inbox-btn',
'data-browser-inbox-action': 'toggle-processed',
textContent: capture.processed ? 'Mark Unprocessed' : 'Mark Processed',
onClick: function () { setProcessed(capture.captureId, !capture.processed); }
})
];
if (capture.workspaceRootPath) {
actionButtons.push(el('button', { className: 'browser-inbox-btn', 'data-browser-inbox-action': 'create-note', textContent: 'Create Note', onClick: function () { conversionAction('note', capture); } }));
if (capture.url) actionButtons.push(el('button', { className: 'browser-inbox-btn', 'data-browser-inbox-action': 'create-link', textContent: 'Create Link', onClick: function () { conversionAction('link', capture); } }));
if (capture.kind === 'file') actionButtons.push(el('button', { className: 'browser-inbox-btn', 'data-browser-inbox-action': 'create-file', textContent: 'Create File', onClick: function () { conversionAction('file', capture); } }));
}
actionButtons.push(el('button', { className: 'browser-inbox-btn danger', 'data-browser-inbox-action': 'remove', textContent: 'Delete', onClick: function () { removeCapture(capture.captureId); } }));
detailEl.appendChild(el('div', { className: 'browser-inbox-detail-actions' }, actionButtons));
}
function renderCount() {
var visible = visibleCaptures();
countEl.textContent = visible.length === captures.length
? captures.length + ' item' + (captures.length === 1 ? '' : 's')
: visible.length + ' of ' + captures.length + ' items';
clearBtn.disabled = rootPath
? !captures.some(function (capture) { return cleanWorkspace(capture.workspaceRootPath) === rootPath; })
: captures.length === 0;
}
function render() {
statusFilterEl.value = statusFilter;
searchInput.value = searchQuery;
renderWorkspaceFilterOptions();
renderCount();
statusEl.textContent = statusText;
renderList();
renderDetail();
}
Promise.all([readSettings(), loadWorkspaceOptions()]).then(function (result) {
captures = capturesFromSettings(result[0]);
selectedId = captures[0] ? captures[0].captureId : '';
render();
});
render();
}
var BrowserInboxView = {
mount: renderBrowserInbox,
unmount: function (containerEl) { containerEl.innerHTML = ''; }
};
window.VerstakPluginRegister('verstak.browser-inbox', { components: { BrowserInboxView: BrowserInboxView } });
}.toString() + ')();';
}
function todoBundle() {
return '(' + function () {
var PLUGIN_ID = 'verstak.todo';
var GLOBAL_KEY = 'todos:global';
function injectStyles() {
if (document.getElementById('mock-todo-style')) return;
var style = document.createElement('style');
style.id = 'mock-todo-style';
style.textContent = [
'.todo-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#e0e0f0}',
'.todo-toolbar{display:flex;align-items:center;gap:.5rem;padding:.55rem .75rem;border-bottom:1px solid #16213e;background:#12122a;flex-wrap:wrap}.todo-title{font-size:.86rem;font-weight:600;color:#f0f0ff}.todo-count,.todo-status{font-size:.74rem;color:#8b8ba8}.todo-spacer{flex:1}.todo-filters{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap}',
'.todo-input,.todo-select{min-height:1.85rem;box-sizing:border-box;border:1px solid #1a3a5c;border-radius:4px;background:#101020;color:#e0e0f0;padding:.28rem .42rem;font-size:.76rem}.todo-input.search{width:12rem}.todo-input.textarea{min-height:6rem;resize:vertical}',
'.todo-btn{min-height:1.85rem;padding:.3rem .65rem;border:1px solid #1a3a5c;border-radius:4px;background:#0f3460;color:#e0e0f0;font-size:.76rem;cursor:pointer}.todo-btn.primary{background:#4ecca3;border-color:#4ecca3;color:#102018}.todo-btn.danger{border-color:#633;color:#ffb0b0}',
'.todo-list{flex:1;min-height:0;overflow:auto;padding:.5rem .75rem}.todo-empty{padding:1.5rem;color:#8b8ba8}.todo-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.7rem;padding:.7rem 0;border-bottom:1px solid rgba(22,33,62,.75)}.todo-row.done .todo-row-title{text-decoration:line-through;color:#8b8ba8}.todo-row-title{font-weight:600}.todo-row-description,.todo-row-meta{margin-top:.25rem;font-size:.76rem;color:#aaa}.todo-row-actions{display:flex;justify-content:flex-end;gap:.35rem;flex-wrap:wrap}.todo-badge{display:inline-flex;margin-right:.3rem;padding:.08rem .28rem;border:1px solid #24304f;border-radius:4px}.todo-badge.overdue,.todo-badge.reminder-due{border-color:#633;color:#ffb0b0}.todo-badge.due-soon{border-color:#7a6633;color:#f2d17b}',
'.todo-modal-host[hidden]{display:none}.todo-modal-overlay{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;padding:1rem;background:rgba(0,0,0,.58)}.todo-modal{width:540px;max-width:96vw;display:grid;gap:.75rem;padding:1rem;border:1px solid #2c456a;border-radius:8px;background:#15152c;box-shadow:0 18px 44px rgba(0,0,0,.38)}.todo-modal-title{font-size:.95rem;font-weight:600}.todo-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:.6rem}.todo-field{display:grid;gap:.3rem;font-size:.72rem;color:#8b8ba8}.todo-field.wide{grid-column:1/-1}.todo-modal-actions{display:flex;justify-content:flex-end;gap:.5rem}'
].join('');
document.head.appendChild(style);
}
function el(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (key === 'textContent') node.textContent = attrs[key];
else if (key === 'className') node.className = attrs[key];
else if (key === 'value') node.value = attrs[key];
else if (key === 'checked') node.checked = !!attrs[key];
else if (key === 'disabled') node.disabled = !!attrs[key];
else if (key.indexOf('data-') === 0) node.setAttribute(key, attrs[key]);
else if (key === 'onClick') node.addEventListener('click', attrs[key]);
else if (key === 'onChange') node.addEventListener('change', attrs[key]);
else if (key === 'onInput') node.addEventListener('input', attrs[key]);
else node[key] = attrs[key];
});
(children || []).forEach(function (child) {
if (child) node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
});
return node;
}
function text(value) { return String(value == null ? '' : value); }
function rootFromProps(props) {
return text((props && (props.workspaceRootPath || props.workspaceName || props.workspaceNodeId)) || (props && props.workspaceNode && (props.workspaceNode.rootPath || props.workspaceNode.name || props.workspaceNode.id))).trim();
}
function rows(value) { return Array.isArray(value) ? value.filter(function (item) { return item && typeof item === 'object'; }) : []; }
function now() { return new Date().toISOString(); }
function dateMs(value) {
value = text(value).trim();
if (!value) return 0;
var normalized = /^\d{4}-\d{2}-\d{2}$/.test(value) ? value + 'T00:00:00' : value;
var date = new Date(normalized);
return isNaN(date.getTime()) ? 0 : date.getTime();
}
function todoId(root, title) { return 'todo:' + (root || 'global') + ':' + Date.now() + ':' + text(title).trim().replace(/\s+/g, '-'); }
function normalizeTodo(value) {
value = value || {};
var status = ['open', 'done', 'cancelled'].indexOf(text(value.status).toLowerCase()) === -1 ? 'open' : text(value.status).toLowerCase();
var createdAt = text(value.createdAt).trim() || now();
return {
id: text(value.id).trim() || todoId(text(value.workspaceRootPath).trim(), value.title),
title: text(value.title).trim(),
description: text(value.description || value.body),
workspaceRootPath: text(value.workspaceRootPath || value.workspaceName).trim(),
workspaceName: text(value.workspaceName || value.workspaceRootPath).trim(),
status: status,
priority: ['low', 'normal', 'high'].indexOf(text(value.priority).toLowerCase()) === -1 ? 'normal' : text(value.priority).toLowerCase(),
dueAt: /^\d{4}-\d{2}-\d{2}$/.test(text(value.dueAt)) ? text(value.dueAt) : '',
reminderAt: text(value.reminderAt),
createdAt: createdAt,
updatedAt: text(value.updatedAt).trim() || createdAt,
completedAt: status === 'done' ? (text(value.completedAt).trim() || createdAt) : '',
sourceUrl: text(value.sourceUrl),
linkedJournalEntryId: text(value.linkedJournalEntryId)
};
}
function sortTodos(list, sortMode) {
return list.slice().sort(function (a, b) {
if (sortMode === 'updated') return text(b.updatedAt).localeCompare(text(a.updatedAt));
var aValue = dateMs(sortMode === 'reminder' ? a.reminderAt : a.dueAt) || Number.MAX_SAFE_INTEGER;
var bValue = dateMs(sortMode === 'reminder' ? b.reminderAt : b.dueAt) || Number.MAX_SAFE_INTEGER;
return aValue - bValue || text(b.updatedAt).localeCompare(text(a.updatedAt));
});
}
function dueState(todo) {
var dueAt = dateMs(todo.dueAt);
if (!dueAt || todo.status !== 'open') return '';
var nowMs = Date.now();
if (dueAt < nowMs) return 'overdue';
if (dueAt <= nowMs + 3 * 24 * 60 * 60 * 1000) return 'due-soon';
return '';
}
function reminderIsDue(todo) { return todo.status === 'open' && dateMs(todo.reminderAt) > 0 && dateMs(todo.reminderAt) <= Date.now(); }
function TodoView() {}
TodoView.mount = function (containerEl, props, api) {
injectStyles();
var workspaceRoot = rootFromProps(props || {});
var isWorkspace = !!workspaceRoot;
var todos = [];
var statusFilter = 'all';
var workspaceFilter = '';
var sortMode = 'due';
var searchQuery = '';
var modalHost = el('div', { className: 'todo-modal-host', hidden: true });
var titleEl = el('span', { className: 'todo-title', textContent: isWorkspace ? 'Todos · ' + workspaceRoot : 'Todos' });
var countEl = el('span', { className: 'todo-count' });
var statusEl = el('span', { className: 'todo-status' });
var statusFilterEl = el('select', { className: 'todo-select', 'data-todo-filter': 'status', onChange: function (event) { statusFilter = event.target.value; render(); } }, [
el('option', { value: 'all', textContent: 'All statuses' }),
el('option', { value: 'open', textContent: 'Open' }),
el('option', { value: 'done', textContent: 'Done' }),
el('option', { value: 'cancelled', textContent: 'Cancelled' })
]);
var workspaceFilterEl = el('select', { className: 'todo-select', 'data-todo-filter': 'workspace', onChange: function (event) { workspaceFilter = event.target.value; render(); } });
var sortEl = el('select', { className: 'todo-select', 'data-todo-filter': 'sort', onChange: function (event) { sortMode = event.target.value; render(); } }, [
el('option', { value: 'due', textContent: 'Sort by due date' }),
el('option', { value: 'reminder', textContent: 'Sort by reminder' }),
el('option', { value: 'updated', textContent: 'Sort by updated' })
]);
var searchEl = el('input', { className: 'todo-input search', type: 'search', placeholder: 'Search todos', 'data-todo-filter': 'search', onInput: function (event) { searchQuery = text(event.target.value).trim().toLowerCase(); render(); } });
var addBtn = el('button', { className: 'todo-btn primary', 'data-todo-action': 'add', textContent: 'Add Todo', onClick: function () { showTodoModal(null); } });
var listEl = el('div', { className: 'todo-list' });
var root = el('div', { className: 'todo-root', 'data-plugin-id': PLUGIN_ID });
var toolbar = el('div', { className: 'todo-toolbar' });
var filters = el('div', { className: 'todo-filters' });
toolbar.appendChild(titleEl);
toolbar.appendChild(countEl);
filters.appendChild(statusFilterEl);
if (!isWorkspace) filters.appendChild(workspaceFilterEl);
filters.appendChild(sortEl);
filters.appendChild(searchEl);
toolbar.appendChild(filters);
toolbar.appendChild(el('span', { className: 'todo-spacer' }));
toolbar.appendChild(statusEl);
toolbar.appendChild(addBtn);
root.appendChild(toolbar);
root.appendChild(listEl);
root.appendChild(modalHost);
containerEl.innerHTML = '';
containerEl.appendChild(root);
function workspaceRoots() {
var found = {};
todos.forEach(function (todo) { if (todo.workspaceRootPath) found[todo.workspaceRootPath] = true; });
if (workspaceRoot) found[workspaceRoot] = true;
return Object.keys(found).sort();
}
function renderWorkspaceOptions() {
if (isWorkspace) return;
workspaceFilterEl.innerHTML = '';
workspaceFilterEl.appendChild(el('option', { value: '', textContent: 'All workspaces' }));
workspaceFilterEl.appendChild(el('option', { value: '__unassigned__', textContent: 'Unassigned' }));
workspaceRoots().forEach(function (rootName) {
workspaceFilterEl.appendChild(el('option', { value: rootName, textContent: rootName }));
});
workspaceFilterEl.value = workspaceFilter;
}
function visibleTodos() {
return sortTodos(todos.filter(function (todo) {
if (isWorkspace && todo.workspaceRootPath !== workspaceRoot) return false;
if (!isWorkspace && workspaceFilter === '__unassigned__' && todo.workspaceRootPath) return false;
if (!isWorkspace && workspaceFilter && workspaceFilter !== '__unassigned__' && todo.workspaceRootPath !== workspaceFilter) return false;
if (statusFilter !== 'all' && todo.status !== statusFilter) return false;
if (!searchQuery) return true;
return [todo.title, todo.description, todo.workspaceRootPath].join('\n').toLowerCase().indexOf(searchQuery) !== -1;
}), sortMode);
}
function persist() { return api.settings.write(GLOBAL_KEY, todos); }
function closeTodoModal() { modalHost.innerHTML = ''; modalHost.hidden = true; }
function showTodoModal(existingTodo) {
var editing = !!existingTodo;
var titleInput = el('input', { className: 'todo-input', type: 'text', value: editing ? existingTodo.title : '', 'data-todo-input': 'title' });
var descriptionInput = el('textarea', { className: 'todo-input textarea', value: editing ? existingTodo.description : '', 'data-todo-input': 'description' });
var priorityInput = el('select', { className: 'todo-select', 'data-todo-input': 'priority' }, [
el('option', { value: 'low', textContent: 'Low' }),
el('option', { value: 'normal', textContent: 'Normal' }),
el('option', { value: 'high', textContent: 'High' })
]);
priorityInput.value = editing ? existingTodo.priority : 'normal';
var dueInput = el('input', { className: 'todo-input', type: 'date', value: editing ? existingTodo.dueAt : '', 'data-todo-input': 'dueAt' });
var reminderInput = el('input', { className: 'todo-input', type: 'datetime-local', value: editing ? existingTodo.reminderAt : '', 'data-todo-input': 'reminderAt' });
var workspaceInput = null;
if (!isWorkspace) {
workspaceInput = el('select', { className: 'todo-select', 'data-todo-input': 'workspaceRootPath' });
workspaceInput.appendChild(el('option', { value: '', textContent: 'Unassigned' }));
workspaceRoots().forEach(function (rootName) { workspaceInput.appendChild(el('option', { value: rootName, textContent: rootName })); });
workspaceInput.value = editing ? existingTodo.workspaceRootPath : '';
}
function saveTodo() {
var title = text(titleInput.value).trim();
if (!title) return;
var rootName = isWorkspace ? workspaceRoot : text(workspaceInput && workspaceInput.value).trim();
var timestamp = now();
var todo = normalizeTodo({
id: editing ? existingTodo.id : todoId(rootName, title),
title: title,
description: descriptionInput.value,
workspaceRootPath: rootName,
workspaceName: rootName,
status: editing ? existingTodo.status : 'open',
priority: priorityInput.value,
dueAt: dueInput.value,
reminderAt: reminderInput.value,
createdAt: editing ? existingTodo.createdAt : timestamp,
updatedAt: timestamp,
completedAt: editing ? existingTodo.completedAt : '',
sourceUrl: editing ? existingTodo.sourceUrl : '',
linkedJournalEntryId: editing ? existingTodo.linkedJournalEntryId : ''
});
todos = editing ? todos.map(function (item) { return item.id === existingTodo.id ? todo : item; }) : [todo].concat(todos);
closeTodoModal();
statusEl.textContent = editing ? 'Todo updated' : 'Todo added';
persist().then(render);
}
var fields = [
el('label', { className: 'todo-field wide' }, ['Title', titleInput]),
el('label', { className: 'todo-field wide' }, ['Description', descriptionInput]),
el('label', { className: 'todo-field' }, ['Priority', priorityInput]),
el('label', { className: 'todo-field' }, ['Due date', dueInput]),
el('label', { className: 'todo-field' }, ['Reminder', reminderInput])
];
if (workspaceInput) fields.push(el('label', { className: 'todo-field' }, ['Workspace', workspaceInput]));
else fields.push(el('div', { className: 'todo-field', textContent: 'Workspace: ' + workspaceRoot }));
modalHost.innerHTML = '';
modalHost.hidden = false;
modalHost.appendChild(el('div', { className: 'todo-modal-overlay' }, [
el('div', { className: 'todo-modal' }, [
el('div', { className: 'todo-modal-title', textContent: editing ? 'Edit Todo' : 'Add Todo' }),
el('div', { className: 'todo-form-grid' }, fields),
el('div', { className: 'todo-modal-actions' }, [
el('button', { className: 'todo-btn', textContent: 'Cancel', onClick: closeTodoModal }),
el('button', { className: 'todo-btn primary', 'data-todo-action': 'save', textContent: editing ? 'Save changes' : 'Add Todo', onClick: saveTodo })
])
])
]));
titleInput.focus();
}
function setTodoStatus(todo, status) {
var timestamp = now();
todos = todos.map(function (item) {
return item.id === todo.id ? Object.assign({}, item, { status: status, completedAt: status === 'done' ? timestamp : '', updatedAt: timestamp }) : item;
});
statusEl.textContent = status === 'done' ? 'Todo marked done' : (status === 'cancelled' ? 'Todo cancelled' : 'Todo reopened');
persist().then(render);
}
function deleteTodo(todo) {
todos = todos.filter(function (item) { return item.id !== todo.id; });
statusEl.textContent = 'Todo deleted';
persist().then(render);
}
function openWorkspace(todo) {
if (!todo.workspaceRootPath) return;
window.dispatchEvent(new CustomEvent('verstak:workspace-selected', { detail: { workspaceName: todo.workspaceRootPath } }));
window.dispatchEvent(new CustomEvent('verstak:workspace-open-tool', { detail: { kind: 'todo' } }));
}
function createJournalEntry(todo) {
if (!isWorkspace || todo.status !== 'done') return;
window.dispatchEvent(new CustomEvent('verstak:workspace-open-tool', {
detail: {
kind: 'journal',
toolRequest: {
type: 'completed-todo',
todo: {
id: todo.id,
title: todo.title,
description: todo.description,
workspaceRootPath: workspaceRoot,
completedAt: todo.completedAt
}
}
}
}));
}
function todoMeta(todo) {
var due = dueState(todo);
var reminderDue = reminderIsDue(todo);
var badges = [
el('span', { className: 'todo-badge', textContent: todo.priority + ' priority' }),
el('span', { className: 'todo-badge', textContent: todo.status })
];
if (!isWorkspace) badges.unshift(el('span', { className: 'todo-badge', textContent: todo.workspaceRootPath || 'Unassigned' }));
if (todo.dueAt) badges.push(el('span', { className: 'todo-badge ' + due, textContent: (due === 'overdue' ? 'Overdue · ' : (due === 'due-soon' ? 'Due soon · ' : '')) + 'Due ' + todo.dueAt }));
if (todo.reminderAt) badges.push(el('span', { className: 'todo-badge ' + (reminderDue ? 'reminder-due' : ''), textContent: (reminderDue ? 'Reminder due ' : 'Reminder ') + todo.reminderAt }));
return el('div', { className: 'todo-row-meta' }, badges);
}
function renderList() {
var visible = visibleTodos();
listEl.innerHTML = '';
if (!visible.length) {
listEl.appendChild(el('div', { className: 'todo-empty', textContent: todos.length ? 'No todos match the current filters.' : 'No todos yet.' }));
return;
}
visible.forEach(function (todo) {
var actions = [];
if (!isWorkspace && todo.workspaceRootPath) actions.push(el('button', { className: 'todo-btn', 'data-todo-action': 'open-workspace', textContent: 'Open workspace', onClick: function () { openWorkspace(todo); } }));
if (todo.status === 'open') {
actions.push(el('button', { className: 'todo-btn', 'data-todo-action': 'mark-done', textContent: 'Done', onClick: function () { setTodoStatus(todo, 'done'); } }));
actions.push(el('button', { className: 'todo-btn', 'data-todo-action': 'cancel', textContent: 'Cancel', onClick: function () { setTodoStatus(todo, 'cancelled'); } }));
} else {
actions.push(el('button', { className: 'todo-btn', 'data-todo-action': 'reopen', textContent: 'Reopen', onClick: function () { setTodoStatus(todo, 'open'); } }));
}
if (isWorkspace && todo.status === 'done') actions.push(el('button', { className: 'todo-btn', 'data-todo-action': 'create-journal-entry', textContent: 'Create Journal Entry', onClick: function () { createJournalEntry(todo); } }));
actions.push(el('button', { className: 'todo-btn', 'data-todo-action': 'edit', textContent: 'Edit', onClick: function () { showTodoModal(todo); } }));
actions.push(el('button', { className: 'todo-btn danger', 'data-todo-action': 'delete', textContent: 'Delete', onClick: function () { deleteTodo(todo); } }));
listEl.appendChild(el('div', { className: 'todo-row' + (todo.status === 'done' ? ' done' : ''), 'data-todo-id': todo.id }, [
el('div', {}, [
el('div', { className: 'todo-row-title', textContent: todo.title || 'Untitled todo' }),
todo.description ? el('div', { className: 'todo-row-description', textContent: todo.description }) : null,
todoMeta(todo)
]),
el('div', { className: 'todo-row-actions' }, actions)
]));
});
}
function render() {
var visible = visibleTodos();
countEl.textContent = visible.length === todos.length ? todos.length + ' todo' + (todos.length === 1 ? '' : 's') : visible.length + ' of ' + todos.length + ' todos';
statusFilterEl.value = statusFilter;
sortEl.value = sortMode;
searchEl.value = searchQuery;
renderWorkspaceOptions();
renderList();
}
api.settings.read().then(function (settings) {
todos = rows((settings || {})[GLOBAL_KEY]).map(normalizeTodo);
render();
}).catch(render);
render();
};
TodoView.unmount = function (containerEl) { containerEl.innerHTML = ''; };
window.VerstakPluginRegister('verstak.todo', { components: { TodoView: TodoView } });
}.toString() + ')();';
}
function searchPluginBundle() {
return '(' + function () {
function el(tag, attrs, children) {
var node = document.createElement(tag);
attrs = attrs || {};
Object.keys(attrs).forEach(function (key) {
if (attrs[key] == null) return;
if (key === 'className') node.className = attrs[key];
else if (key.indexOf('on') === 0) node.addEventListener(key.slice(2).toLowerCase(), attrs[key]);
else if (key === 'textContent') node.textContent = attrs[key];
else node.setAttribute(key, attrs[key]);
});
(children || []).forEach(function (child) {
if (child == null) return;
node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
});
return node;
}
function clean(path) { return String(path || '').split('/').filter(Boolean).join('/'); }
function SearchView(containerEl, props, api) {
if (!document.getElementById('mock-search-style')) {
var style = document.createElement('style');
style.id = 'mock-search-style';
style.textContent = '.search-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#e0e0e0}.search-toolbar{display:flex;gap:.5rem;padding:.55rem .75rem;border-bottom:1px solid #16213e;background:#12122a}.search-input{flex:1;min-width:180px;font-size:.86rem;padding:.42rem .55rem;border:1px solid #333;border-radius:4px;background:#0d0d1a;color:#e0e0e0;outline:none}.search-input:focus{border-color:#4ecca3}.search-btn{font-size:.8rem;padding:.42rem .7rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ddd}.search-scope,.search-status{font-size:.78rem;color:#8b8ba8}.search-status{padding:.45rem .75rem;border-bottom:1px solid rgba(22,33,62,.55)}.search-results{flex:1;min-height:0;overflow:auto}.search-empty{padding:2rem;color:#666;text-align:center}.search-result{padding:.7rem .85rem;border-bottom:1px solid rgba(22,33,62,.55)}.search-path{color:#4ecca3}.search-snippet{margin-top:.25rem;color:#cfcfe0;font-size:.8rem}';
document.head.appendChild(style);
}
containerEl.innerHTML = '';
containerEl.className = 'search-root';
containerEl.setAttribute('data-plugin-id', 'verstak.search');
var rootPath = clean(props && (props.workspaceRootPath || props.workspaceName));
var query = '';
var timer = null;
var results = [];
var input = el('input', { className: 'search-input', type: 'search', placeholder: 'Search files, folders, text', 'data-search-input': 'query' });
var button = el('button', { className: 'search-btn', 'data-search-action': 'run', textContent: 'Search' });
var status = el('div', { className: 'search-status', textContent: 'Enter at least 2 characters.' });
var list = el('div', { className: 'search-results' });
containerEl.appendChild(el('div', { className: 'search-toolbar' }, [
input,
button,
el('span', { className: 'search-scope', title: rootPath || 'Vault' }, [rootPath || 'Vault'])
]));
containerEl.appendChild(status);
containerEl.appendChild(list);
function render() {
list.innerHTML = '';
if (!results.length) {
list.appendChild(el('div', { className: 'search-empty' }, [query.length < 2 ? 'Enter at least 2 characters.' : 'No results']));
return;
}
results.forEach(function (item) {
list.appendChild(el('div', { className: 'search-result' }, [
el('div', { className: 'search-path', textContent: item.relativePath }),
el('div', { className: 'search-snippet', textContent: item.name })
]));
});
}
async function run() {
query = input.value.trim();
if (query.length < 2) {
results = [];
status.textContent = 'Enter at least 2 characters.';
render();
return;
}
var entries = await api.files.list(rootPath);
var needle = query.toLowerCase();
results = (Array.isArray(entries) ? entries : []).filter(function (item) {
return String(item.name || item.relativePath || '').toLowerCase().indexOf(needle) !== -1;
});
status.textContent = results.length + ' result' + (results.length === 1 ? '' : 's');
render();
}
function schedule() {
if (timer) clearTimeout(timer);
timer = setTimeout(run, 100);
}
input.addEventListener('input', schedule);
button.addEventListener('click', run);
render();
containerEl.__searchMockCleanup = function () { if (timer) clearTimeout(timer); };
}
window.VerstakPluginRegister('verstak.search', {
components: {
SearchView: {
mount: SearchView,
unmount: function (containerEl) {
if (containerEl.__searchMockCleanup) containerEl.__searchMockCleanup();
containerEl.innerHTML = '';
}
}
}
});
}.toString() + ')();';
}
function platformTestBundle() {
return [
"(function(){",
"var DiagnosticsPanel={",
"mount:function(containerEl,props,api){",
"containerEl.innerHTML='';",
"containerEl.__ptCleanup=[];",
"function track(fn){if(typeof fn==='function')containerEl.__ptCleanup.push(fn);}",
"var root=document.createElement('div');",
"root.className='pt-root';",
"var title=document.createElement('h2');",
"title.className='pt-plugin-name';",
"title.textContent='Platform Diagnostics';",
"var pluginId=document.createElement('p');",
"pluginId.className='pt-plugin-id';",
"pluginId.textContent=api.pluginId;",
"var status=document.createElement('div');",
"status.className='pt-badge pt-badge-success';",
"status.textContent='Frontend Bundle Loaded';",
"var saved=document.createElement('div');",
"saved.className='pt-card pt-saved-setting';",
"saved.textContent='Saved setting: loading...';",
"var cap=document.createElement('div');",
"cap.className='pt-capability-result';",
"cap.textContent='Capabilities: loading...';",
"api.capabilities.list().then(function(caps){cap.textContent='Capabilities: '+caps.length+' available';});",
"api.settings.read('savedText').then(function(value){saved.textContent='Saved setting: '+(value||'');});",
"var input=document.createElement('input');",
"input.className='pt-setting-input';",
"input.setAttribute('aria-label','Saved setting');",
"input.value='changed value';",
"var button=document.createElement('button');",
"button.className='btn btn-primary pt-save-setting';",
"button.textContent='Save Setting';",
"button.addEventListener('click',function(){api.settings.write('savedText',input.value).then(function(){saved.textContent='Saved setting: '+input.value;});});",
"api.capabilities.has('verstak/platform-test/v1').then(function(ok){status.textContent='Frontend Bundle Loaded | capability '+(ok?'available':'missing');});",
"var command=document.createElement('div');",
"command.className='pt-command-result';",
"command.textContent='Command: registering...';",
"api.commands.register('verstak.platform-test.show-version',function(){return {version:'0.1.0',source:'bundled-frontend'};}).then(function(unregister){track(unregister);return api.commands.execute('verstak.platform-test.show-version',{});}).then(function(result){status.setAttribute('data-command-status',result.status||'');command.textContent='Command: '+result.status+' '+result.result.version+' from '+result.result.source;});",
"var eventResult=document.createElement('div');",
"eventResult.className='pt-event-result';",
"eventResult.textContent='Event: subscribing...';",
"api.events.subscribe('verstak.platform-test.echo',function(event){eventResult.textContent='Event: received '+event.payload.message;eventResult.setAttribute('data-event-status','received');}).then(function(unsubscribe){track(unsubscribe);return api.events.publish('verstak.platform-test.echo',{message:'hello-event'});});",
"var filesResult=document.createElement('div');",
"filesResult.className='pt-files-result';",
"filesResult.textContent='Files: running...';",
"var filesError=document.createElement('div');",
"filesError.className='pt-files-error-result';",
"filesError.textContent='Files error path: checking...';",
"var workbenchResult=document.createElement('div');",
"workbenchResult.className='pt-workbench-result';",
"workbenchResult.textContent='Workbench: ready';",
"function makeWorkbenchButton(cls,label,request){var b=document.createElement('button');b.className='btn btn-primary '+cls;b.textContent=label;b.addEventListener('click',function(){workbenchResult.textContent='Workbench: opening...';api.workbench.editResource(request).then(function(result){workbenchResult.textContent='Workbench: opened '+result.request.path+' with '+(result.providerId||'no-provider');workbenchResult.setAttribute('data-workbench-status',result.status==='opened'?'ok':result.status);}).catch(function(err){workbenchResult.textContent='Workbench error: '+(err&&err.message?err.message:String(err));workbenchResult.setAttribute('data-workbench-status','error');});});return b;}",
"var textWorkbenchButton=makeWorkbenchButton('pt-open-workbench-text','Open Text Diagnostic',{kind:'vault-file',path:'Docs/todo.txt',extension:'.txt',mime:'text/plain',context:{sourceView:'files'}});",
"var markdownWorkbenchButton=makeWorkbenchButton('pt-open-workbench-markdown','Open Markdown Diagnostic',{kind:'vault-file',path:'Docs/readme.md',extension:'.md',context:{sourceView:'files'}});",
"var notesWorkbenchButton=makeWorkbenchButton('pt-open-workbench-notes','Open Notes Diagnostic',{kind:'vault-file',path:'Notes/Overview.md',extension:'.md',context:{sourceView:'notes',isInsideNotesFolder:true,notesMode:true}});",
"api.files.createFolder('PlatformTest').catch(function(e){if(String(e).indexOf('conflict')===-1)throw e;}).then(function(){return api.files.writeText('PlatformTest/files-api.txt','hello files',{createIfMissing:true,overwrite:true});}).then(function(){return api.files.readText('PlatformTest/files-api.txt');}).then(function(text){if(text!=='hello files')throw new Error('read mismatch');return api.files.list('PlatformTest');}).then(function(entries){if(!entries.some(function(e){return e.relativePath==='PlatformTest/files-api.txt';}))throw new Error('list missing file');return api.files.move('PlatformTest/files-api.txt','PlatformTest/files-api-moved.txt',{overwrite:true});}).then(function(){return api.files.trash('PlatformTest/files-api-moved.txt');}).then(function(){filesResult.textContent='Files: wrote/read/listed/moved/trashed';filesResult.setAttribute('data-files-status','ok');}).catch(function(err){filesResult.textContent='Files error: '+(err&&err.message?err.message:String(err));filesResult.setAttribute('data-files-status','error');});",
"api.files.readText('.verstak/vault.json').then(function(){filesError.textContent='Files error path: unexpectedly allowed';filesError.setAttribute('data-files-error-status','error');}).catch(function(err){var message=err&&err.message?err.message:String(err);if(message.indexOf('reserved-path')===-1&&message.indexOf('.verstak')===-1){filesError.textContent='Files error path: wrong error '+message;filesError.setAttribute('data-files-error-status','error');return;}filesError.textContent='Files error path: rejected reserved-path';filesError.setAttribute('data-files-error-status','expected');});",
"root.appendChild(title);",
"root.appendChild(pluginId);",
"root.appendChild(status);",
"root.appendChild(saved);",
"root.appendChild(input);",
"root.appendChild(button);",
"root.appendChild(cap);",
"root.appendChild(command);",
"root.appendChild(eventResult);",
"root.appendChild(filesResult);",
"root.appendChild(filesError);",
"root.appendChild(textWorkbenchButton);",
"root.appendChild(markdownWorkbenchButton);",
"root.appendChild(notesWorkbenchButton);",
"root.appendChild(workbenchResult);",
"containerEl.appendChild(root);",
"},",
"unmount:function(containerEl){while(containerEl.__ptCleanup&&containerEl.__ptCleanup.length){containerEl.__ptCleanup.pop()();}containerEl.innerHTML='';}",
"};",
"var MarkdownDiagnosticProvider={",
"mount:function(containerEl,props,api){",
"containerEl.innerHTML='';",
"var root=document.createElement('div');",
"root.className='pt-root pt-workbench-result';",
"root.setAttribute('data-workbench-status','ok');",
"var req=(props&&props.request)||{};",
"var ctx=(req.context&&req.context.notesMode)||false?'notes-markdown':((req.extension==='.md'||req.extension==='.markdown')?'generic-markdown':'generic-text');",
"root.setAttribute('data-resource-path',req.path||'');",
"root.setAttribute('data-resource-mode',req.mode||'');",
"root.setAttribute('data-resource-context',ctx);",
"root.textContent='Workbench: opened '+(req.path||'')+' with '+((props&&props.providerId)||'')+' mode='+(req.mode||'')+' context='+ctx;",
"containerEl.appendChild(root);",
"},",
"unmount:function(containerEl){containerEl.innerHTML='';}",
"};",
"var PlatformTestSettings={",
"mount:function(containerEl,props,api){",
"containerEl.innerHTML='<div class=\"pt-root\"><h2>Platform Test Settings</h2><p>'+api.pluginId+'</p></div>';",
"},",
"unmount:function(containerEl){containerEl.innerHTML='';}",
"};",
"window.VerstakPluginRegister('verstak.platform-test',{components:{DiagnosticsPanel:DiagnosticsPanel,PlatformTestSettings:PlatformTestSettings,MarkdownDiagnosticProvider:MarkdownDiagnosticProvider}});",
"})();"
].join('');
}
// ── Mock API ───────────────────────────────────────────────────────
var mock = {
GetPlugins: function () { return Promise.resolve(allPlugins()); },
GetCapabilities: function () { return Promise.resolve(allCapabilities()); },
GetPermissions: function () { return Promise.resolve(allPermissions()); },
GetContributions: function () { return Promise.resolve(allContributions()); },
GetVaultStatus: function () { return Promise.resolve(vaultStatus); },
GetVaultPluginState: function () { return Promise.resolve(vaultPluginState); },
GetAppSettings: function () { return Promise.resolve(appSettings); },
GetPluginFrontendInfo: function (pluginId) {
var s = pluginStates[pluginId];
if (s && s.manifest && s.manifest.frontend) {
return Promise.resolve({ entry: s.manifest.frontend.entry });
}
return Promise.resolve({});
},
GetPluginLocalization: function (pluginId, locale) {
return Promise.resolve([mockPluginCatalog(pluginId, locale), '']);
},
ReadPluginSettings: function (pluginId) {
return Promise.resolve([Object.assign({}, pluginSettings[pluginId] || {}), '']);
},
WritePluginSettings: function (pluginId, settings) {
pluginSettings[pluginId] = Object.assign({}, settings || {});
return Promise.resolve('');
},
ReadPluginSetting: function () { return Promise.resolve(null); },
WritePluginSetting: function () { return Promise.resolve(null); },
ReadPluginDataJSON: function (pluginId, name) {
var data = (pluginData[pluginId] && pluginData[pluginId][name]) || {};
return Promise.resolve([Object.assign({}, data), '']);
},
ReadPluginDataNDJSON: function (pluginId, name) {
var data = (pluginData[pluginId] && pluginData[pluginId][name]) || [];
if (!Array.isArray(data) && pluginId === 'verstak.activity' && name === 'activity-events') data = [];
if (!data.length && pluginId === 'verstak.activity' && name === 'activity-events') {
data = Object.keys(pluginSettings[pluginId] || {}).filter(function (key) {
return key === 'events' || key === 'events:global' || key.indexOf('events:workspace:') === 0;
}).flatMap(function (key) {
return Array.isArray(pluginSettings[pluginId][key]) ? pluginSettings[pluginId][key] : [];
});
}
return Promise.resolve([Array.isArray(data) ? data.slice() : [], '']);
},
WritePluginDataJSON: function (pluginId, name, data) {
pluginData[pluginId] = pluginData[pluginId] || {};
pluginData[pluginId][name] = Object.assign({}, data || {});
return Promise.resolve('');
},
WritePluginDataNDJSON: function (pluginId, name, records) {
pluginData[pluginId] = pluginData[pluginId] || {};
pluginData[pluginId][name] = Array.isArray(records) ? records.slice() : [];
return Promise.resolve('');
},
OpenWorkbenchResource: function (pluginId, request) {
return openWorkbenchResource(pluginId, request || {}, '');
},
EditWorkbenchResource: function (pluginId, request) {
return openWorkbenchResource(pluginId, request || {}, 'edit');
},
GetWorkbenchOpenedResources: function () {
return Promise.resolve(openedResources.map(function (resource) {
return Object.assign({}, resource, { request: Object.assign({}, resource.request || {}) });
}));
},
GetWorkbenchPreferences: function () {
return Promise.resolve(Object.assign({}, workbenchPreferences));
},
UpdateWorkbenchPreferences: function (preferences) {
workbenchPreferences = Object.assign({}, workbenchPreferences, preferences || {});
return Promise.resolve('');
},
PluginSyncStatus: function (pluginId) {
var err = requirePluginSyncPermission(pluginId, false);
if (err) return Promise.resolve([{}, err]);
return Promise.resolve([syncStatusDTO(), '']);
},
PluginSyncConfigure: function (pluginId, serverUrl) {
var err = requirePluginSyncPermission(pluginId, true);
if (err) return Promise.resolve(err);
syncState.configured = true;
syncState.serverUrl = serverUrl || '';
syncState.deviceId = 'mock-device';
syncState.deviceName = 'mock-device';
syncState.connected = true;
syncState.revoked = false;
syncState.tokenStored = true;
syncState.lastError = '';
syncState.statusLabel = 'connected';
pluginSettings[pluginId] = Object.assign({}, pluginSettings[pluginId] || {}, {
serverUrl: syncState.serverUrl,
syncStatus: syncState.statusLabel
});
return Promise.resolve('');
},
PluginSyncDisconnect: function (pluginId) {
var err = requirePluginSyncPermission(pluginId, false);
if (err) return Promise.resolve(err);
syncState = makeDefaultSyncState();
pluginSettings[pluginId] = Object.assign({}, pluginSettings[pluginId] || {}, {
serverUrl: '',
syncStatus: syncState.statusLabel
});
return Promise.resolve('');
},
PluginSyncTestConnection: function (pluginId, serverUrl) {
var err = requirePluginSyncPermission(pluginId, true);
if (err) return Promise.resolve(err);
if (!serverUrl) return Promise.resolve('server URL is required');
return Promise.resolve('');
},
PluginSyncSetInterval: function (pluginId, minutes) {
var err = requirePluginSyncPermission(pluginId, false);
if (err) return Promise.resolve(err);
syncState.syncInterval = Number(minutes) || 0;
return Promise.resolve('');
},
PluginSyncResetKey: function (pluginId) {
var err = requirePluginSyncPermission(pluginId, false);
if (err) return Promise.resolve(err);
syncState.configured = false;
syncState.deviceId = '';
syncState.deviceName = '';
syncState.connected = false;
syncState.revoked = false;
syncState.tokenStored = false;
syncState.lastError = '';
syncState.statusLabel = 'disconnected';
pluginSettings[pluginId] = Object.assign({}, pluginSettings[pluginId] || {}, {
syncStatus: syncState.statusLabel
});
return Promise.resolve('');
},
PluginSyncNow: function (pluginId) {
var err = requirePluginSyncPermission(pluginId, true);
if (err) return Promise.resolve([{}, err]);
if (!syncState.configured) return Promise.resolve([{}, 'sync not configured']);
syncState.lastSyncAt = new Date().toISOString();
syncState.lastError = '';
syncState.statusLabel = 'connected';
return Promise.resolve([{ pushed: 0, pulled: 0, serverSequence: syncState.serverSequence }, '']);
},
GetPluginAssetContent: function (pluginId, assetPath) {
if (pluginId === 'verstak.platform-test' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(platformTestBundle());
}
if (pluginId === 'verstak.default-editor' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(defaultEditorBundle());
}
if (pluginId === 'verstak.files' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(filesPluginBundle());
}
if (pluginId === 'verstak.trash' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(trashPluginBundle());
}
if (pluginId === 'verstak.notes' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(simplePluginBundle('verstak.notes', 'NotesView', 'notes-root', 'Notes'));
}
if (pluginId === 'verstak.activity' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(activityBundle());
}
if (pluginId === 'verstak.journal' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(journalBundle());
}
if (pluginId === 'verstak.browser-inbox' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(browserInboxBundle());
}
if (pluginId === 'verstak.todo' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(todoBundle());
}
if (pluginId === 'verstak.secrets' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(simplePluginBundle('verstak.secrets', 'SecretsView', 'secrets-root', 'Secrets'));
}
if (pluginId === 'verstak.search' && assetPath === 'frontend/dist/index.js') {
return Promise.resolve(searchPluginBundle());
}
return Promise.resolve('');
},
GetPluginCapability: function (pluginId, capId) {
var caps = allCapabilities();
var found = caps.find(function (cap) { return cap.name === capId; });
return Promise.resolve([found ? Object.assign({ available: true }, found) : { available: false, name: capId }, '']);
},
ListPluginCapabilities: function () { return Promise.resolve([allCapabilities(), '']); },
ExecutePluginCommand: function (pluginId, commandId, args) {
var s = pluginStates[pluginId];
var commands = ((s && s.manifest && s.manifest.contributes && s.manifest.contributes.commands) || []);
var found = commands.find(function (cmd) { return cmd.id === commandId; });
if (!found) return Promise.resolve([{}, 'command not declared']);
return Promise.resolve([{ status: 'declared', pluginId: pluginId, commandId: commandId, handler: found.handler, args: args || {} }, '']);
},
PublishPluginEvent: function () { return Promise.resolve(''); },
SubscribePluginEvent: function (pluginId, eventName) {
var s = pluginStates[pluginId];
if (!s || !s.enabled || s.status !== 'loaded') return Promise.resolve('plugin not enabled and loaded');
if (!eventName) return Promise.resolve('event name is empty');
if (!s.manifest.permissions || s.manifest.permissions.indexOf('events.subscribe') === -1) {
return Promise.resolve('plugin lacks required permission events.subscribe');
}
return Promise.resolve('');
},
ListVaultFiles: function (pluginId, relativeDir) {
var err = requirePluginPermission(pluginId, 'files.read');
if (err) return Promise.resolve([[], err]);
var norm = normalizeVaultPath(relativeDir, true);
if (norm.error) return Promise.resolve([[], norm.error]);
var dir = norm.path;
if (!vaultFiles[dir] || vaultFiles[dir].type !== 'folder') return Promise.resolve([[], 'not-found: ' + dir]);
var prefix = dir ? dir + '/' : '';
var entries = [];
Object.keys(vaultFiles).forEach(function (path) {
if (path === dir || path.indexOf(prefix) !== 0) return;
var rest = path.slice(prefix.length);
if (!rest || rest.indexOf('/') !== -1) return;
entries.push(fileEntry(path, vaultFiles[path]));
});
return Promise.resolve([entries, '']);
},
GetVaultFileMetadata: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.read');
if (err) return Promise.resolve([{}, err]);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve([{}, norm.error]);
var node = vaultFiles[norm.path];
if (!node) return Promise.resolve([{}, 'not-found: ' + norm.path]);
return Promise.resolve([fileEntry(norm.path, node), '']);
},
ReadVaultTextFile: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.read');
if (err) return Promise.resolve(['', err]);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve(['', norm.error]);
var node = vaultFiles[norm.path];
if (!node) return Promise.resolve(['', 'not-found: ' + norm.path]);
if (node.type !== 'file') return Promise.resolve(['', 'not-regular-file: ' + norm.path]);
return Promise.resolve([node.content || '', '']);
},
ReadVaultFileBytes: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.read');
if (err) return Promise.resolve([{}, err]);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve([{}, norm.error]);
var node = vaultFiles[norm.path];
if (!node) return Promise.resolve([{}, 'not-found: ' + norm.path]);
if (node.type !== 'file') return Promise.resolve([{}, 'not-regular-file: ' + norm.path]);
var content = node.content || '';
var dataBase64 = typeof btoa === 'function' ? btoa(content) : '';
return Promise.resolve([{
relativePath: norm.path,
size: content.length,
mimeHint: norm.path.toLowerCase().endsWith('.png') ? 'image/png' : '',
dataBase64: dataBase64
}, '']);
},
WriteVaultTextFile: function (pluginId, relativePath, content, options) {
var err = requirePluginPermission(pluginId, 'files.write');
if (err) return Promise.resolve(err);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve(norm.error);
options = options || {};
var existing = vaultFiles[norm.path];
if (existing && existing.type !== 'file') return Promise.resolve('not-regular-file: ' + norm.path);
if (existing && !options.overwrite) return Promise.resolve('conflict: ' + norm.path);
if (!existing && !options.createIfMissing) return Promise.resolve('not-found: ' + norm.path);
var parent = parentPath(norm.path);
if (!vaultFiles[parent] || vaultFiles[parent].type !== 'folder') return Promise.resolve('parent-not-found: ' + parent);
vaultFiles[norm.path] = { type: 'file', content: String(content == null ? '' : content), modifiedAt: new Date().toISOString() };
return Promise.resolve('');
},
WriteVaultFileBytes: function (pluginId, relativePath, dataBase64, options) {
var err = requirePluginPermission(pluginId, 'files.write');
if (err) return Promise.resolve(err);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve(norm.error);
options = options || {};
var existing = vaultFiles[norm.path];
if (existing && existing.type !== 'file') return Promise.resolve('not-regular-file: ' + norm.path);
if (existing && !options.overwrite) return Promise.resolve('conflict: ' + norm.path);
if (!existing && !options.createIfMissing) return Promise.resolve('not-found: ' + norm.path);
var parent = parentPath(norm.path);
if (!vaultFiles[parent] || vaultFiles[parent].type !== 'folder') return Promise.resolve('parent-not-found: ' + parent);
var content = typeof atob === 'function' ? atob(String(dataBase64 || '')) : '';
vaultFiles[norm.path] = { type: 'file', content: content, modifiedAt: new Date().toISOString() };
return Promise.resolve('');
},
CreateVaultFolder: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.write');
if (err) return Promise.resolve(err);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve(norm.error);
if (vaultFiles[norm.path]) return Promise.resolve('conflict: ' + norm.path);
var parent = parentPath(norm.path);
if (!vaultFiles[parent] || vaultFiles[parent].type !== 'folder') return Promise.resolve('parent-not-found: ' + parent);
vaultFiles[norm.path] = { type: 'folder', modifiedAt: new Date().toISOString() };
return Promise.resolve('');
},
MoveVaultPath: function (pluginId, fromRelativePath, toRelativePath, options) {
var err = requirePluginPermission(pluginId, 'files.write');
if (err) return Promise.resolve(err);
var from = normalizeVaultPath(fromRelativePath, false);
var to = normalizeVaultPath(toRelativePath, false);
if (from.error) return Promise.resolve(from.error);
if (to.error) return Promise.resolve(to.error);
options = options || {};
if (!vaultFiles[from.path]) return Promise.resolve('not-found: ' + from.path);
if (vaultFiles[from.path].type === 'folder' && (to.path === from.path || to.path.indexOf(from.path + '/') === 0)) {
return Promise.resolve('move-into-self: ' + from.path + ' -> ' + to.path);
}
if (vaultFiles[to.path] && !options.overwrite) return Promise.resolve('conflict: ' + to.path);
var parent = parentPath(to.path);
if (!vaultFiles[parent] || vaultFiles[parent].type !== 'folder') return Promise.resolve('parent-not-found: ' + parent);
var moving = Object.keys(vaultFiles).filter(function (path) { return path === from.path || path.indexOf(from.path + '/') === 0; });
moving.forEach(function (path) {
var suffix = path.slice(from.path.length);
vaultFiles[to.path + suffix] = vaultFiles[path];
delete vaultFiles[path];
});
return Promise.resolve('');
},
TrashVaultPath: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.delete');
if (err) return Promise.resolve([{}, err]);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve([{}, norm.error]);
if (!vaultFiles[norm.path]) return Promise.resolve([{}, 'not-found: ' + norm.path]);
var trashId = 'mock-' + Date.now() + '-' + Math.random().toString(16).slice(2);
var trashPath = '.verstak/trash/files/' + trashId + '/' + baseName(norm.path);
var originalNode = vaultFiles[norm.path];
var originalType = originalNode.type || 'file';
var originalSize = originalType === 'file' ? String(originalNode.content || '').length : 0;
var moving = Object.keys(vaultFiles).filter(function (path) { return path === norm.path || path.indexOf(norm.path + '/') === 0; });
trashPayloads[trashId] = moving.map(function (path) {
return { suffix: path.slice(norm.path.length), entry: Object.assign({}, vaultFiles[path]) };
});
moving.forEach(function (path) { delete vaultFiles[path]; });
var entry = { originalPath: norm.path, trashPath: trashPath, trashId: trashId, deletedAt: new Date().toISOString(), originalType: originalType, basename: baseName(norm.path), size: originalSize };
trashEntries.unshift(entry);
return Promise.resolve([entry, '']);
},
ListVaultTrash: function (pluginId) {
var err = requirePluginPermission(pluginId, 'files.delete');
if (err) return Promise.resolve([[], err]);
return Promise.resolve([trashEntries.slice(), '']);
},
RestoreVaultTrash: function (pluginId, trashId, options) {
var deleteErr = requirePluginPermission(pluginId, 'files.delete');
if (deleteErr) return Promise.resolve(['', deleteErr]);
var writeErr = requirePluginPermission(pluginId, 'files.write');
if (writeErr) return Promise.resolve(['', writeErr]);
options = options || {};
var entry = trashEntries.find(function (item) { return item.trashId === trashId; });
if (!entry) return Promise.resolve(['', 'not-found: trash entry ' + trashId]);
var target = normalizeVaultPath(options.targetPath || entry.originalPath, false);
if (target.error) return Promise.resolve(['', target.error]);
if (vaultFiles[target.path] && !options.overwrite) return Promise.resolve(['', 'conflict: ' + target.path]);
var parent = parentPath(target.path);
if (!vaultFiles[parent] || vaultFiles[parent].type !== 'folder') return Promise.resolve(['', 'parent-not-found: ' + parent]);
if (options.overwrite) {
Object.keys(vaultFiles).filter(function (path) { return path === target.path || path.indexOf(target.path + '/') === 0; }).forEach(function (path) { delete vaultFiles[path]; });
}
(trashPayloads[trashId] || []).forEach(function (item) {
vaultFiles[target.path + item.suffix] = Object.assign({}, item.entry, { modifiedAt: new Date().toISOString() });
});
delete trashPayloads[trashId];
trashEntries = trashEntries.filter(function (item) { return item.trashId !== trashId; });
return Promise.resolve([target.path, '']);
},
DeleteVaultTrash: function (pluginId, trashId) {
var err = requirePluginPermission(pluginId, 'files.delete');
if (err) return Promise.resolve(err);
var entry = trashEntries.find(function (item) { return item.trashId === trashId; });
if (!entry) return Promise.resolve('not-found: trash entry ' + trashId);
delete trashPayloads[trashId];
trashEntries = trashEntries.filter(function (item) { return item.trashId !== trashId; });
return Promise.resolve('');
},
OpenVaultPathExternal: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.openExternal');
if (err) return Promise.resolve(err);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve(norm.error);
if (!vaultFiles[norm.path]) return Promise.resolve('not-found: ' + norm.path);
externalOpens.push({ action: 'open', path: norm.path });
window.__wailsMockExternalOpens = externalOpens.slice();
return Promise.resolve('');
},
OpenExternalURL: function (pluginId, rawURL) {
var err = requirePluginPermission(pluginId, 'files.openExternal');
if (err) return Promise.resolve(err);
externalOpens.push({ action: 'url', path: String(rawURL || '') });
window.__wailsMockExternalOpens = externalOpens.slice();
return Promise.resolve('');
},
ShowVaultPathInFolder: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.openExternal');
if (err) return Promise.resolve(err);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve(norm.error);
if (!vaultFiles[norm.path]) return Promise.resolve('not-found: ' + norm.path);
externalOpens.push({ action: 'show', path: norm.path });
window.__wailsMockExternalOpens = externalOpens.slice();
return Promise.resolve('');
},
ListWorkspaces: function () {
return Promise.resolve(listWorkspacesFromTree());
},
ListWorkspaceTemplates: function () {
return Promise.resolve(builtInWorkspaceTemplates().map(function (template) {
return {
id: template.id,
name: template.name,
description: template.description,
version: template.version,
workspaceTools: template.workspaceTools.slice()
};
}));
},
CreateWorkspace: function (name, templateID) {
var norm = normalizeVaultPath(name, false);
if (norm.error || norm.path !== String(name || '').trim() || norm.path.indexOf('/') !== -1) {
return Promise.resolve(norm.error || 'invalid-workspace-name');
}
if (vaultFiles[norm.path]) return Promise.resolve('conflict: ' + norm.path);
var template = workspaceTemplateByID(templateID);
if (!template) return Promise.resolve('template-not-found: ' + String(templateID || ''));
vaultFiles[norm.path] = { type: 'folder', modifiedAt: new Date().toISOString() };
template.folders.forEach(function (folder) {
vaultFiles[norm.path + '/' + folder] = { type: 'folder', modifiedAt: new Date().toISOString() };
});
workspaceMetadata[norm.path] = metadataForTemplate(norm.path, template);
workspaceTree.nodes.push(makeWorkspaceNode(norm.path, workspaceTree.nodes.length + 1));
return Promise.resolve({ name: norm.path, rootPath: norm.path });
},
RenameWorkspace: function (oldName, newName) {
var oldNorm = normalizeVaultPath(oldName, false);
var newNorm = normalizeVaultPath(newName, false);
if (oldNorm.error) return Promise.resolve(oldNorm.error);
if (newNorm.error || newNorm.path.indexOf('/') !== -1) return Promise.resolve(newNorm.error || 'invalid-workspace-name');
if (!vaultFiles[oldNorm.path]) return Promise.resolve('not-found: ' + oldNorm.path);
if (vaultFiles[newNorm.path]) return Promise.resolve('conflict: ' + newNorm.path);
Object.keys(vaultFiles).filter(function (path) {
return path === oldNorm.path || path.indexOf(oldNorm.path + '/') === 0;
}).forEach(function (path) {
var suffix = path.slice(oldNorm.path.length);
vaultFiles[newNorm.path + suffix] = vaultFiles[path];
delete vaultFiles[path];
});
workspaceTree.nodes = workspaceTree.nodes.map(function (n) {
if (n.id !== oldNorm.path) return n;
return makeWorkspaceNode(newNorm.path, n.order);
});
if (workspaceMetadata[oldNorm.path]) {
workspaceMetadata[newNorm.path] = Object.assign({}, workspaceMetadata[oldNorm.path], { workspaceName: newNorm.path });
delete workspaceMetadata[oldNorm.path];
}
if (workspaceTree.currentNodeId === oldNorm.path) workspaceTree.currentNodeId = newNorm.path;
return Promise.resolve('');
},
TrashWorkspace: function (name) {
var norm = normalizeVaultPath(name, false);
if (norm.error) return Promise.resolve(norm.error);
if (!vaultFiles[norm.path]) return Promise.resolve('not-found: ' + norm.path);
Object.keys(vaultFiles).filter(function (path) {
return path === norm.path || path.indexOf(norm.path + '/') === 0;
}).forEach(function (path) { delete vaultFiles[path]; });
workspaceTree.nodes = workspaceTree.nodes.filter(function (n) { return n.id !== norm.path; });
delete workspaceMetadata[norm.path];
if (workspaceTree.currentNodeId === norm.path) workspaceTree.currentNodeId = workspaceTree.nodes[0] ? workspaceTree.nodes[0].id : '';
return Promise.resolve({ originalPath: norm.path, trashPath: '.verstak/trash/workspaces/mock/' + norm.path, trashId: 'mock', deletedAt: new Date().toISOString() });
},
GetWorkspaceMetadata: function (name) {
var norm = normalizeVaultPath(name, false);
if (norm.error) return Promise.resolve(norm.error);
if (!vaultFiles[norm.path]) return Promise.resolve('not-found: ' + norm.path);
return Promise.resolve(cloneJson(workspaceMetadata[norm.path] || genericWorkspaceMetadata(norm.path)));
},
UpdateWorkspaceMetadata: function (name, patch) {
var norm = normalizeVaultPath(name, false);
if (norm.error) return Promise.resolve(norm.error);
if (!vaultFiles[norm.path]) return Promise.resolve('not-found: ' + norm.path);
var next = Object.assign({}, workspaceMetadata[norm.path] || genericWorkspaceMetadata(norm.path), patch || {}, { workspaceName: norm.path, updatedAt: new Date().toISOString() });
workspaceMetadata[norm.path] = next;
return Promise.resolve(cloneJson(next));
},
GetCurrentWorkspace: function () {
var found = workspaceTree.nodes.find(function (n) { return n.id === workspaceTree.currentNodeId; });
return Promise.resolve(found ? { name: found.name || found.id, rootPath: found.rootPath || found.name || found.id } : null);
},
GetCurrentWorkspaceNode: function () {
var found = workspaceTree.nodes.find(function (n) { return n.id === workspaceTree.currentNodeId; });
return Promise.resolve(found ? Object.assign({}, found) : null);
},
GetWorkspaceTree: function () { return Promise.resolve(cloneWorkspaceTree()); },
ArchiveWorkspaceNode: function (id) { return this.TrashWorkspace(id).then(function (response) { return typeof response === 'string' ? response : ''; }); },
CreateWorkspaceNode: function (parentId, nodeType, title) {
return this.CreateWorkspace(title, 'default').then(function (response) {
if (typeof response === 'string') return { error: response };
var ws = response;
return makeWorkspaceNode(ws.name, workspaceTree.nodes.length);
});
},
MoveWorkspaceNode: function () { return Promise.resolve(''); },
RenameWorkspaceNode: function (id, title) { return this.RenameWorkspace(id, title); },
SetCurrentWorkspace: function (id) {
var found = workspaceTree.nodes.some(function (n) { return n.id === id; });
if (!found) return Promise.resolve('workspace not found: ' + id);
workspaceTree.currentNodeId = id;
return Promise.resolve('');
},
SetCurrentWorkspaceNode: function (id) { return this.SetCurrentWorkspace(id); },
SelectDirectory: function () { return Promise.resolve(''); },
SelectVaultForOpen: function () { return Promise.resolve(''); },
CreateVault: function () { return Promise.resolve(null); },
OpenVault: function () { return Promise.resolve(null); },
CloseVault: function () { return Promise.resolve(null); },
SetCurrentVault: function () { return Promise.resolve(''); },
UpdateAppSettings: function (patch) {
appSettings = Object.assign({}, appSettings, patch || {});
if (patch && patch.language) localStorage.setItem('verstak-test-language', patch.language);
return Promise.resolve('');
},
RecordDesiredPlugin: function () { return Promise.resolve(''); },
WriteFrontendLog: function () { return Promise.resolve(); },
EnablePlugin: function (pluginId) {
if (pluginStates[pluginId]) {
pluginStates[pluginId].status = 'loaded';
pluginStates[pluginId].enabled = true;
if (vaultPluginState.disabledPlugins.indexOf(pluginId) !== -1) {
vaultPluginState.disabledPlugins = vaultPluginState.disabledPlugins.filter(function (id) { return id !== pluginId; });
}
if (vaultPluginState.enabledPlugins.indexOf(pluginId) === -1) {
vaultPluginState.enabledPlugins.push(pluginId);
}
}
return Promise.resolve(null);
},
DisablePlugin: function (pluginId) {
if (pluginStates[pluginId]) {
pluginStates[pluginId].status = 'disabled';
pluginStates[pluginId].enabled = false;
if (vaultPluginState.enabledPlugins.indexOf(pluginId) !== -1) {
vaultPluginState.enabledPlugins = vaultPluginState.enabledPlugins.filter(function (id) { return id !== pluginId; });
}
if (vaultPluginState.disabledPlugins.indexOf(pluginId) === -1) {
vaultPluginState.disabledPlugins.push(pluginId);
}
}
return Promise.resolve(null);
},
ReloadPlugins: function () {
if (reloadResponseMode === 'raw-count') {
return Promise.resolve(Object.keys(pluginStates).length);
}
return Promise.resolve([Object.keys(pluginStates).length, 'Reloaded ' + Object.keys(pluginStates).length + ' plugin(s).']);
}
};
// ── Install bridge ─────────────────────────────────────────────────
if (!window['go']) window['go'] = {};
if (!window['go']['api']) window['go']['api'] = {};
window['go']['api']['App'] = mock;
// ── Test helpers (exposed for Playwright) ──────────────────────────
window.__wailsMock = {
reset: function () {
pluginStates = {
'verstak.platform-test': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.platform-test',
name: 'Platform Test',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Runtime test plugin for verifying the Verstak platform.',
source: 'official',
icon: '🧪',
provides: ['verstak/platform-test/v1', 'verstak/diagnostics/v1'],
requires: ['verstak/core/plugin-manager/v1', 'verstak/core/capability-registry/v1'],
optionalRequires: ['verstak/core/vault/v1', 'verstak/core/sync/v1', 'verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['vault.read', 'events.publish', 'events.subscribe', 'ui.register', 'commands.register', 'storage.namespace', 'files.read', 'files.write', 'files.delete', 'files.openExternal', 'workbench.open'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [
{ id: 'verstak.platform-test.diagnostics', title: 'Platform Diagnostics', icon: '🧪', component: 'DiagnosticsPanel' }
],
commands: [
{ id: 'verstak.platform-test.run-tests', title: 'Run Platform Tests', handler: 'runAllTests' },
{ id: 'verstak.platform-test.show-version', title: 'Show Version Info', handler: 'showVersion' }
],
sidebarItems: [
{ id: 'verstak.platform-test.sidebar', title: 'Platform Test', icon: '🧪', view: 'verstak.platform-test.diagnostics', position: 100 }
],
statusBarItems: [
{ id: 'verstak.platform-test.status', label: '🧪 All Tests Pass', position: 'right', handler: 'openDiagnostics' }
],
settingsPanels: [
{ id: 'verstak.platform-test.settings', title: 'Platform Test Settings', icon: '🧪', component: 'PlatformTestSettings' }
],
openProviders: [
{
id: 'verstak.platform-test.markdown-diagnostic',
title: 'Platform Test Markdown Diagnostic',
priority: 10,
component: 'MarkdownDiagnosticProvider',
supports: [
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] }
]
}
]
}
},
rootPath: '/tmp/verstak-test/plugins/platform-test',
error: ''
},
'verstak.default-editor': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.default-editor',
name: 'Default Editor',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Built-in text and markdown editor/viewer.',
source: 'official',
icon: 'edit',
provides: ['verstak/default-editor/v1'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'files.write', 'workbench.open'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
openProviders: [
{
id: 'verstak.default-editor.text',
title: 'Default Text Editor',
priority: 50,
component: 'DefaultEditor',
supports: [
{ kind: 'vault-file', extensions: ['.txt', '.log', '.conf', '.ini', '.toml', '.yaml', '.yml', '.json', '.csv'], mime: ['text/plain', 'application/json'], contexts: ['generic-text'] }
]
},
{
id: 'verstak.default-editor.markdown',
title: 'Default Markdown Editor',
priority: 50,
component: 'DefaultEditor',
supports: [
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown'] }
]
},
{
id: 'verstak.default-editor.notes-markdown',
title: 'Default Notes Markdown Editor',
priority: 50,
component: 'DefaultEditor',
supports: [
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['notes-markdown'] }
]
}
]
}
},
rootPath: '/tmp/verstak-test/plugins/default-editor',
error: ''
},
'verstak.files': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.files',
name: 'Files',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Minimal vault file navigator.',
source: 'official',
icon: 'folder',
provides: ['verstak/files/v1'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'files.write', 'files.delete', 'files.openExternal', 'workbench.open', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.files.view', title: 'Files', icon: 'folder', component: 'FilesView' }],
workspaceItems: [{ id: 'verstak.files.workspace', title: 'Files', icon: 'folder', component: 'FilesView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/files',
error: ''
},
'verstak.trash': makeTrashPluginState(),
'verstak.notes': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.notes',
name: 'Notes',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped notes manager.',
source: 'official',
icon: 'edit',
provides: ['verstak/notes/v1'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'files.write', 'files.delete', 'events.subscribe', 'workbench.open', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
workspaceItems: [{ id: 'verstak.notes.workspace', title: 'Notes', icon: 'edit', component: 'NotesView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/notes',
error: ''
},
'verstak.sync': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.sync',
name: 'Sync',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Synchronize vault data across devices.',
source: 'official',
icon: 'refresh-cw',
provides: ['verstak/sync/v1', 'verstak/sync.status/v1'],
requires: ['verstak/core/files/v1'],
permissions: ['files.read', 'files.write', 'network.remote', 'storage.namespace', 'sync.participate', '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', handler: 'SyncStatusBar' }]
}
},
rootPath: '/tmp/verstak-test/plugins/sync',
error: ''
},
'verstak.activity': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.activity',
name: 'Activity',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped activity log for public plugin events.',
source: 'official',
icon: 'activity',
provides: ['activity.log', 'activity.provider', 'activity.reconstruction'],
permissions: ['events.subscribe', 'storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.activity.view', title: 'Activity', icon: 'activity', component: 'ActivityView' }],
sidebarItems: [{ id: 'verstak.activity.sidebar', title: 'Activity', icon: 'activity', view: 'verstak.activity.view', position: 20 }],
workspaceItems: [{ id: 'verstak.activity.workspace', title: 'Activity', icon: 'activity', component: 'ActivityView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/activity',
error: ''
},
'verstak.journal': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.journal',
name: 'Journal',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped journal with user-authored entries and optional Activity links.',
source: 'official',
icon: 'book-open',
provides: ['worklog', 'journal', 'report.worklog'],
permissions: ['storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.journal.view', title: 'Journal', icon: 'book-open', component: 'JournalView' }],
sidebarItems: [{ id: 'verstak.journal.sidebar', title: 'Journal', icon: 'book-open', view: 'verstak.journal.view', position: 30 }],
workspaceItems: [{ id: 'verstak.journal.workspace', title: 'Journal', icon: 'book-open', component: 'JournalView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/journal',
error: ''
},
'verstak.browser-inbox': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.browser-inbox',
name: 'Browser Inbox',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Global browser capture queue with explicit workspace assignment.',
source: 'official',
icon: 'inbox',
provides: ['browser.inbox'],
permissions: ['events.subscribe', 'files.read', 'storage.namespace', 'ui.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
views: [{ id: 'verstak.browser-inbox.view', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }],
sidebarItems: [{ id: 'verstak.browser-inbox.sidebar', title: 'Browser Inbox', icon: 'inbox', view: 'verstak.browser-inbox.view', position: 30 }],
workspaceItems: [{ id: 'verstak.browser-inbox.workspace', title: 'Browser Inbox', icon: 'inbox', component: 'BrowserInboxView' }]
}
},
rootPath: '/tmp/verstak-test/plugins/browser-inbox',
error: ''
},
'verstak.todo': makeTodoPluginState(),
'verstak.secrets': makeSecretsPluginState(),
'verstak.search': {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: 'verstak.search',
name: 'Search',
version: '0.1.0',
apiVersion: '0.1.0',
description: 'Workspace-scoped vault text search provider.',
source: 'official',
icon: 'search',
provides: ['verstak/search/v1', 'search.provider'],
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
permissions: ['files.read', 'workbench.open', 'storage.namespace', 'ui.register', 'events.subscribe', 'commands.register'],
frontend: { entry: 'frontend/dist/index.js' },
contributes: {
workspaceItems: [{ id: 'verstak.search.workspace', title: 'Search', icon: 'search', component: 'SearchView' }],
commands: [{ id: 'verstak.search.searchVaultText', title: 'Search Vault Text', handler: 'verstak.search.searchVaultText' }],
searchProviders: [{ id: 'verstak.search.vault-text', label: 'Vault Text Search', handler: 'verstak.search.searchVaultText' }]
}
},
rootPath: '/tmp/verstak-test/plugins/search',
error: ''
}
};
vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' };
vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files', 'verstak.notes', 'verstak.sync', 'verstak.activity', 'verstak.journal', 'verstak.browser-inbox', 'verstak.search'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }, { id: 'verstak.notes', version: '0.1.0', source: 'official' }, { id: 'verstak.sync', version: '0.1.0', source: 'official' }, { id: 'verstak.activity', version: '0.1.0', source: 'official' }, { id: 'verstak.journal', version: '0.1.0', source: 'official' }, { id: 'verstak.browser-inbox', version: '0.1.0', source: 'official' }, { id: 'verstak.search', version: '0.1.0', source: 'official' }] };
vaultPluginState.enabledPlugins.push('verstak.trash');
vaultPluginState.desiredPlugins.push({ id: 'verstak.trash', version: '0.1.0', source: 'official' });
vaultPluginState.enabledPlugins.push('verstak.todo');
vaultPluginState.desiredPlugins.push({ id: 'verstak.todo', version: '0.1.0', source: 'official' });
vaultPluginState.enabledPlugins.push('verstak.secrets');
vaultPluginState.desiredPlugins.push({ id: 'verstak.secrets', version: '0.1.0', source: 'official' });
localStorage.removeItem('verstak-test-language');
appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [], language: 'system' };
workbenchPreferences = {};
openedResources = [];
pluginSettings = { 'verstak.platform-test': { savedText: 'initial value' } };
vaultFiles = makeDefaultVaultFiles();
externalOpens = [];
trashEntries = [];
trashPayloads = {};
window.__wailsMockExternalOpens = [];
workspaceTree = makeDefaultWorkspaceTree();
workspaceMetadata = {};
reloadResponseMode = 'tuple';
syncState = makeDefaultSyncState();
},
setPluginStatus: function (pluginId, status, enabled) {
if (pluginStates[pluginId]) {
pluginStates[pluginId].status = status;
pluginStates[pluginId].enabled = enabled;
}
},
getPluginState: function (pluginId) {
return pluginStates[pluginId] ? Object.assign({}, pluginStates[pluginId]) : null;
},
addSyntheticPlugins: function (count) {
var total = Number(count || 0);
for (var i = 1; i <= total; i++) {
var id = 'verstak.synthetic-layout-' + String(i).padStart(2, '0');
pluginStates[id] = {
status: 'loaded',
enabled: true,
manifest: {
schemaVersion: 1,
id: id,
name: 'Synthetic Layout Plugin ' + i,
version: '0.0.' + i,
apiVersion: '0.1.0',
description: 'Synthetic plugin used by frontend layout tests.',
source: 'test',
provides: ['verstak/synthetic-layout-' + i + '/v1'],
requires: [],
optionalRequires: [],
permissions: [],
contributes: {
views: [],
commands: [],
sidebarItems: [],
statusBarItems: [],
settingsPanels: []
}
},
rootPath: '/tmp/verstak-test/plugins/synthetic-layout-' + i + '/with/a/long/path/for/responsive-checks',
error: ''
};
if (vaultPluginState.enabledPlugins.indexOf(id) === -1) {
vaultPluginState.enabledPlugins.push(id);
}
if (!vaultPluginState.desiredPlugins.some(function (p) { return p.id === id; })) {
vaultPluginState.desiredPlugins.push({ id: id, version: '0.0.' + i, source: 'test' });
}
}
},
setVaultStatus: function (status) { vaultStatus = status; },
setVaultPluginState: function (state) { vaultPluginState = state; },
setTrashDeletedAt: function (trashId, deletedAt) {
trashEntries.forEach(function (entry) {
if (entry.trashId === trashId) entry.deletedAt = deletedAt;
});
},
setReloadResponseMode: function (mode) { reloadResponseMode = mode || 'tuple'; }
};
window.__wailsMockReady = true;
console.log('[wails-mock] bridge installed');
})();