diff --git a/packages/extension/e2e.mjs b/packages/extension/e2e.mjs index ef75cbd..077024e 100644 --- a/packages/extension/e2e.mjs +++ b/packages/extension/e2e.mjs @@ -148,6 +148,28 @@ const all = itemsAfter.items ?? itemsAfter; const recordingSummary = await api(`/api/reports/${all[0].share_token}`); const steps = recordingSummary.steps ?? []; + // keep a step screenshot around for manual inspection: the recorder bar + // must not appear in it (plugin UI is hidden during captures) + const shotStep = steps.find((s) => s.screenshot_attachment_id != null); + if (shotStep) { + // steps reference the attachment row id; the files route wants the file id + const attachment = (recordingSummary.attachments ?? []).find((a) => a.id === shotStep.screenshot_attachment_id); + const fileId = attachment?.file_id ?? shotStep.screenshot_attachment_id; + const fileResponse = await fetch( + `${SERVER}/api/reports/by-token/${all[0].share_token}/files/${fileId}` + ); + if (!fileResponse.ok) { + console.log( + "step screenshot fetch failed:", + fileResponse.status, + JSON.stringify({ attachments: recordingSummary.attachments?.map((a) => a.file_id), steps: steps.map((s) => s.screenshot_attachment_id) }) + ); + } else { + const { writeFile } = await import("node:fs/promises"); + await writeFile("/tmp/ltt-shots/23-step-screenshot.png", Buffer.from(await fileResponse.arrayBuffer())); + console.log("step screenshot saved for inspection"); + } + } console.log( "recording:", JSON.stringify({ diff --git a/packages/extension/src/background/index.ts b/packages/extension/src/background/index.ts index b43ad29..f6f12c0 100644 --- a/packages/extension/src/background/index.ts +++ b/packages/extension/src/background/index.ts @@ -51,10 +51,23 @@ return buffer; } +async function setUiHidden(tabId: number, hidden: boolean): Promise { + // content script may be absent (chrome-internal pages) — ignore failures + await browser.tabs.sendMessage(tabId, { type: "set_ui_hidden", hidden }).catch(() => {}); +} + async function captureVisibleTab(tabId: number): Promise { - // the first argument of captureVisibleTab is a *window* id const tab = await browser.tabs.get(tabId); - return (await browser.tabs.captureVisibleTab(tab.windowId ?? undefined, { format: "png" })) as string; + // hide the plugin's own UI so it never lands in the screenshot + await setUiHidden(tabId, true); + try { + // let the compositor repaint the page without the overlay + await new Promise((resolve) => setTimeout(resolve, 150)); + // the first argument of captureVisibleTab is a *window* id + return (await browser.tabs.captureVisibleTab(tab.windowId ?? undefined, { format: "png" })) as string; + } finally { + await setUiHidden(tabId, false); + } } async function captureForEvent(tabId: number, eventIndex: number): Promise { diff --git a/packages/extension/src/content/index.ts b/packages/extension/src/content/index.ts index a05f18f..2652bec 100644 --- a/packages/extension/src/content/index.ts +++ b/packages/extension/src/content/index.ts @@ -7,6 +7,7 @@ import type { ElementContext } from "@ltt/shared"; import { mountOverlay } from "./overlay/mount"; import { buildElementContext, collectEnvironment } from "../lib/selector"; +import { pushUiHidden, popUiHidden } from "./uiVisibility"; let overlay: ReturnType | null = null; @@ -124,6 +125,13 @@ return true; } + case "set_ui_hidden": + // the background hides all plugin UI while it grabs a screenshot + if (Boolean(msg.hidden)) pushUiHidden(); + else popUiHidden(); + sendResponse({ ok: true }); + return true; + default: return true; } diff --git a/packages/extension/src/content/overlay/NoteComposer.vue b/packages/extension/src/content/overlay/NoteComposer.vue index 2eb65f7..677624e 100644 --- a/packages/extension/src/content/overlay/NoteComposer.vue +++ b/packages/extension/src/content/overlay/NoteComposer.vue @@ -199,7 +199,7 @@ display: flex; flex-direction: column; gap: 10px; - width: min(920px, calc(100vw - 48px)); + width: min(1100px, calc(100vw - 32px)); max-height: calc(100vh - 48px); overflow-y: auto; padding: 16px; @@ -233,7 +233,7 @@ } /* big drawing area: the whole screenshot visible at once, canvas contain-fit */ .composer-screenshot :deep(.annotation-canvas-wrap) { - height: 55vh; + height: 62vh; } .composer-placeholder { padding: 40px; diff --git a/packages/extension/src/content/overlay/RegionSelector.vue b/packages/extension/src/content/overlay/RegionSelector.vue index d67fdee..a8c20c2 100644 --- a/packages/extension/src/content/overlay/RegionSelector.vue +++ b/packages/extension/src/content/overlay/RegionSelector.vue @@ -1,6 +1,7 @@