autokap 1.8.4 → 1.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1107,6 +1107,7 @@ async function prepareScreenshotBufferForDirectUpload(input, metadata, program,
1107
1107
  }
1108
1108
  output = await applyDeviceFrame(output, variantSpec.deviceFrame, {
1109
1109
  orientation: inferCliOrientation(metadata.viewport ?? null),
1110
+ viewport: metadata.viewport ?? undefined,
1110
1111
  colorScheme: metadata.theme,
1111
1112
  outputScale: normalizeCliDeviceScaleFactor(metadata.deviceScaleFactor)
1112
1113
  ?? normalizeCliDeviceScaleFactor(program.outputScale)
package/dist/mockup.d.ts CHANGED
@@ -191,6 +191,18 @@ export interface MockupOptions {
191
191
  };
192
192
  /** Explicit color scheme override — bypasses auto-detection from edge colors */
193
193
  colorScheme?: 'light' | 'dark';
194
+ /**
195
+ * Viewport the screenshot was captured at (logical CSS px). For frameless
196
+ * browser mockups this drives the frame geometry so the content area matches
197
+ * the capture aspect ratio — without it, captures taken at a custom preset
198
+ * viewport get stretched by the fit:'fill' resize into the device config's
199
+ * logical dimensions. Ignored for devices with a frame asset (their screen
200
+ * rect is fixed by the asset).
201
+ */
202
+ viewport?: {
203
+ width: number;
204
+ height: number;
205
+ };
194
206
  }
195
207
  export interface ResolvedDeviceFrameDescriptor {
196
208
  id: string;
package/dist/mockup.js CHANGED
@@ -94,6 +94,22 @@ function normalizeBrowserCornerRadius(cornerRadius, dpr) {
94
94
  }
95
95
  return cornerRadius * dpr;
96
96
  }
97
+ // Bounds for capture viewports consumed by mockup geometry. The execution
98
+ // schema only enforces positive integers, so clamp here to keep the physical
99
+ // canvas (logical × outputScale ≤ 4) under sharp's ~32767px dimension limit.
100
+ const MIN_MOCKUP_VIEWPORT_PX = 16;
101
+ const MAX_MOCKUP_VIEWPORT_PX = 7680;
102
+ /** Validate a capture viewport for mockup geometry. Returns null when unusable. */
103
+ function sanitizeMockupViewport(viewport) {
104
+ const width = viewport?.width;
105
+ const height = viewport?.height;
106
+ if (!Number.isFinite(width) || !Number.isFinite(height))
107
+ return null;
108
+ if (width <= 0 || height <= 0)
109
+ return null;
110
+ const clamp = (value) => Math.max(MIN_MOCKUP_VIEWPORT_PX, Math.min(MAX_MOCKUP_VIEWPORT_PX, Math.round(value)));
111
+ return { width: clamp(width), height: clamp(height) };
112
+ }
97
113
  /**
98
114
  * Resolve the orientation config for a given orientation.
99
115
  * Prefers per-orientation configs; falls back to rotation logic for legacy format.
@@ -526,9 +542,14 @@ export async function applyDeviceFrame(screenshot, deviceId, options) {
526
542
  frameHeight: geometry.frameHeight,
527
543
  screenRect: geometry.screenRect,
528
544
  };
529
- // For frameless browser devices: use device config logical dimensions as reference.
530
- // The screenshot is resized to fit the content area (sharp fit:'fill').
531
- // This matches how the Studio client renders frameless browsers.
545
+ // For frameless browser devices: build the frame around the capture viewport
546
+ // when provided (exact inverse of computeMockupLayout's content-area math, so
547
+ // the content area maps 1:1 to the screenshot and fit:'fill' cannot distort
548
+ // captures taken at a custom preset viewport). Without a viewport, fall back
549
+ // to the device config logical dimensions — this matches how the Studio
550
+ // client renders frameless browsers, and is identical to the viewport path
551
+ // for captures taken at the device's default viewport (logical − safe areas,
552
+ // see resolveDeviceViewport in capture-request-plan.ts).
532
553
  // Safari uses a 52px visible toolbar (cropped from the 88px asset);
533
554
  // Chrome uses a two-row 86px toolbar.
534
555
  const BROWSER_BAR_HEIGHT = resolved.browserStyle === 'safari' ? 52 : 86;
@@ -539,11 +560,20 @@ export async function applyDeviceFrame(screenshot, deviceId, options) {
539
560
  const safeAreaTop = normalizeBrowserSafeAreaTop(resolved.safeArea.top, 1, // logical pixels — pixelScale (os) handles final scaling
540
561
  BROWSER_BAR_HEIGHT);
541
562
  resolved.safeArea = { ...resolved.safeArea, top: safeAreaTop };
542
- // Frame = device config logical dimensions (not screenshot dimensions)
543
- geo.frameWidth = logicalW;
544
- geo.frameHeight = logicalH;
545
- geo.screenRect = { x: 0, y: 0, width: logicalW, height: logicalH };
546
- logger.debug(`[mockup] frameless browser: logicalW=${logicalW}, logicalH=${logicalH}, os=${os}, geo=${geo.frameWidth}x${geo.frameHeight}`);
563
+ const captureViewport = sanitizeMockupViewport(opts.viewport);
564
+ if (captureViewport) {
565
+ geo.frameWidth = captureViewport.width
566
+ + ((resolved.safeArea.left ?? 0) + (resolved.safeArea.right ?? 0)) * scale;
567
+ geo.frameHeight = captureViewport.height
568
+ + (safeAreaTop + (resolved.safeArea.bottom ?? 0)) * scale;
569
+ }
570
+ else {
571
+ // Frame = device config logical dimensions (not screenshot dimensions)
572
+ geo.frameWidth = logicalW;
573
+ geo.frameHeight = logicalH;
574
+ }
575
+ geo.screenRect = { x: 0, y: 0, width: geo.frameWidth, height: geo.frameHeight };
576
+ logger.debug(`[mockup] frameless browser: logicalW=${logicalW}, logicalH=${logicalH}, viewport=${captureViewport ? `${captureViewport.width}x${captureViewport.height}` : 'none'}, os=${os}, geo=${geo.frameWidth}x${geo.frameHeight}`);
547
577
  }
548
578
  else if (!hasFrame && geo.frameWidth === 0 && geo.frameHeight === 0) {
549
579
  // Non-browser frameless fallback
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autokap",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "AI-powered CLI tool for capturing clean screenshots of websites",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",