v0.1.0-alpha: interactive demo platform core
- Fastify API: demos/steps CRUD, content-addressed assets, scoped API keys (read/author/publish/admin, draft-only default per D4) - Player: image + sandboxed DOM steps, hotspots, tooltips, keyboard nav, privacy-first beacons (no IP storage, viewer tokens only — R7) - Editor: React SPA — demo list, drag-to-draw hotspots, annotations, publish - MCP server: Streamable HTTP, scope-filtered tools, untrusted-data wrapping on viewer-supplied content (R8); platform never calls a model - Capture extension (MV3): screenshot-flow recording, hotspots pre-placed from real clicks - Docker: single app image + postgres, Traefik-ready (expose only) E2E verified: capture->author->publish->play->beacon->lead->analytics + full MCP tool surface with scope filtering.
This commit is contained in:
commit
9b09f14916
33 changed files with 5555 additions and 0 deletions
25
extension/background.js
Normal file
25
extension/background.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Screenshot-flow capture: on each click in the recorded tab, grab a screenshot
|
||||
// plus the click position, building image Steps with pre-placed hotspots.
|
||||
let recording = { active: false, tabId: null, steps: [] };
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg.type === "start") {
|
||||
recording = { active: true, tabId: msg.tabId, steps: [] };
|
||||
chrome.scripting.executeScript({ target: { tabId: msg.tabId }, files: ["content.js"] });
|
||||
sendResponse({ ok: true });
|
||||
} else if (msg.type === "click" && recording.active && sender.tab?.id === recording.tabId) {
|
||||
chrome.tabs.captureVisibleTab({ format: "png" }, (dataUrl) => {
|
||||
recording.steps.push({ dataUrl, xPct: msg.xPct, yPct: msg.yPct });
|
||||
sendResponse({ ok: true });
|
||||
});
|
||||
return true; // async
|
||||
} else if (msg.type === "status") {
|
||||
sendResponse({ active: recording.active, count: recording.steps.length });
|
||||
} else if (msg.type === "stop") {
|
||||
recording.active = false;
|
||||
sendResponse({ steps: recording.steps });
|
||||
} else if (msg.type === "discard") {
|
||||
recording = { active: false, tabId: null, steps: [] };
|
||||
sendResponse({ ok: true });
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue