verstak-official-plugins/plugins/platform-test/frontend/dist/index.js

772 lines
32 KiB
JavaScript
Raw Permalink 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.

/* ===========================================================
Platform Test Plugin — Verstak v2 Frontend Bundle
Contract: window.VerstakPluginRegister(id, { components })
=========================================================== */
(function () {
'use strict';
/* ------------------------------------------------------------------ */
/* Style injection */
/* Loads style.css once into the document head */
/* ------------------------------------------------------------------ */
function injectStyles() {
if (document.getElementById('pt-style-injected')) return;
var link = document.createElement('link');
link.id = 'pt-style-injected';
link.rel = 'stylesheet';
link.href = 'frontend/style.css';
document.head.appendChild(link);
}
/* ------------------------------------------------------------------ */
/* Utilities */
/* ------------------------------------------------------------------ */
function el(tag, attrs, children) {
var elem = document.createElement(tag);
if (attrs) {
Object.keys(attrs).forEach(function (k) {
if (k === 'className') { elem.className = attrs[k]; }
else if (k === 'style' && typeof attrs[k] === 'object') {
Object.assign(elem.style, attrs[k]);
}
else if (k.slice(0, 2) === 'on') {
elem.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
}
else { elem.setAttribute(k, attrs[k]); }
});
}
if (children) {
(Array.isArray(children) ? children : [children]).forEach(function (c) {
if (c == null) return;
elem.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
});
}
return elem;
}
function div(className, children) {
return el('div', { className: className }, children);
}
function span(className, text) {
return el('span', { className: className }, [text]);
}
/* ------------------------------------------------------------------ */
/* DiagnosticsPanel component */
/* ------------------------------------------------------------------ */
var DiagnosticsPanel = {
mount: function (containerEl, props, api) {
/* Inject shared styles */
injectStyles();
containerEl.innerHTML = '';
containerEl.className = 'pt-root';
containerEl.__ptCleanup = [];
function trackCleanup(fn) {
if (typeof fn === 'function') {
if (!Array.isArray(containerEl.__ptCleanup)) {
try { fn(); } catch (e) { console.error('[platform-test] late cleanup error:', e); }
return;
}
containerEl.__ptCleanup.push(fn);
}
}
/* ── Header ─────────────────────────────────────────────────── */
var header = div('pt-header', [
span('pt-icon', '◉'),
div('pt-title-group', [
el('h2', { className: 'pt-plugin-name' }, ['Platform Diagnostics']),
el('p', { className: 'pt-plugin-id' }, [api.pluginId]),
]),
span('pt-version', 'v' + (props && props.version ? props.version : '0.1.0')),
]);
/* ── Status badge ──────────────────────────────────────────── */
var badge = div('pt-badge pt-badge-success', [
el('span', {}, ['✅']),
el('span', {}, ['Frontend Bundle Loaded']),
]);
var badgeRow = div('', [badge]);
/* ── Real Plugin API bridge checks ─────────────────────────── */
var savedValue = span('pt-list-value pt-saved-setting', 'Saved setting: loading...');
var capabilityValue = span('pt-list-value pt-capability-result', 'Capabilities: loading...');
var commandValue = span('pt-list-value pt-command-result', 'Command: registering...');
var eventValue = span('pt-list-value pt-event-result', 'Event: subscribing...');
var filesValue = span('pt-list-value pt-files-result', 'Files: running...');
var filesErrorValue = span('pt-list-value pt-files-error-result', 'Files error path: checking...');
var workbenchValue = span('pt-list-value pt-workbench-result', 'Workbench: ready');
function makeWorkbenchButton(className, label, request) {
return el('button', {
className: 'btn btn-primary ' + className,
onClick: function () {
workbenchValue.textContent = 'Workbench: opening...';
api.workbench.editResource(request)
.then(function (result) {
workbenchValue.textContent = 'Workbench: opened ' + result.request.path + ' with ' + (result.providerId || 'no-provider');
workbenchValue.setAttribute('data-workbench-status', result.status === 'opened' ? 'ok' : result.status);
})
.catch(function (err) {
workbenchValue.textContent = 'Workbench error: ' + (err && err.message ? err.message : String(err));
workbenchValue.setAttribute('data-workbench-status', 'error');
});
},
}, [label]);
}
var openTextWorkbenchButton = makeWorkbenchButton('pt-open-workbench-text', 'Open Text Diagnostic', {
kind: 'vault-file',
path: 'Docs/todo.txt',
extension: '.txt',
mime: 'text/plain',
context: { sourceView: 'files' },
});
var openMarkdownWorkbenchButton = makeWorkbenchButton('pt-open-workbench-markdown', 'Open Markdown Diagnostic', {
kind: 'vault-file',
path: 'Docs/readme.md',
extension: '.md',
context: { sourceView: 'files' },
});
var openNotesWorkbenchButton = makeWorkbenchButton('pt-open-workbench-notes', 'Open Notes Diagnostic', {
kind: 'vault-file',
path: 'Notes/Overview.md',
extension: '.md',
context: {
sourceView: 'notes',
isInsideNotesFolder: true,
notesMode: true,
},
});
var settingInput = el('input', {
className: 'pt-setting-input',
type: 'text',
'aria-label': 'Saved setting',
value: 'changed value',
});
var saveStatus = span('pt-list-value', '');
var saveButton = el('button', {
className: 'btn btn-primary pt-save-setting',
onClick: function () {
saveStatus.textContent = 'Saving...';
api.settings.write('savedText', settingInput.value)
.then(function () {
savedValue.textContent = 'Saved setting: ' + settingInput.value;
saveStatus.textContent = 'Saved';
})
.catch(function (err) {
saveStatus.textContent = 'Error: ' + (err && err.message ? err.message : String(err));
});
},
}, ['Save Setting']);
api.settings.read('savedText')
.then(function (value) {
var text = value || '';
settingInput.value = text || 'changed value';
savedValue.textContent = 'Saved setting: ' + text;
})
.catch(function (err) {
savedValue.textContent = 'Settings error: ' + (err && err.message ? err.message : String(err));
});
api.capabilities.list()
.then(function (caps) {
capabilityValue.textContent = 'Capabilities: ' + caps.length + ' available';
})
.catch(function (err) {
capabilityValue.textContent = 'Capabilities error: ' + (err && err.message ? err.message : String(err));
});
api.capabilities.has('verstak/platform-test/v1')
.then(function (available) {
badge.setAttribute('data-capability-status', available ? 'available' : 'missing');
badge.lastChild.textContent = 'Frontend Bundle Loaded | capability ' + (available ? 'available' : 'missing');
})
.catch(function (err) {
badge.setAttribute('data-capability-status', 'error');
badge.lastChild.textContent = 'Capability error: ' + (err && err.message ? err.message : String(err));
});
api.commands.register('verstak.platform-test.show-version', function () {
return {
version: '0.1.0',
source: 'bundled-frontend',
};
})
.then(function (unregister) {
trackCleanup(unregister);
return api.commands.execute('verstak.platform-test.show-version', {});
})
.then(function (result) {
badge.setAttribute('data-command-status', result.status || '');
commandValue.textContent = 'Command: ' + result.status + ' ' + result.result.version + ' from ' + result.result.source;
})
.catch(function (err) {
badge.setAttribute('data-command-status', 'error');
commandValue.textContent = 'Command error: ' + (err && err.message ? err.message : String(err));
console.error('[platform-test] command bridge error:', err);
});
api.events.subscribe('verstak.platform-test.echo', function (event) {
var message = event && event.payload ? event.payload.message : '';
eventValue.textContent = 'Event: received ' + message;
eventValue.setAttribute('data-event-status', 'received');
})
.then(function (unsubscribe) {
trackCleanup(unsubscribe);
return api.events.publish('verstak.platform-test.echo', { message: 'hello-event' });
})
.catch(function (err) {
eventValue.textContent = 'Event error: ' + (err && err.message ? err.message : String(err));
eventValue.setAttribute('data-event-status', 'error');
});
api.files.createFolder('PlatformTest')
.catch(function (err) {
if (String(err).indexOf('conflict') === -1) throw err;
})
.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) {
var found = entries.some(function (entry) {
return entry.relativePath === 'PlatformTest/files-api.txt';
});
if (!found) 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 () {
filesValue.textContent = 'Files: wrote/read/listed/moved/trashed';
filesValue.setAttribute('data-files-status', 'ok');
})
.catch(function (err) {
filesValue.textContent = 'Files error: ' + (err && err.message ? err.message : String(err));
filesValue.setAttribute('data-files-status', 'error');
});
api.files.readText('.verstak/vault.json')
.then(function () {
filesErrorValue.textContent = 'Files error path: unexpectedly allowed';
filesErrorValue.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) {
filesErrorValue.textContent = 'Files error path: wrong error ' + message;
filesErrorValue.setAttribute('data-files-error-status', 'error');
return;
}
filesErrorValue.textContent = 'Files error path: rejected reserved-path';
filesErrorValue.setAttribute('data-files-error-status', 'expected');
});
var bridgeCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Real Plugin API Bridge']),
el('ul', { className: 'pt-list' }, [
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Persisted setting'),
savedValue,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'New value'),
settingInput,
]),
el('li', { className: 'pt-list-item' }, [
saveButton,
saveStatus,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Capabilities'),
capabilityValue,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Command runtime'),
commandValue,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Event runtime'),
eventValue,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Files runtime'),
filesValue,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Files reserved path'),
filesErrorValue,
]),
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', 'Workbench routing'),
openTextWorkbenchButton,
openMarkdownWorkbenchButton,
openNotesWorkbenchButton,
workbenchValue,
]),
]),
]);
/* ── Test results summary ──────────────────────────────────── */
var testsData = [
{ label: 'Plugin Registration', status: 'pass' },
{ label: 'Capability: verstak/platform-test/v1', status: 'pass' },
{ label: 'Capability: verstak/diagnostics/v1', status: 'pass' },
{ label: 'Capability: verstak/core/files/v1', status: 'pass' },
{ label: 'Capability: verstak/core/workbench/v1', status: 'pass' },
{ label: 'API Contract Compliance', status: 'pass' },
];
var totalTests = testsData.length;
var passedTests = testsData.filter(function (t) { return t.status === 'pass'; }).length;
var summaryRow = div('pt-test-summary', [
div('pt-test-stat', [
span('pt-test-stat-value pt-pass', String(passedTests)),
span('pt-test-stat-label', 'Passed'),
]),
div('pt-test-stat', [
span('pt-test-stat-value pt-fail', String(totalTests - passedTests)),
span('pt-test-stat-label', 'Failed'),
]),
div('pt-test-stat', [
span('pt-test-stat-value', String(totalTests)),
span('pt-test-stat-label', 'Total'),
]),
div('pt-test-stat', [
span('pt-test-stat-value pt-pass', '100%'),
span('pt-test-stat-label', 'Success Rate'),
]),
]);
var testsList = el('ul', { className: 'pt-list' });
testsData.forEach(function (t) {
var dot = el('span', { className: 'pt-cap-dot pt-cap-dot-ok' });
var item = el('li', { className: 'pt-list-item' }, [
el('span', { className: 'pt-list-label' }, [dot, ' ', t.label]),
span('pt-list-value pt-pass', t.status === 'pass' ? '✓ PASS' : '✗ FAIL'),
]);
testsList.appendChild(item);
});
var testsCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Test Results']),
summaryRow,
testsList,
]);
/* ── Capabilities status via API ───────────────────────────── */
var knownCaps = [
{ id: 'verstak/platform-test/v1', label: 'Platform Test API' },
{ id: 'verstak/diagnostics/v1', label: 'Diagnostics API' },
{ id: 'verstak/core/vault/v1', label: 'Vault API (optional)' },
{ id: 'verstak/core/sync/v1', label: 'Sync API (optional)' },
{ id: 'verstak/core/workbench/v1', label: 'Workbench API' },
];
var capList = el('ul', { className: 'pt-list' });
knownCaps.forEach(function (cap) {
var dot = el('span', {
className: 'pt-cap-dot pt-cap-dot-missing',
});
var statusText = span('pt-list-value', 'Checking...');
var item = el('li', { className: 'pt-list-item' }, [
el('span', { className: 'pt-list-label' }, [dot, ' ', cap.label]),
statusText,
]);
capList.appendChild(item);
api.capabilities.has(cap.id)
.then(function (available) {
dot.className = 'pt-cap-dot ' + (available ? 'pt-cap-dot-ok' : 'pt-cap-dot-missing');
statusText.textContent = available ? '✓ Available' : '— Unavailable';
})
.catch(function () {
dot.className = 'pt-cap-dot pt-cap-dot-missing';
statusText.textContent = 'Error';
});
});
var capsCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Registered Capabilities']),
capList,
]);
/* ── Plugin info ───────────────────────────────────────────── */
var infoList = el('ul', { className: 'pt-list' });
var infoItems = [
{ label: 'Plugin ID', value: api.pluginId },
{ label: 'Bundle Status', value: 'Loaded ✓' },
{ label: 'Registration Scheme', value: 'VerstakPluginRegister' },
{ label: 'Components', value: 'DiagnosticsPanel, PlatformTestSettings' },
{ label: 'Container', value: containerEl.tagName.toLowerCase() + (containerEl.id ? '#' + containerEl.id : '') },
];
infoItems.forEach(function (item) {
infoList.appendChild(
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', item.label),
span('pt-list-value', item.value),
])
);
});
var infoCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Plugin Info']),
infoList,
]);
/* ── Host API status ───────────────────────────────────────── */
var apiStatusList = el('ul', { className: 'pt-list' });
var apiChecks = [
{ label: 'events.publish', ok: typeof api.events.publish === 'function' },
{ label: 'events.subscribe', ok: typeof api.events.subscribe === 'function' },
{ label: 'settings.read', ok: typeof api.settings.read === 'function' },
{ label: 'settings.write', ok: typeof api.settings.write === 'function' },
{ label: 'commands.execute', ok: typeof api.commands.execute === 'function' },
{ label: 'capabilities.has', ok: typeof api.capabilities.has === 'function' },
{ label: 'files.list', ok: typeof api.files.list === 'function' },
{ label: 'files.readText', ok: typeof api.files.readText === 'function' },
{ label: 'files.writeText', ok: typeof api.files.writeText === 'function' },
{ label: 'files.trash', ok: typeof api.files.trash === 'function' },
{ label: 'workbench.openResource', ok: typeof api.workbench.openResource === 'function' },
{ label: 'workbench.editResource', ok: typeof api.workbench.editResource === 'function' },
];
apiChecks.forEach(function (chk) {
var dot = el('span', {
className: 'pt-cap-dot ' + (chk.ok ? 'pt-cap-dot-ok' : 'pt-cap-dot-missing'),
});
apiStatusList.appendChild(
el('li', { className: 'pt-list-item' }, [
el('span', { className: 'pt-list-label' }, [dot, ' ', chk.label]),
span('pt-list-value', chk.ok ? '✓ Ready' : '✗ Missing'),
])
);
});
var apiCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Host API Methods']),
apiStatusList,
]);
/* ── Mouse Event Inspector ──────────────────────────────────── */
var mouseLog = [];
var mouseCapturing = false;
var mouseHandlers = [];
var mouseEventTypes = ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'auxclick'];
var mouseLogContainer = el('pre', {
className: 'pt-mouse-log',
style: {
background: '#0d0d1a',
color: '#4ecca3',
padding: '0.75rem',
borderRadius: '6px',
fontSize: '0.75rem',
fontFamily: 'monospace',
maxHeight: '300px',
overflow: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
margin: '0.5rem 0',
lineHeight: '1.5',
},
}, ['']);
var mouseCountSpan = span('pt-list-value', '0');
function renderMouseLog() {
if (mouseLog.length === 0) {
mouseLogContainer.textContent = '(no events captured)';
mouseCountSpan.textContent = '0';
return;
}
var lines = mouseLog.map(function (e, i) {
return '[' + (i + 1) + '] ' + e.type + ' button=' + e.button + ' buttons=' + e.buttons +
' which=' + e.which + ' pointerType=' + e.pointerType +
' defaultPrevented=' + e.defaultPrevented +
' target=' + e.targetTag + (e.targetClass ? '.' + e.targetClass : '');
});
mouseLogContainer.textContent = lines.join('\n');
mouseCountSpan.textContent = String(mouseLog.length);
mouseLogContainer.scrollTop = mouseLogContainer.scrollHeight;
}
function onMouseEvent(e) {
mouseLog.push({
type: e.type,
button: e.button,
buttons: e.buttons,
which: e.which,
pointerType: e.pointerType || '',
defaultPrevented: e.defaultPrevented,
targetTag: e.target ? e.target.tagName : '',
targetClass: e.target ? (e.target.className || '') : '',
time: Date.now(),
});
if (mouseLog.length > 200) mouseLog.shift();
renderMouseLog();
}
function startMouseCapture() {
if (mouseCapturing) return;
mouseCapturing = true;
mouseEventTypes.forEach(function (type) {
var handler = function (e) { onMouseEvent(e); };
window.addEventListener(type, handler, true);
mouseHandlers.push({ type: type, handler: handler });
});
startStopBtn.textContent = '■ Stop Capture';
startStopBtn.setAttribute('data-mouse-capturing', 'true');
renderMouseLog();
}
function stopMouseCapture() {
if (!mouseCapturing) return;
mouseCapturing = false;
mouseHandlers.forEach(function (h) {
window.removeEventListener(h.type, h.handler, true);
});
mouseHandlers = [];
startStopBtn.textContent = '▶ Start Capture';
startStopBtn.setAttribute('data-mouse-capturing', 'false');
}
var startStopBtn = el('button', {
className: 'btn btn-primary',
style: { marginRight: '0.5rem' },
onClick: function () {
if (mouseCapturing) { stopMouseCapture(); } else { startMouseCapture(); }
},
}, ['▶ Start Capture']);
var clearBtn = el('button', {
className: 'btn btn-secondary',
style: { marginRight: '0.5rem' },
onClick: function () {
mouseLog = [];
renderMouseLog();
},
}, ['Clear']);
var copyBtn = el('button', {
className: 'btn btn-secondary',
onClick: function () {
var json = JSON.stringify(mouseLog, null, 2);
if (navigator.clipboard) {
navigator.clipboard.writeText(json).then(function () {
copyBtn.textContent = '✓ Copied!';
setTimeout(function () { copyBtn.textContent = 'Copy JSON'; }, 1500);
});
} else {
var ta = document.createElement('textarea');
ta.value = json;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
copyBtn.textContent = '✓ Copied!';
setTimeout(function () { copyBtn.textContent = 'Copy JSON'; }, 1500);
}
},
}, ['Copy JSON']);
trackCleanup(function () { stopMouseCapture(); });
var mouseCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Mouse Event Inspector']),
el('p', { style: { margin: '0 0 0.5rem', color: '#a0a0b8', fontSize: '0.8rem' } }, [
'Captures ALL mouse/pointer events on window. Press back/forward buttons to see what WebKitGTK reports.',
]),
el('div', { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' } }, [
startStopBtn,
clearBtn,
copyBtn,
span('pt-list-label', ' Events:'),
mouseCountSpan,
]),
mouseLogContainer,
]);
/* ── Assemble ──────────────────────────────────────────────── */
containerEl.appendChild(header);
containerEl.appendChild(badgeRow);
containerEl.appendChild(bridgeCard);
containerEl.appendChild(testsCard);
containerEl.appendChild(capsCard);
containerEl.appendChild(mouseCard);
containerEl.appendChild(infoCard);
containerEl.appendChild(apiCard);
},
unmount: function (containerEl) {
if (Array.isArray(containerEl.__ptCleanup)) {
while (containerEl.__ptCleanup.length > 0) {
var cleanup = containerEl.__ptCleanup.pop();
try { cleanup(); } catch (e) { console.error('[platform-test] cleanup error:', e); }
}
}
containerEl.innerHTML = '';
containerEl.className = '';
delete containerEl.__ptCleanup;
},
};
/* ------------------------------------------------------------------ */
/* MarkdownDiagnosticProvider component */
/* ------------------------------------------------------------------ */
var MarkdownDiagnosticProvider = {
mount: function (containerEl, props) {
injectStyles();
var request = props && props.request ? props.request : {};
var context = request.context && (request.context.notesMode || request.context.isInsideNotesFolder)
? 'notes-markdown'
: ((request.extension === '.md' || request.extension === '.markdown') ? 'generic-markdown' : 'generic-text');
containerEl.innerHTML = '';
containerEl.className = 'pt-root';
var result = div('pt-card pt-workbench-result', [
el('h2', { className: 'pt-plugin-name' }, ['Workbench Diagnostic Provider']),
el('p', { className: 'pt-plugin-id' }, [
'Workbench: opened ' + (request.path || '') + ' with ' + ((props && props.providerId) || '') +
' mode=' + (request.mode || '') + ' context=' + context,
]),
]);
result.setAttribute('data-workbench-status', 'ok');
result.setAttribute('data-resource-path', request.path || '');
result.setAttribute('data-resource-mode', request.mode || '');
result.setAttribute('data-resource-context', context);
containerEl.appendChild(result);
},
unmount: function (containerEl) {
containerEl.innerHTML = '';
containerEl.className = '';
},
};
/* ------------------------------------------------------------------ */
/* PlatformTestSettings component */
/* ------------------------------------------------------------------ */
var PlatformTestSettings = {
mount: function (containerEl, props, api) {
injectStyles();
containerEl.innerHTML = '';
containerEl.className = 'pt-root';
/* ── Counter state (local, not persisted) ──────────────────── */
var counterState = { value: 0 };
/* ── Header ─────────────────────────────────────────────────── */
var header = div('pt-header', [
span('pt-icon', '⚙️'),
div('pt-title-group', [
el('h2', { className: 'pt-plugin-name' }, ['Platform Test Settings']),
el('p', { className: 'pt-plugin-id' }, [api.pluginId]),
]),
]);
/* ── Info card ──────────────────────────────────────────────── */
var infoCard = div('pt-card', [
el('p', { style: { margin: '0', color: '#a0a0b8', fontSize: '0.85rem' } }, [
'Settings panel loaded from plugin frontend bundle via ',
el('code', { style: { background: '#1a1a2e', padding: '0.1rem 0.3rem', borderRadius: '3px', color: '#4ecca3' } }, ['VerstakPluginRegister']),
' contract.',
]),
]);
/* ── Counter section ────────────────────────────────────────── */
var counterDisplay = div('pt-counter', [
el('span', { className: 'pt-counter-value' }, [String(counterState.value)]),
span('pt-counter-label', 'clicks (session only, no persistence)'),
]);
var incrementBtn = el('button', { className: 'btn btn-primary', onClick: function () {
counterState.value += 1;
counterDisplay.firstChild.textContent = String(counterState.value);
}}, ['+ Increment']);
var decrementBtn = el('button', { className: 'btn btn-secondary', onClick: function () {
counterState.value = Math.max(0, counterState.value - 1);
counterDisplay.firstChild.textContent = String(counterState.value);
}}, [' Decrement']);
var resetBtn = el('button', { className: 'btn btn-secondary', onClick: function () {
counterState.value = 0;
counterDisplay.firstChild.textContent = '0';
}}, ['↺ Reset']);
var btnGroup = el('div', { style: { display: 'flex', gap: '0.5rem' } }, [
incrementBtn, decrementBtn, resetBtn,
]);
var counterCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Interactive Counter (Local State)']),
counterDisplay,
btnGroup,
el('p', { style: { marginTop: '0.75rem', color: '#6c6c8a', fontSize: '0.7rem' } }, [
'This counter is a local demo. State is not persisted — refreshing resets it.',
]),
]);
/* ── Settings stub ─────────────────────────────────────────── */
var settingsDemoList = el('ul', { className: 'pt-list' });
var settingsItems = [
{ label: 'Auto-run on load', value: 'true' },
{ label: 'Verbose logging', value: 'false' },
{ label: 'Theme override', value: 'inherit' },
{ label: 'Notifications', value: 'enabled' },
];
settingsItems.forEach(function (s) {
settingsDemoList.appendChild(
el('li', { className: 'pt-list-item' }, [
span('pt-list-label', s.label),
span('pt-list-value', s.value),
])
);
});
var settingsCard = div('pt-card', [
el('h3', { className: 'pt-card-title' }, ['Plugin Settings (Demo)']),
settingsDemoList,
el('p', { style: { marginTop: '0.5rem', color: '#6c6c8a', fontSize: '0.7rem' } }, [
'Use api.settings.read() / api.settings.write() for persisted settings.',
]),
]);
/* ── Assemble ──────────────────────────────────────────────── */
containerEl.appendChild(header);
containerEl.appendChild(infoCard);
containerEl.appendChild(counterCard);
containerEl.appendChild(settingsCard);
},
unmount: function (containerEl) {
containerEl.innerHTML = '';
containerEl.className = '';
},
};
/* ------------------------------------------------------------------ */
/* Register with the host */
/* ------------------------------------------------------------------ */
window.VerstakPluginRegister('verstak.platform-test', {
components: {
DiagnosticsPanel: DiagnosticsPanel,
PlatformTestSettings: PlatformTestSettings,
MarkdownDiagnosticProvider: MarkdownDiagnosticProvider,
},
});
})();