Conversation
Cache the handle element before React invalidates the synthetic event's currentTarget, preventing a TypeError when the pointermove listener is removed on pointerup/pointercancel.
The video elements in SingleFrameView and FrameInstanceGrid hardcoded object-fit: contain, overriding the layer's mediaFit (cover/fill). Remove the override so video respects the fill/fit setting like images.
Start video playback and wait for an actually-presented frame before the MediaRecorder captures the canvas stream, and repaint the canvas before recorder.start(). readyState >= 2 alone wasn't enough (it can be satisfied by the pre-load position), so drawImage rendered blank/black frames at the head of the recording.
…coding The capture loop opened the first chunk with the pre-media warm-up frame: captureStream() samples on its own cadence, and requestFrame() is Chromium-only, so the blank lead survived on Safari/Firefox. Paint the finally-presented media, force a stream frame where possible, and wait one sampling period before starting the recorder. Also wait for real playback (clock advancing past the poster frame) on the active layer and every frame-instance video, and keep the capture canvas composited with a tiny non-zero opacity. Quality was capped by the browser's low default intermediate bitrate: record a clean 16 Mbps VP8/WebM stepping stone and let the H.264 pass set the real quality via CRF (26/20/16). Encode was slow because the WASM x264 is single-threaded; cap the auto-sized canvas long edge at 1440px and use the ultrafast preset, cutting an 18s/3-frame export from ~4 minutes to ~2.
…r uk media string
…rrent test counts
The config uses top-level import/defineConfig, so Vite loads it as CommonJS unless the file is ESM (.mts). Rename and switch __dirname to import.meta.dirname; update doc references.
The sitemap already advertises every locale sibling, but the [locale] pages didn't. Add alternates.languages mirroring sitemap plus a canonical URL, and drop two dead messages.errors locals.
Exports: - even-out video capture dimensions + -vf scale guard so default MP4 export (1200x675) no longer fails libx264/yuv420p - exportImage/Jpeg/Webp/Avif/Pdf return success; "Exported" toast no longer masks failures - aborted video/batch exports no longer surface an error toast; batch MP4 loop purges ffmpeg temp files on failure - PDF page uses customSize verbatim so the raster fallback is not stretched when aspects differ - SVG video scene always snapshots the poster frame (parity with PNG); crossOrigin set before src in all three detached-element spots - downloadBlob revokes after 5s to survive large-blob handoffs - multi-frame transparent scenes stay transparent (no white wash) Renderers: - landscape instances draw skin/body/shadow/URL in native orientation inside the rotated context (canvas + SVG + tilt quad), matching the preview rotor; tiltMatrixSvg now maps non-origin rects correctly - text layers map "middle" to canvas "center" instead of a silently dropped invalid value - annotation gradients use the CSS angle convention (sin, -cos) and farthest-corner radial radius; SVG radial uses userSpaceOnUse - arrow annotations stroke with the configured width and round caps - watermark never draws literal "null" when the logo fails to load State: - recent-media persistence drops oversized data URLs instead of rehydrating truncated (invalid) ones onto layers - setMedia on a locked layer no longer records a recent-media entry - removing a frame instance/layer re-points activeFrameInstanceId and selectedFrameIds instead of leaving dangling ids - toggleLayerSelected keeps the active layer inside the selection - normalizeScene.num treats null/false/"" as missing (fallback), not 0 Components/hooks: - ContextMenu resize listener actually removes itself (leak + focus steal) - pinch baseline clears on touchcancel so stale gestures can't hijack later two-finger touches - RecentMediaGrid defers to after mount (zustand persist hydration) - screen recording labels the blob with the recorder's real mimetype - custom frame ids get a random suffix; EditorShell drops the orphan focus trap; command list memo no longer rebuilt every render
…and API High: - Annotation inline editing: Backspace/Delete while typing no longer deletes the annotation (stopPropagation in contentEditable + canvas contentEditable guard); shortcuts and arrows no longer fire or move frames while editing text - Single-frame exports (PNG/JPEG/WebP/AVIF/video/worker) now render every visible layer's media with per-layer fit/filters/opacity/blend, matching the preview's stacked media slot; spin API video scenes get their poster frame instead of an empty screen - spin API: request body is streamed with a hard 40MB cap before parsing, non-numeric seeds are rejected, a failed Chromium launch is no longer cached forever, the in-page render got a timeout and the PNG cache a byte budget - Shortcut rebinding now conflicts with fixed combos (mod+v, mod+k, r/f) - HTML export: arrow stroke width scaled to viewBox units, circle annotations render as circles, multi-frame videos keep their mediaStyle and mediaFit - blendMode is applied by every export path (canvas, SVG, HTML) - Screen recording: a second start during the picker dialog can no longer leak an orphaned capture session Medium: - False "Unsaved" toast on project switch, shared videoRef scrubbing the wrong clip, pan gesture hijacking native video controls - SVG export keeps the screen glare when tilted or landscape-rotated; radial background gradient matches the CSS farthest-corner geometry across canvas/SVG/preview; watermark paints above annotations - Safari WebM downloads use the actual container extension; frame skins are keyed per instance so materials survive; every focus-trapped dialog parks global shortcuts; palette "Add layer" validates files; stuck media spinner clears when its layer hides; palette cache is LRU-capped; starting an export supersedes a running one and ZIP exports are cancellable
…ollapse, tablet nav, multi-delete - Reset action now returns a blank canvas (buildEmptyScene) instead of re-seeding the demo grid; confirm copy updated across locales - Mobile/tablet (<=980px): undo moves to the bottom tab bar (with step count badge), secondary toolbar actions fold behind a '...' overflow menu (theme/share/commands/shortcuts/reset/fullscreen); toolbar stays one row. The 769-980px band now shares the bottom-sheet navigation instead of burying panels below the fold - ContextMenu: checked option marker + triggerRef so clicking the menu button toggles instead of close-reopen - ControlPanel search filter, ArrangeSection split, PositionSection scope labels, ExportDialog backdrop no-op while busy, distinct reset/shortcuts icons (from earlier in this pass) - PreviewCanvas Delete/Backspace removes all selected annotations via new one-undo-step removeAnnotations(ids) - Tests: +15 unit/component (2402 total), stale Layers->Panels e2e selectors repaired, axe audit fixed to disableRules (withRules narrowed the scan instead of excluding), a11y mobile overflow fallback, VRT baselines re-shot (9 pngs)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Tour dialog title was a heading that broke heading order on mobile (hidden Controls panel caused an h1->h3 jump); keep the visual style with a non-heading element since the dialog already carries an aria-label. Silence the remaining advisory Lighthouse 12 insight audits alongside their siblings.
- Add deviceChrome.ts: per-frame ChromeSpec metrics (status bar, dock, home indicator) for iPhone 14, iPhone 15, iPhone 16 Pro, Pixel 8 Pro, Galaxy 24, iPhone SE, iPad, Galaxy Tab, desktop, watch. - Rewrite screenChrome.ts: superellipse squircle dock icons, spec-driven status bar/clock/indicator positioning, lock-screen notification cards (Messages/Calendar) with showNotifications toggle. - Thread frame parameter through all three renderers (mockupRenderer, svgMarkup, canvasDrawing) for per-device chrome layout. - Add showNotifications to ScreenChrome type, normalizeScreenChrome, and ScreenControls toggle (en/ru). 2432 tests, typecheck, lint clean.
…nd folders Android home screen - editable 4-6 row x 3-5 col app grid (gridCols/gridRows), labeled icon presets (google/classic/minimal/none) and per-icon editor (label/color/emoji) - custom dock icons now honored on Android docks as well (regression fixed) - folder cells occupy trailing grid slots (stacked-tile stub + label) - clock/weather widget cards reserve top grid rows and shift icons down Renderer - dedup grid geometry into shared androidGridGeom() used by SVG and canvas - guard short screens (fits=false) instead of negative radii - NOTIFICATION_APPS is the single default source across store/render/UI UX - ScreenControls detail groups moved to the shared Section accordion (localStorage-persisted, aria-controls); 6 sections, collapsed by default - grid editor shows first 4 rows with "Show all 20 icons" reveal - per-field reset buttons replaced by one conditional "Reset all customizations" - active grid preset marked via aria-pressed; panel search keywords cover folder/widget/grid/dock/notification (en+ru) i18n: new keys in en+ru, 55 locales synced
- define missing --success token (dark #4ade80 / light #16a34a); used by toolbar saved status and EditorToolbar save-state text - replace undefined token references: --panel-bg → --field-bg (notification-row text inputs), --border → --panel-border (Animation/ Media sections), --text-primary → --text (ContextMenu) - theme-aware toggle track via var(--range-track) instead of hardcoded white overlay (invisible in light theme) - shared .color-input class replaces duplicated inline COLOR_INPUT_STYLE in ScreenControls/BackgroundModeControls and styles previously unstyled AnnotationEditor swatches; adds the standard accent focus ring - shared .color-input-row replaces inline flex/label rows, aligning BackgroundModeControls font size with the rest of the panel - 12x12 stroke icons for the six ScreenControls sections; drop redundant .grid-dims-row span override
…onents - extend tokens: --accent-hover/--accent-contrast, --radius-xs, --overlay, --shimmer, --brand-violet, z-index ladder (--z-modal..--z-skip) - replace hardcoded accent tints/glows with color-mix(var(--accent)) so light theme no longer shows dark-theme cyan - fix light-theme regressions: shimmer visibility, toggle knob contrast, skip-link/badge text via --accent-contrast - normalize radii (4/6/7/9px -> --radius-xs/--radius-sm), disabled opacity (0.3/0.35/0.45 -> 0.4), overlay opacities, spinner and modal timings - migrate ad-hoc overlays to the shared modal system (ShareQrDialog, OnboardingTour backdrop, PwaRegister toast) and inline copies to shared classes (.toast-status, .field-error, .text-dim-sm, .btn-sm) - drop dead .btn-ghost, legacy --text-secondary and #4f8cff fallback - refresh VRT baselines for the normalized visuals
- make the themed range slider the default for all input[type=range]; .range-wrap (compact) and input.trim-range (dual thumb) keep their variants - style the native color-input swatch as a themed field chip (field-bg/field-border, neutralized webkit/moz swatch chrome) - cover input[type=url] by the canonical .field input styling and move the template-name field to .template-save-row rules instead of inline copies - define the previously missing .btn-link (ScreenControls text actions) and .group-icon-btn; replace copy-pasted button inline styles with .btn-compact/.btn-flex/.btn-block modifiers - background section: gradient linear/radial toggles now use the canonical Segmented control, media palette swatches reuse .swatch-btn (hover, focus ring, ring-based active state) and preset active state comes from aria-pressed CSS instead of an inline 2px border - unify disabled opacity at 0.4 (ContextMenu) and add .check-label - update component test for the Segmented gradient-type control
- add missing base definitions for .field-hint (used bare outside its two scope wrappers) and style .field textarea via the shared field selector - global ::placeholder color; drop the inline resize on the text textarea - fix ScreenControls add-folder button using .btn-sm without the base .btn - ErrorBoundary fallback reuses .empty-state classes with a .btn.btn-sm retry - enlarge touch-target thumbs for default range sliders on coarse pointers - FrameInstanceList layer label reuses .range-label instead of inline copies
…e frame
The Position section's zoom slider sat in the media-scope group (fit/pan/
rotation act on the media inside the device), but it scaled the whole
mockup — a leftover of the frame-zoom model. New semantics, consistent
across every renderer (CSS preview, canvas, SVG, HTML, video):
- layer.zoom scales the media inside the screen about the screen center;
the device/bezel stays put. Range widened to 80-300% (shared LAYER_ZOOM
constant used by the slider, pinch gesture and normalizeScene clamp)
- animation presets keep whole-frame motion: useFrameTransform's static
branch pins identity, sampleVideoTransform('none') returns zoom 1,
frame-box scaling (computeFrameBox/Instances, shadow) consumes only the
sampled animation transform
- media slot: new untransformed clipping frame over the screen in
buildSceneCss (mediaSlotStyle); media elements live inside it so
rotate/scale can't spill over the bezel — the CSS counterpart of the
exporters' clipScreen/SVG clip group. Blend moves onto the slot because
a clip-path slot creates a stacking context and would isolate a child
mix-blend-mode
- post-zoom pan: transform chain becomes rotate -> translate(offset *
(zoom - 1) * 50%) -> scale, mirrored in drawMediaSource and svgMarkup,
so the fitted axis (landscape media on a portrait screen) can pan once
zoomed; total pan provably stays within cover bounds (no gaps)
- canvas paints RENDER.mediaBackdrop behind the media (matches the CSS
media background when zoom < 100% or contain fit leaves screen uncovered)
- htmlMarkup/OG wrap media in the slot; pinch-zoom clamps via LAYER_ZOOM
- tests updated for the new model (frame box unchanged by static zoom,
identity export transform for static scenes, slot geometry assertions)
Regenerated with npm run test:vrt:update against the production build. Picks up the enlarged preview zoom targets (WCAG target-size fix) and drops a stale bottom-left artifact baked into the previous captures that no build renders anymore.
Statuses (toasts, export progress) lived inline between button groups, shifting actions and wrapping the toolbar. They now render in an always-mounted middle slot with ellipsis clipping. Undo/redo counters get a readable badge style, hit targets grow to 32px, and secondary actions fold behind overflow from 1180px so narrow desktops never clip.
Add per-format hints (en+ru, synced to all locales), a live custom-size summary with aspect and megapixels, and a notice that platform presets adjust the canvas aspect. The dialog no longer traps the user while an export runs: Esc/backdrop/close always dismiss without cancelling, progress and cancel stay in the toolbar.
…ns on top Migrate 0-instance legacy scenes to one centered instance on load, keep overlays above the single-frame device, always show frames list, and center the first added frame instead of scale-1 offset clone.
Drop the stale centering transform once the card is measured, auto-open the panel bottom sheet on mobile steps (restoring the prior sheet on finish), re-measure after the sheet transition and glide the card.
Replace the three scattered canvas-corner chip stacks (upload/clear, zoom, grid) with one PreviewDockBar pinned to the panel bottom: it never covers the artwork, never collides on narrow canvases and never shifts with the scene aspect ratio. Upload stays next to Clear in multi-frame mode (one-click replace), the zoom % is separated from Fit, and canvas screenshots stay chrome-free. Below 480px the dock compacts to icons with a smaller slider so every control stays reachable.
Five labeled tabs never fit the 276-310px side panel and truncated to stubs. Below 480px of panel width (side panels, phone sheets) tabs show icons with tooltips; wider sheets keep labels. Explicit aria-labels keep accessible names stable for tests and screen readers.
Ten undifferentiated accordions now render under four labeled groups (Content, Device, Adjust, Scene) in novice-friendly order. Section ids, defaults and prefs are untouched; the filter also matches group labels so a task-level query opens every member section.
Count badges became aria-hidden with the icon-tab refactor, dropping e.g. 'Layers, 2' to just 'Layers'. Badges are now referenced via aria-describedby while names stay stable labels.
The dock toolbar had a single Tab stop pattern missing: arrows now move between controls (Home/End jump to the ends) while sliders and selects keep native arrow behavior. Hidden controls are skipped via verified focus steps; the programmatic-only file input never joins the walk.
Switching aspect ratios left multi-frame scenes tiny since instance scales belonged to the old ratio. New undoable Fit to canvas action (Arrange section + command palette) reflows existing instances onto a grid for the current ratio, preserving frames and layer bindings.
Sheets drop from 72dvh to 52dvh with a lighter backdrop so the preview stays visible and live while adjusting controls. Below 360px the dock also hides its steppers and shrinks the slider (iPhone SE fits).
Viewport breakpoints missed narrow panels at wide viewports (landscape phones, slim desktop columns), overflowing the dock with no scroll affordance. A .preview-body inline-size container now scopes the compact/ultra dock rules to the actual panel width; it also stops the shrink-wrapped dock from blowing the panel wider than its grid track.
Stacked layout below 980px and at least 600px of height uses a fixed viewport shell with a stretching preview row (no dead void on tablets). Shorter screens keep the original scrolling rules. Also moves the preview column min-height from an EditorShell inline style (which beat every stylesheet rule) into desktop CSS.
Five icon buttons left the project name an unreadable stub. Rename stays inline; duplicate/export/bundle/delete move behind a row-level more-actions menu reusing the shared ContextMenu.
Long unbroken template/project names no longer force grid tracks wider via min-width chains (wrap anywhere / ellipsis hold). Icon rows in Arrange and frame-card heads wrap instead of clipping. Hidden tooltips no longer contribute their box to ancestor scrollable overflow.
Video exports on headless/SwiftShader runners died as "Recording produced no frames." Three stacked causes, each verified by instrumenting the live recording: - the 16Mbps intermediate bitrate (126acd3) starved the software VP8 encoder; back to 2.5Mbps, which it keeps up with on any machine - a fixed-position capture canvas (semi-transparent or behind the opaque page background) received no compositor frames at square/portrait sizes; the canvas is now an in-flow, opaque, absolutely positioned element behind the app UI - capture is now explicit: captureStream(0) + requestFrame() per painted tick, with a 1px getImageData readback before each snapshot to force synchronous 2D rasterization — SwiftShader rasterizes lazily and a bare requestFrame() snapshotted a pending buffer, so the encoder produced zero data even though pixels provably changed on the canvas
- Clear-media chip moved into the dock bar (dae6ec9), a sibling of #preview-canvas — scope the locator to that toolbar - since the media-slot model (8d53bce) both preview and export paint a #0a0a0a backdrop behind media, so the transparent-screen shadow test now asserts dark, not background-bright - parity spec measured the preview box before hiding editor chrome, and hiding chips reflows the canvas: the stale clip stretched the preview and inflated the diff 0.136 vs 0.06. Measure after the style tag and allow 60/channel for the irreducible cross-rasterizer AA residual - "Undo" matched "Fit to canvas"'s title (undoable); use exact Undo (⌘Z)/Redo (⇧⌘Z) names - tab labels hide in compact icon mode (650a385); assert on the tab role's accessible name instead of visible text
…ty guards - .toast-status now renders as a floating pill anchored to the toolbar: the nowrap/overflow-hidden toolbar (4098dfa) collapsed its flexible middle slot at 1280px, clipping the toast to zero width and hiding it from Playwright while clipboard copy actually succeeded - component tests: save-toast asserts role/class; lock in the dock-bar-outside-#preview-canvas structure the e2e relies on - unit guards against renderer drift: media backdrop color/rect parity with buildSceneCss, and screen-chrome geometry parity between canvas, SVG and the CSS cutout fractions
The darwin VRT baselines broke on CI with ~928px of font-rasterization noise (0.07%) against an 80px budget: - pin visual-regression and og-image to macos-26: macos-latest migrated 15→26 mid-2026 and silently invalidates darwin baselines - VRT thresholds switch to maxDiffPixelRatio (0.005 full-scene, 0.002 dialogs) — scale-aware and an order of magnitude above AA noise while still catching real layout regressions; waitForStable awaits document.fonts.ready instead of a blind timeout - OG freshness job replaces byte-exact git diff with scripts/check-og.mjs (zero-dep PNG pixel diff, 0.2% budget): real staleness measures 0.5%, cross-version font noise ~0.07%; og-image.png regenerated to match the current frame geometry
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.