Changelog
What's new in Storeshots
Tracked in CHANGELOG.md on GitHub.
Changelog
All notable changes to Storeshots are documented here. The format is based on Keep a Changelog and this project loosely follows Semantic Versioning.
[Unreleased]
Added
- iPad landscape mode. Landscape was previously Android-tablet-only. Selecting iPad in the editor now shows a Portrait ↕ / Landscape ↔ toggle, mirroring the Android tablets. Landscape has its own screenshot upload slot, device frame, and export sizes (iPad Pro 12.9" & 11" — 2732×2048 / 2224×1668). (#6)
[0.11.0] — 2026-06-07
Added
- Translations manager. A new "Translations" toolbar action opens a
side-by-side table of every selected language. Edit any label/headline inline,
import a language from a JSON (
[{label, headline}]) or CSV file, export a language's copy as JSON for external translation, and AI-translate from one source language into the others (translates the copy you already have instead of re-deriving it from screenshots). - Per-device screenshot picker for paired layouts. The two-phone layouts (V2/V6) showed the same screenshot on both frames with no way to change the second one. In focused mode each device frame now has a screenshot picker, so you can show a different uploaded screenshot per frame — no more automatic "next slide" reuse and the collisions it caused. Each frame also has its own delete button.
- Apply layout to all languages. Element layout stays per-language on purpose (different word lengths need different placement), but a new "Apply layout → all languages" action in the focused-mode Layout menu copies the current slide's layout (position, variant) into every language at once. Text is left untouched.
- Store preview for iPad and tablets. The store preview was iPhone-only; it now works for every device except the feature graphic, with platform-aware labels (App Store / Play Store).
- Headline guidance. The sidebar headline editor shows a live word/line counter that warns when a headline exceeds the ASO limit (≤5 words/line, ≤2 lines), plus a "Fold" badge on slides 1–3 to flag what shows above the fold.
- The Feature Graphic is now fully editable. The 1024×500 Play Store banner was a fixed layout; now every piece — app icon, text, and the feature chips — can be dragged, resized, and rotated on the canvas. Add or remove text, chip, and icon elements, edit text and chip content from the side panel, and reset to the default with one click. Custom layouts ride along in the project file.
- AI brief field. An optional free-text box (under Description) steers copy generation — audience, tone, keywords, differentiators — on top of the app name, description, and screenshots. It is sent to ✦ Headlines, ✦ Full design, and multi-language generation.
- AI now suggests feature chips. ✦ Full design returns a prioritised feature list that fills the feature-graphic chips and the Key features field, ready to revise.
Changed
- Tidier editor toolbar. The language switcher moved into the device row (removing a whole toolbar row), the focused-mode actions are grouped under a single "Layout" menu (variant + reset + apply-to-all), and the action bar now wraps instead of clipping buttons off the edge.
- Visual slide filmstrip. The focused-mode slide switcher is now a row of screenshot thumbnails (index badge + label, active slide ringed) instead of plain text chips.
- Start templates seed the AI brief with an audience/tone hint for their vertical (SaaS, Game, Finance, Health), so AI generation starts on-target.
- Android listings now use all 10 slides (was 8), matching the App Store, so the last two screenshots are no longer dropped and there's more room for alternative layouts.
- Screenshot upload tabs are grouped by form factor (Phone / Tablet) so the Android tablet portrait/landscape variants no longer crowd one flat row; the same devices are still there, just tidier.
Fixed
- Brand style "Auto" now works on every device. Colour extraction read only the iPhone screenshots, so "Auto" did nothing on iPad/Android. It now uses the active device's screenshots and falls back to any uploaded screenshot.
- Dark-mode form controls. On systems set to dark mode, native controls (selects, inputs, the colour picker) rendered with the OS dark theme and became unreadable. The UI is pinned to a light colour-scheme.
- Adding an app-icon element with no icon now shows a warning instead of silently adding an invisible element.
- Screenshots no longer repeat across slides. The two paired-device layouts reused a neighbouring slide's screenshot (slides 1 & 2 shared the first, slides 6 & 7 shared the seventh), so one uploaded screenshot appeared twice while another went unused. Each slide now maps 1:1 to its own screenshot; the paired layouts show that slide's screenshot as a faded echo behind the main device. Applies to both App Store and Play Store layouts.
0.10.0 — 2026-06-04
Added
- Typeface picker with custom font upload. Brand style now has a Typeface
section with eight built-in fonts — Inter, Poppins, Montserrat, DM Sans,
Space Grotesk, Roboto, Playfair Display, Lora — each previewed in its own
typeface, plus an Upload button for your own
.ttf/.otf/.woff/.woff2(max 2 MB, extension + size validated). The chosen font applies to every slide's text and is embedded into PNG exports; a custom font is saved with the project and travels inside the exported.storeshots.json.
Changed
- Cookie consent is now stored in a small first-party cookie
(
storeshots_consent, ~120 bytes) instead of localStorage, so it travels with the request (readable during SSR) and stays minimal. A returning visitor's existing localStorage consent is migrated into the cookie once and the old key is cleared, so nobody is re-prompted.
Fixed
- Android phone mockup now matches modern devices. The frame borrowed the iPhone's aspect ratio (≈9:18) and painted its own fake camera punch-hole on top of the one already in the uploaded screenshot, so captures looked like a different/older device and the cutouts never lined up. The frame now uses a ~9:20 ratio (Pixel/Galaxy) and the painted punch-hole is gone, so the screenshot's real status bar and cutout show through.
0.9.0 — 2026-06-04
Added
- Per-language editing in the editor. Generated languages are now kept in
state (
copyByLocale) instead of being produced and discarded at export time. A language tab bar above the canvas (shown when 2+ languages are selected) lets you switch between languages, see how each one reads, and edit copy inline — every edit is saved per language. A green dot marks languages that already have copy. - Configurable export dialog. A single Export button opens a panel to
pick any combination of languages × devices × sizes and download them as one
ZIP, nested as
<language>/<device>/<size>/NN-label.png(the language folder is omitted when only one language is selected). Store presets (App Store, Play Store, Everything) act as one-click quick-fills for the device/size checkboxes. - Parallel slide capture. Slides within a single device/size frame are now captured concurrently (capped at 3) to speed up large multi-target exports.
Changed
- Language generation is centralized in the sidebar. The sidebar's ✦ Headlines / ✦ Full design buttons now generate copy for every selected language at once (previously only the active one), filling the editor's language tabs. This removes the earlier duplicate "Translate all" trigger and unifies generation under one mental model.
- Export is unified. The separate "Export All", "Bundle", and "Locales" buttons are replaced by the single language-aware Export dialog above.
Fixed
- AI generation no longer fails with
Bad escaped character in JSON. Model responses that contain invalid backslash escapes or raw control characters are now repaired before parsing, across/api/generate-copyand/api/copy-variants. Valid\nline breaks are preserved. - iPhone 6.1″ export size corrected from the retired
1125×2436to1179×2556, which App Store Connect accepts. All four iPhone sizes now match the store's accepted dimensions. - Exports are now the exact selected pixel size. Capture used a 2× pixel
ratio, producing double-sized images (e.g.
1320×2868→2640×5736) that the store rejected; it now renders at 1×. - Uploaded screenshots are no longer dropped on AI generate. When the AI
returned a partial, duplicate, or out-of-range
imageIndexset, some screenshots were reordered tonull. Reordering is now lossless — any uploaded image the AI didn't place is re-homed into a free slot. - Editor no longer crashes after layout/variant edits across languages.
Editing a slide beyond a shorter language's copy length left a sparse array
with
undefinedentries (Cannot read properties of undefined (reading 'label')); copy is now densified on every write.
0.8.1 — 2026-06-03
Fixed
- AI generation no longer fails with
413 Payload Too Large. Uploaded screenshots are now downscaled client-side to a ~1024px JPEG before being sent to/api/generate-copy, cutting the request body ~10–20×. Full-res base64 images previously exceeded the hosting proxy's body limit, so the request was rejected before reaching the handler. Falls back to the original data URL if a canvas resize fails. - AI generation no longer fails with
404 "No endpoints found". The OpenRouter model slugsanthropic/claude-3-5-sonnetandgoogle/gemini-2.0-flash-001, plus the free quick-pick chips (gemini-2.0-flash-exp:free,llama-4-maverick:free,deepseek-chat-v3-0324:free), had been retired by OpenRouter. Replaced the default model, server fallbacks, and sidebar quick-picks with current slugs (anthropic/claude-sonnet-4.6,google/gemini-2.5-flash, and live free vision modelsgemma-4:free,nemotron-vl:free,kimi-k2:free). Removed the retired Claude 3.5 entries from the Claude model picker.
Added
- Comma-separated feature input — the sidebar feature field now accepts several comma-separated features at once, de-duplicating against existing ones, instead of one entry per submit.
Changed
- pnpm build-script allowlist moved from
package.json'spnpmfield topnpm-workspace.yaml(onlyBuiltDependencies), the canonical location for pnpm 11. FixesERR_PNPM_IGNORED_BUILDSabortingnuxt dev's pre-dev install check.
0.8.0 — 2026-05-24
Added
- Layout customization — focused canvas with full transform controls.
Each slide can now be repositioned, resized, and rotated freely instead
of being locked to a fixed template. Closes the most-asked feature
request (issue #5).
- Focused canvas mode — clicking the new ✥ "Edit layout" button on any slide opens a single-slide editor that takes over the canvas area. Thumb strip at the bottom for quick jumping between slides; ESC or the Close button returns to the grid.
- Move drag — grab any device frame or caption to reposition it. Holding Shift locks motion to the dominant axis; release within 2% of canvas center, edges, or the element's original position to snap magnetically. Alt suppresses snap for fine positioning.
- Figma-style resize — devices get 4 aspect-locked corner handles, captions get E/W edge handles. The opposite corner stays fixed while the grabbed handle follows the cursor (the element's anchor migrates on pointerdown so subsequent renders inherit the new pinning). Widths snap to 25/50/75/100% and the original value.
- Free rotation — a ⟳ handle above each element rotates around its center. Holding Shift snaps to 15° increments for clean angles. Available on devices and captions.
- Layout variant picker — focused header has a dropdown of all 10 baseline variants with one-line descriptions. Switching variant drops any element/position overrides since they belong to the old layout.
- Add and remove elements — focused header's "+ Add" dropdown inserts a Device frame, Caption text, or App icon centered on the canvas. Each element gets a red × button on hover to remove it.
- Reset Layout — single header button wipes elements + variant + position back to the slide's default. Always rendered (disabled when there's nothing to reset) so the escape hatch is discoverable.
Internal
- Replaced the 10 hardcoded variant branches in
SlideTemplate.vuewith a data-driven iteration over aSlideElement[]discriminated union (device / caption / blob / icon). The original variants live on asVARIANT_PRESETSincanvas.tsfor backwards compatibility; blobs and backgrounds stay slide-level for this phase since they have no planned per-element UI. - New
useElementOverridecomposable centralizes the read/write bookkeeping: clones the preset on first edit, drops overrides cleanly when they match the preset again, and handles the variant-change cascade. - New
app/utils/anchor.tsexposes the anchor → CSS-sides / transform math shared between the renderer and the transform overlay so positioning stays bit-identical.
0.7.0 — 2026-05-24
Added
- Locale matrix — multi-language export — new "Locales" button in the
toolbar generates AI copy in every selected language and exports each as
a separate folder inside a single ZIP (
/en/,/tr/,/ja/, …). No extra server work needed: the existing/api/generate-copylocale param is called once per language in sequence. - 28-language support — expanded from 13 to 28 languages across AI
copy generation, variant generation, and the sidebar language picker.
New additions: Hindi, Indonesian, Polish, Swedish, Danish, Norwegian,
Finnish, Czech, Romanian, Ukrainian, Vietnamese, Thai, Hungarian,
Brazilian Portuguese (
pt) / European Portuguese (pt-PT), Traditional Chinese (zh-TW). - Unified "Languages" selector — replaced the separate "App language" (single-select) and "Export languages" (multi-select) fields with a single multi-select. First selected language drives the editor preview and AI generate; all selected languages are included in the Locale bundle export.
Fixed
- Dark mode bleed on light-forced sites — inputs and textareas were
rendered with dark backgrounds for OS-dark-mode users even though
colorMode.preference: 'light'was set. Root cause: Tailwind v4 defaults dark variant to@media (prefers-color-scheme: dark)regardless of the@nuxtjs/color-modeclass strategy. Fixed by adding@variant dark (&:is(.dark, .dark *))inmain.cssso dark utilities only activate when the.darkclass is explicitly present.
Improved
copy-variantslocale handling — variant endpoint now maps locale codes to full language names (sameLOCALE_NAMEStable asgenerate-copy) and injects an explicit "ALL output MUST be in [language]" constraint into both system and user prompts, preventing mixed-language output on non-Latin locales.
Internal
- TypeScript strictness fixes in
AppSidebar.vue: array index accesses guarded with!/?? null,FileListindex assertions, andsetSsRefparameter typed asElement | ComponentPublicInstance | nullinstead of implicitany.
0.6.3 — 2026-05-07
Added
- Skip empty slides on bulk export — "Export all" and preset ZIP bundles now skip slides with no screenshot uploaded for the current device, so users no longer ship blank template PNGs to the stores. The trust slide stays in by design (it's intentionally text-only). Single-slide download from a card is unchanged.
- "No screenshot" badge on slide cards so empty slides are visible at a glance before bulk export.
- Toast feedback after bulk runs: how many slides exported, how many skipped, or a clear warning when nothing was eligible.
0.6.2 — 2026-05-07
Improved
- Cross-origin font safety on export — fonts are now inlined via
getFontEmbedCSSbefore capture, removing canvas taint as a failure mode for blank exports. - Transparent and dark designs preserved — the export pipeline no longer forces a white background.
- Sharper exports —
pixelRatio: 2for store-ready resolution. - No more full-screen flash during download — the offscreen capture container now sits off-viewport instead of on top of the page.
- Faster, more reliable capture — 1200ms blanket sleep replaced with
document.fonts.ready+requestAnimationFrame+ a small layout settle.try/finallyguarantees cleanup even when capture throws.
Fixed
- Removed
allowTaint/useCORSopts (those belong tohtml2canvas,html-to-imageignores them). - Restored
cacheBust: trueso stale assets can't re-introduce blank frames. - Reverted the temporary CSP
base-uriblob:addition — it wasn't a meaningful base href source and the underlying console warning is a separate concern.
0.6.1 — 2026-05-07
Fixed
- Blank screenshot exports — downloaded PNGs were sometimes empty.
Capture pipeline rebuilt to clone the export node into a dedicated
offscreen container, wait for fonts, force reflow, and run a robust
warm-up + try/catch with a fallback path. Inter font is preloaded
with
display: swapand Google Fonts getpreconnecthints for faster, more reliable availability. Huge thanks to @wolfhongkong for diagnosing and fixing this in #2.
0.6.0 — 2026-05-06
Added
- Copy variants generator — new "Variants" button in the toolbar
produces three tone-of-voice alternatives of the current slides
(confident · warm · punchy) so the user can A/B test direction
without re-running the vision pipeline. Picking a variant preserves
any per-slide position fine-tunes. Backed by
/api/copy-variants. - Auto palette from screenshots — "Auto" wand button on the Style step extracts a brand palette (primary + accent + bg gradient) from the uploaded screenshots via a tiny client-side colour quantiser. No AI key required. Skips near-white / near-black / near-grey buckets so the result reflects real brand colour, not background.
- Claude model picker — Provider step now exposes a model dropdown when Claude is selected: Opus 4.7, Sonnet 4.6, Haiku 4.5, plus the older 3.5 Sonnet / 3.5 Haiku. Choice persists per project and is passed through to every AI endpoint.
Changed
- Sidebar layout unified across every step. Each wizard step now
follows the same shape: a
<header>with a title, optional right action, and a one-sentence description; field blocks with a consistenttext-xs font-semiboldlabel andtext-[10px]helper; every input forced tow-fullso widths line up flush with the 4 px section padding. - AI step's two stacked sub-panels (Generate + Settings) now read as one cohesive section with a clear "AI generation" intro followed by a "Provider & key" sub-section.
0.5.0 — 2026-05-06
Added
- Quick-start templates — a chooser auto-opens on a fresh editor and is reachable any time from the new "Templates" button. Four presets (SaaS, Game, Finance, Health) pre-fill colours, slide copy, and key features. User assets (icon, screenshots, AI key) are never overwritten.
- App Store preview — a "Store preview" button (active on iPhone) opens a modal that simulates the listing row users see first: app icon + name + tagline + the first 3 slides at iOS-row aspect ratio.
- Sticky thumbnails strip above the slide grid, one chip per slide. Click a chip to jump the preview to that slide.
- Empty-state hint — when no screenshots have been uploaded yet, a banner above the slide grid points the user back to the Screenshots step instead of leaving them confused by placeholder previews.
- Keyboard shortcuts —
⌘/Ctrl + Eexports all,⌘/Ctrl + Ggenerates copy via AI,Esccloses the slide-edit modal. All shortcuts skip while typing in form fields.
0.4.0 — 2026-05-06
Added
- Wizard sidebar — sticky 5-step nav (Basics · Screenshots · AI · Headlines · Style) replaces the single-scroll dense sidebar. Only the active step renders; a green dot marks completion per step heuristically.
- Basics step polish — full-width icon dropzone with hover/preview/✕, consistent inputs, description character counter (/600), and features as a chip input (press Enter or comma to add, ✕ to remove).
- Inline slide edit — pencil button on each preview card opens a modal with label + headline fields. No more scrolling the sidebar to find slide N.
- Caption fine-tune drag — new "Adjust" button on each slide enters a drag-to-reposition mode. Pointer deltas are divided by the preview scale to produce true canvas-pixel offsets, so the adjustment carries through to bundle exports. Save / Reset / Cancel toolbar inside the card.
Changed
- Toolbar split into two rows — Row 1: Home · App name · Project · Export · Bundle. Row 2: Device tabs · Orientation · Size. Frees the action area, removes overflow on narrow viewports.
- Slide cards no longer click-to-download — the entire card was a download trigger, which produced accidental downloads when clicking near the slide. Three explicit hover buttons now: ✏ Edit, ✥ Adjust, ⬇ Download. The Feature Graphic preview gets an explicit Download button too instead of being a giant click target.
0.3.0 — 2026-05-06
Added
- Export bundles — a new "Bundle" dropdown next to Export All produces
every size a target store accepts in a single ZIP. Three presets:
- App Store: iPhone 6.9", 6.5", 6.3", 6.1" + iPad Pro 12.9", 11"
- Play Store: Android Phone, 7" Tablet, 10" Tablet
- Everything: union of both
The flow walks each device + size, captures every slide, and downloads a
ZIP organised by
device-sizefolders. Original device state is restored after the run.
- Project save / load — a "Project" dropdown with two actions:
- Save project → downloads a versioned
.storeshots.jsonsnapshot. The file format is versioned (__storeshots: 1) so a future schema change can refuse incompatible files cleanly. - Load project → restores the editor from a snapshot. The exported file strips the API key so a shared snapshot can never leak credentials; on import the in-memory key is preserved so the user doesn't re-paste.
- Save project → downloads a versioned
0.2.2 — 2026-05-06
Fixed
/changelogwas crashing on Netlify Functions withrequire() of ES Module @exodus/bytes/encoding-lite.js not supported, bubbling up fromisomorphic-dompurify's JSDOM dependency. Replaced the sanitiser with jagajs — zero dependencies, dual ESM/CJS exports, serverless-safe.- The page is now prerendered at build time (
routeRules: { '/changelog': { prerender: true } }), so Netlify serves it as static HTML and the Functions runtime never touches it.
0.2.1 — 2026-05-05
Added
- Update banner — when a new deploy goes live, open tabs detect the change
by polling
/api/version(every 5 min and on tab focus) and surface a "Refresh" prompt instead of silently running stale assets that 404. - Explicit Netlify cache headers:
_nuxt/*isimmutablefor a year, HTML is always revalidated. Prevents the previous failure mode where a cached HTML pointed at a hashed CSS file that the new deploy had already invalidated. buildIdnow prefers Netlify'sCOMMIT_REF(git SHA) over a build-time timestamp, so the same commit redeployed produces the same id and won't trigger a spurious refresh prompt.
Security
- CSP hardened: enabled per-request nonce via
nuxt-securityand removed'unsafe-inline'fromscript-src. Hydration scripts now carry an explicit nonce;'strict-dynamic'allows scripts loaded by trusted entry chunks (e.g. Umami) without widening the host allowlist.'unsafe-eval'remains pending a vendor audit. - Changelog page sanitises rendered Markdown with
isomorphic-dompurifyas a defence-in-depth layer, so a malicious PR landing HTML inCHANGELOG.mdcannot escalate to stored XSS. /api/generate-copyinput hardening: enforced an 8MB body cap, typed and length-bounded every field (provider must beclaudeoropenrouter,slideCount1–12, max 12 images, 1.5MB per image), and rejected unrecognised payloads early with413/400.featuresis correctly typed asstring[](was briefly mis-typed during development).
Fixed
- AI generation surfaced silent console errors when API keys were missing or
the upstream provider rejected the request. Errors now appear as user-facing
toasts via
useToast()(Nuxt UI 4), backed by an<UApp>root wrapper.
0.2.0 — 2026-05-05
Added
- Public changelog:
CHANGELOG.mdat the repo root and a rendered/changelogpage on the site, both kept in sync. Linked from the landing page header, landing footer, and the legal layout footer. - Umami analytics — privacy-first, cookieless, MIT-licensed, GDPR-safe by
default. Gated behind the
analyticscookie-consent toggle viauseScriptTriggerConsent()(the canonical @nuxt/scripts v1 consent primitive), so opting out blocks any third-party request. Configured viaNUXT_PUBLIC_UMAMI_WEBSITE_ID/NUXT_PUBLIC_UMAMI_HOST(defaults to Umami Cloud, override for self-host).
Changed
- Replaced Google Analytics 4 + Google Tag Manager with Umami. The earlier
GA/GTM integration (added and removed in this same release window) was
inconsistent with Storeshots' privacy stance: an open-source tool that holds
API keys in
sessionStorageand respects Do Not Track shouldn't ship its visitors' page views to Google. Umami is cookieless and stores no personal data, so the analytics flow is now fully aligned with the rest of the product. - CSP narrowed: removed
googletagmanager.comandgoogle-analytics.com, added onlycloud.umami.isand Umami's API gateway. - Cookie banner copy for the "Analytics" category updated to reflect the new cookieless reality.
Fixed
- Screenshot export coming out blank in production. The CSP
base-uri 'none'default blocked the<base>tag thathtml-to-imageinjects into its cloned DOM; relaxed to'self'. (#1)
0.1.0 — 2026-04-23
Initial public beta.
Added
- Marketing landing page with dual-platform (App Store + Google Play) hero preview.
- Editor at
/editorfor composing slides with device mockups. - AI-assisted copywriting via OpenRouter (key held in sessionStorage only).
- Smart slide ordering and export through
html-to-image. - Legal pages: Privacy, Cookies, Terms; cookie consent banner with granular categories (necessary, functional, analytics, marketing).
- SEO scaffold via
@nuxtjs/seo: sitemap, robots.txt with AI-crawler allow/deny lists, Open Graph and Twitter cards, schema.org Organization markup. - Security headers via
nuxt-securitywith a tight CSP and rate limiting on server routes. - Mobile warning overlay for screen widths the editor does not yet support.
- AGPL-3.0-or-later license.