{"id":"69e5403cb792b3cd096abf38","title":"2026 04 19 Build 617","path":"how-to/committee/release-notes/2026-04-19-build-617","contentMarkdown":"# 19-Apr-2026 — focal-point picker, save flow, screenshot fidelity [#243](https://github.com/nbarrett/ngx-ramblers/issues/243)\n\n## [build 617](https://github.com/nbarrett/ngx-ramblers/actions/runs/24638531312) — [commit 1a05985](https://github.com/nbarrett/ngx-ramblers/commit/1a059852da3a1b7c1947e7722ce6b85f628cd4d6)\n\n_____\n\n### Photo screenshot fidelity (originally #243):\n\nThe banner editor's saved/exported screenshot stretched the photo bitmap\nbecause html2canvas (via ngx-capture) does not honour `object-fit: cover`\non `<img>` elements, while the live preview does (a global\n`img { object-fit: cover }` rule applies). The captured PNG embedded in\nbooking confirmation emails therefore showed the photo distorted, even\nthough the editable preview looked correct.\n\nRender the photo as a `<div>` with `background-image` and\n`background-size: cover` instead. html2canvas handles CSS background\nimages correctly, so the captured screenshot now matches the live\npreview pixel-for-pixel. The cropper transform (scale + translate) and\nright/bottom anchoring are preserved.\n\nAlso moves the `(load)=\"setPaperCutImageHeight()\"` trigger onto the\nbackground image (the actual source of the height measurement) and\ndrops a stray empty `<img/>` left over from the original markup.\n\n#### Banner editor:\n- Photo focal-point picker driven by FocalPoint (x/y/zoom) on Image, with the\npicker's resizable handle controlling editableBanner.bannerHeight (200-900px).\nReplaces the fixed 42.1% padding-bottom with explicit height.px when set.\n- Photo URL uses imageSource(..., true) to skip /admin/ prefixing.\n- New Edit Saved Image button (with disabled \"Not Saved Yet\" tooltip when no\nfileNameData) that opens app-image-cropper-and-resizer preloaded with the\nsaved banner PNG. On save, fileNameData.awsFileName is updated and the\nbanner is re-saved.\n- Banner select uses [compareWith] so it stays bound to the canonical record\nafter a save swaps the response banner back into the list by id.\n- URL sync via StoredValue.BANNER (\"banner\") query param using kebab-case slug;\nreads on init, writes on bannerSelected via router.navigate({...}, merge).\n- Page title shows \"Banners\n- {ordinal} of {total}\".\n- Saved image summary (\"Saved image: W x H px, NN kb.\") on its own row\nbeneath the action buttons, populated via a hidden img + fetch(blob.size)\nwhether or not the saved preview is toggled. Resets on banner switch\nand on save.\n- Save flow now sets notify busy=true so the alert shows a spinning faSpinner\nduring the upload, replacing the static success icon until notify.hide().\n- Cache-busting via savedImageVersion timestamp, and the saved view auto-\ntoggles after a save.\n- normaliseBanner strips any rootFolder/ prefix doubled into awsFileName so\nlegacy persisted state still resolves; savedImagedSaved strips the prefix\non the way back from the cropper to prevent the doubling at source. The\nprefix stripper is implemented as a small recursive helper to keep\nno-restricted-syntax (no imperative loops) happy.\n- HostListener onResize signature trimmed to drop the unused $event param.\n\n#### Image cropper:\n- Default cropper position no longer forced from natural-pixel coordinates;\nwhen no saved cropperPosition is supplied the cropper component handles its\nown full-bounds default in display pixels (avoids the giant rectangle when\nnatural >> display).\n- 12px padding on the cropper host via ::ng-deep so the corner/edge resize\nhandles render fully inside the host's overflow:hidden region instead of\nbeing clipped at the right and bottom edges.","contentHtml":"<h1>19-Apr-2026 — focal-point picker, save flow, screenshot fidelity <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/243\">#243</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/24638531312\">build 617</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/1a059852da3a1b7c1947e7722ce6b85f628cd4d6\">commit 1a05985</a></h2>\n<hr>\n<h3>Photo screenshot fidelity (originally #243):</h3>\n<p>The banner editor&#39;s saved/exported screenshot stretched the photo bitmap\nbecause html2canvas (via ngx-capture) does not honour <code>object-fit: cover</code>\non <code>&lt;img&gt;</code> elements, while the live preview does (a global\n<code>img { object-fit: cover }</code> rule applies). The captured PNG embedded in\nbooking confirmation emails therefore showed the photo distorted, even\nthough the editable preview looked correct.</p>\n<p>Render the photo as a <code>&lt;div&gt;</code> with <code>background-image</code> and\n<code>background-size: cover</code> instead. html2canvas handles CSS background\nimages correctly, so the captured screenshot now matches the live\npreview pixel-for-pixel. The cropper transform (scale + translate) and\nright/bottom anchoring are preserved.</p>\n<p>Also moves the <code>(load)=&quot;setPaperCutImageHeight()&quot;</code> trigger onto the\nbackground image (the actual source of the height measurement) and\ndrops a stray empty <code>&lt;img/&gt;</code> left over from the original markup.</p>\n<h4>Banner editor:</h4>\n<ul>\n<li>Photo focal-point picker driven by FocalPoint (x/y/zoom) on Image, with the\npicker&#39;s resizable handle controlling editableBanner.bannerHeight (200-900px).\nReplaces the fixed 42.1% padding-bottom with explicit height.px when set.</li>\n<li>Photo URL uses imageSource(..., true) to skip /admin/ prefixing.</li>\n<li>New Edit Saved Image button (with disabled &quot;Not Saved Yet&quot; tooltip when no\nfileNameData) that opens app-image-cropper-and-resizer preloaded with the\nsaved banner PNG. On save, fileNameData.awsFileName is updated and the\nbanner is re-saved.</li>\n<li>Banner select uses [compareWith] so it stays bound to the canonical record\nafter a save swaps the response banner back into the list by id.</li>\n<li>URL sync via StoredValue.BANNER (&quot;banner&quot;) query param using kebab-case slug;\nreads on init, writes on bannerSelected via router.navigate({...}, merge).</li>\n<li>Page title shows &quot;Banners</li>\n<li>{ordinal} of {total}&quot;.</li>\n<li>Saved image summary (&quot;Saved image: W x H px, NN kb.&quot;) on its own row\nbeneath the action buttons, populated via a hidden img + fetch(blob.size)\nwhether or not the saved preview is toggled. Resets on banner switch\nand on save.</li>\n<li>Save flow now sets notify busy=true so the alert shows a spinning faSpinner\nduring the upload, replacing the static success icon until notify.hide().</li>\n<li>Cache-busting via savedImageVersion timestamp, and the saved view auto-\ntoggles after a save.</li>\n<li>normaliseBanner strips any rootFolder/ prefix doubled into awsFileName so\nlegacy persisted state still resolves; savedImagedSaved strips the prefix\non the way back from the cropper to prevent the doubling at source. The\nprefix stripper is implemented as a small recursive helper to keep\nno-restricted-syntax (no imperative loops) happy.</li>\n<li>HostListener onResize signature trimmed to drop the unused $event param.</li>\n</ul>\n<h4>Image cropper:</h4>\n<ul>\n<li>Default cropper position no longer forced from natural-pixel coordinates;\nwhen no saved cropperPosition is supplied the cropper component handles its\nown full-bounds default in display pixels (avoids the giant rectangle when\nnatural &gt;&gt; display).</li>\n<li>12px padding on the cropper host via ::ng-deep so the corner/edge resize\nhandles render fully inside the host&#39;s overflow:hidden region instead of\nbeing clipped at the right and bottom edges.</li>\n</ul>\n"}