Skip to content

fix: support non-16:9 custom templates (4:3 etc.) and fix two pre-existing live preview issues - #283

Merged
ShotaroKataoka merged 4 commits into
mainfrom
fix/aspect-ratio-agnostic-canvas-208
Aug 5, 2026
Merged

fix: support non-16:9 custom templates (4:3 etc.) and fix two pre-existing live preview issues#283
ShotaroKataoka merged 4 commits into
mainfrom
fix/aspect-ratio-agnostic-canvas-208

Conversation

@ShotaroKataoka

@ShotaroKataoka ShotaroKataoka commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #208

Summary

Fixes layout breakage with custom templates whose slide size is not 16:9 (e.g. 4:3).
Also resolves two pre-existing live-preview problems that surfaced while working on it.

Background: this is not a new coordinate system, it is an alignment fix

The engine's px coordinate system already had a correct basis — 1920 px fixed
width, height following the aspect ratio
— implemented in builder and
converter (EMU_PER_PX = slide_width / 1920). A 4:3 template's real canvas is
therefore 1920×1440 px.

The problem was that every other layer hardcoded a height of 1080. So this PR
does not introduce a new coordinate system; it aligns the remaining layers with
the basis that already existed.

Symptom in the issue Root cause
Content sits at the top, large empty area at the bottom (main symptom) Knowledge layer. references/ teaches "canvas is 1920×1080, drawing area y=173–950", so the model never designs below y=1080
Elements that fit are reported as out-of-bounds Hardcoded height bound in lint
Element positions drift from template placeholders analyzer converted to px with a fixed emu=6350 (reporting 1440×1080 for 4:3)
Text overflow detection is inaccurate measure always scaled the SVG viewBox to 1080
Preview is cropped top and bottom in the Web UI aspect-[16/9] frame + object-cover crops ~25% vertically

Note that lint is not involved in the main symptom — the model never attempts
y>1080 in the first place, so it is never pushed back by lint. The false
out-of-bounds warning is both an independent symptom and a landmine that only
detonates once the knowledge layer is fixed
. That is why the implementation
order is fixed as engine layer → knowledge layer.

Changes

Engine (8243368f)

  • Added slide_size_px() / emu_per_px() to sdpm.engine and replaced the
    inline computations in builder and converter (behaviour unchanged)
  • analyzer: dropped the fixed emu=6350 in favour of the 1920-width basis
  • lint: removed the height out-of-bounds check, making lint independent of
    slide size
    • Rationale is a cost asymmetry: a false positive actively misleads the agent
      into breaking the layout, while a false negative is caught by the
      generate-time check and by preview inspection
    • The width check is a constant that is always correct, so it stays
    • As a result, both the lint(data, slide_size=...) API extension and the
      cache-staleness problem on the remote save path (where the template binary is
      unreachable) disappeared from the design entirely
  • generate: added a height-bound warning based on the template's real
    dimensions (zero false positives) and a slideSize mismatch warning
  • deck.json: the engine writes slideSize only when creating a new deck
    (api.init / pptx import); it never updates an existing deck.json.
    deck.json is the spec the agent writes — if the engine rewrote existing
    values, the single-writer principle would break and generate would gain a
    side effect of mutating its own input
  • measure: switched to an isotropic scale derived from the viewBox width
  • Unified the basis for grid overlay, imbalance reporting, and detect_layout

Knowledge layer (30b33f0c)

  • Canvas dimensions rewritten as "width is always 1920, height comes from
    slideSize"
  • Derived values (drawing area, etc.) now teach the derivation and keep the
    16:9 numbers as an example
    • Ratio scaling would be the wrong abstraction: y=173 is
      title.y2 + margin (a placeholder-derived absolute) and y=950 is
      "130 px above the bottom" (an absolute margin). Fattening the title band by
      1.33× on 4:3 would be incorrect
  • art-direction / vibe now record slideSize into deck.json
    (vibe never calls analyze_template, so deck.json is the only path that
    reaches the composer)
  • composer gained the rule "width is fixed at 1920, height varies — always read
    slideSize"

Web UI and live preview (d4d6faa3)

All three problems came from one variable carrying two different meanings.

  1. Preview follows the real aspect ratio (symptom 5 in the issue)

    • Slide previews (SlideThumbnail / WorkspaceView / SlideCarousel /
      AnimatedSlidePreview) now follow the actual ratio
    • The width cap keeps the existing "convert a height constraint into a width
      limit" structure and only the ratio is lifted from the child. Using w-fit
      instead collapses the width to 0 (parent width ← child width ← parent width)
    • Deck list cards are intentionally left unchanged — CSS Grid rows align
      to the tallest cell, so mixing 16:9 and 4:3 decks would leave ~33% blank
      under the 16:9 cards. These are identification thumbnails, so cropping is
      acceptable
    • No metadata propagation (API / types / DDB) was needed
  2. The first few slides never animated (pre-existing)

    • Animation was suppressed for 3 seconds after the slides tab appeared, and a
      new deck switches to that tab as soon as slides arrive — so the first
      slides were always shown instantly
    • Replaced the time-based approximation with a mount-time fact. This is the
      design that was already reached on 2026-04-13; the 3-second timer was a
      later addition whose rationale was never documented
  3. The PNG fallback never appeared (pre-existing)

    • setError(false) ran on every 1-second poll, so the fallback was unmounted
      before it could be seen
    • When the render container was not mounted the URL was marked as processed,
      leaving a permanently black box
    • A payload with neither bgSvg nor components is now treated as a failure
      (a background-only slide is still legitimate)
    • SlideThumbnail gained onError and an explicit placeholder when
      previewUrl is missing

Documentation (392f833d)

  • A "Slide size (aspect ratio)" section plus known limitations in
    docs/en/custom-template.md
  • CHANGELOG.md [Unreleased]

Testing

  • make all804 passed, 6 skipped
  • cd web-ui && npx eslint src/ --quiet && npx tsc --noEmit && npm test
    229 passed
  • Six new test files. analyzer had no tests at all, so tests pinning its 16:9
    behaviour were added before changing it — without them, "existing tests pass
    unchanged" would not have been valid evidence of 16:9 invariance
  • tests/test_converter_scale.py and test_converter_elements.py pass
    unchanged, which is the evidence for 16:9 invariance

Verified on a live AWS deployment

  • A 4:3 template uses the full canvas (1920×1440 px)
  • Existing decks render their previews
  • New decks show the live preview and animate

Security scan

ash scan --mode local --fail-on-findings reports 20 findings and fails, but
all of them come from npm-audit (hono / ip-address / postcss / next /
sharp / undici) and are unrelated to this PR. There are no findings from the
code itself — bandit, semgrep, detect-secrets, and checkov all pass. Dependency
updates are handled by Dependabot and are out of scope here.

Out of scope (known limitations)

Item Reason
Architecture-diagram box auto-height under-estimates by ~25% on non-16:9 (layout/placement.py) The calibration needs the physical EMU width plumbed in, which would break the arch layout engine's "template-agnostic pure px space" property. That is a different kind of change from the rest of this PR (which only uses dimensions already at hand), so it will be a separate issue. Workaround — specify box.height explicitly — is documented
The 8 style-gallery HTML files are fixed at 1920×1080 The composer only consumes :root design tokens, so documenting "style demos use a fixed 16:9 canvas" is sufficient
Deck list thumbnails are cropped Accepted deliberately as a trade-off for consistent grid row heights
Web UI skeleton ratio (layout shift before the image loads) Making this exact requires metadata propagation; deferred
Several slides animate at once when switching from the spec tab to the slides tab Solvable with visibility-based detection (IntersectionObserver); a separate improvement from this PR

Note

No 4:3-specific branching was introduced, so other ratios such as 16:10 and A4
work automatically. Verification was concentrated on 4:3.

16:9 以外(4:3 等)のカスタムテンプレートでレイアウトが崩れる問題 (#208) を修正。

座標系の正は builder/converter に既にある「幅1920px固定・高さ可変」とし、
それに従っていなかったレイヤーを整合させる:

- engine に slide_size_px() / emu_per_px() を新設し、builder と converter の
  インライン計算を置き換え(挙動不変)
- analyzer: emu=6350 固定をやめ幅1920正規化に統一(4:3 で 1440x1080 と
  誤報告していたものが 1920x1440 になる)
- lint: 高さ OOB チェックを撤去しスライドサイズ非依存に純化。
  4:3 の正しい配置を誤警告で押し戻すフィードバックループを解消。
  幅チェックは定数として常に正しいので維持
- generate: テンプレート実寸ベースの高さ境界警告と slideSize 食い違い警告を追加
- deck.json の slideSize は新規生成時のみ engine が書く(api.init / pptx import)。
  既存 deck.json は更新しない
- measure: viewBox 幅基準の等方スケールに変更(4:3 で y が圧縮される問題)
- grid overlay / imbalance / detect_layout の基準を統一

16:9 の挙動は完全に不変(全変更が16:9では同値)。
analyzer は既存テストが皆無だったため 16:9 pin テストを先に追加した。

SPEC: 20260805-0100_aspect-ratio-agnostic-slide-canvas
Refs: #208
references が「キャンバスは1920×1080、描画領域 y=173–950」と固定値で教えて
いたため、4:3 テンプレート(実キャンバス 1920×1440px)でも AI が y≤1080 で
設計し下部が空白になっていた (#208 のメイン症状)。

- キャンバス寸法は「幅1920固定・高さは slideSize 参照」に書き換え
- 派生値(描画領域など)は導出式を教え、16:9 の具体値は例示として残す。
  比率スケールにしないのは y=173 が title.y2+margin 由来の絶対値、
  y=950 が「下端から130px」由来の絶対マージンであり、
  4:3 でタイトル帯を1.33倍に太らせるのは誤りだから
- art-direction / vibe に deck.json への slideSize 記録手順を追加
  (vibe は analyze_template を呼ばないため deck.json が唯一の伝達経路)
- composer に「幅1920固定・高さ可変、slideSize を必ず参照」の規則を追加
- スタイルデモは 16:9 固定キャンバスであることを明記
- arch 図の box 自動高さが非16:9で過小になる既知の制約と回避策
  (box.height の明示指定)を arch-layout-engine.md に記載

SPEC: 20260805-0100_aspect-ratio-agnostic-slide-canvas
Refs: #208
…課題を修正

#208 のプレビュー見切れ修正に加え、実装中に発覚した2つの既存問題も解消。
3つとも「異なる意味の状態を1つの変数で兼用していた」ことが原因。

1. プレビューのアスペクト比追従(#208 の症状5)
   aspect-[16/9] 固定枠 + object-cover で 4:3 が上下25%クロップされていた。
   スライドプレビュー(SlideThumbnail / WorkspaceView / SlideCarousel /
   AnimatedSlidePreview)を実比率追従にする。デッキ一覧カードは
   グリッド行高が 16:9/4:3 混在で揃わなくなるため意図的に現状維持。
   幅上限は「高さ制約を幅に変換する」既存の構造を保ち、比率のみ子から
   持ち上げて可変化(w-fit だと親幅←子幅←親幅の循環で幅0に崩壊する)

2. 最初の数枚がアニメーションされない(既存問題)
   slides タブ表示から3秒間の抑制タイマーがあり、新規デッキでは
   スライド出現でタブが自動切替されるため最初の数枚が必ず即時表示に
   なっていた。時間ベースの近似をやめ、マウント時の事実
   (hadSlidesOnMount) で判定する。これは 2026-04-13 の SPEC で既に
   到達していた設計で、settled タイマーは設計意図が文書化されていない
   後付けだった。新規デッキでは knownUrl を渡さない

3. PNG フォールバックが機能しない(既存問題)
   - リトライごとに setError(false) していたためフォールバックが毎秒
     アンマウントされ実質表示されなかった → fetch 成功確定時のみ false に
   - container null 時に無言 return して URL を処理済み扱いしていたため
     恒久的に黒箱になっていた → lastComposeUrlRef をリセットしリトライ可能に
   - bgSvg も components も無い場合を失敗扱いに(背景のみのスライドは正当)
   - SlideThumbnail に onError と previewUrl 不在時の明示プレースホルダを追加

なお skipThisUpdate は「アニメーションなしで即時描画」の意味であり
「描画しない」ではない(early return にすると既存デッキが永久に
描画されなくなる)。

SPEC: 20260805-0100_aspect-ratio-agnostic-slide-canvas
Refs: #208
- docs/en/custom-template.md に「Slide size (aspect ratio)」節を追加。
  座標系(幅1920固定・高さは比率追従)の対応表と、既知の制約
  (arch 図の box 自動高さ、スタイルデモは16:9固定、
  デッキ一覧サムネイルのクロップ)を明記
- CHANGELOG の [Unreleased] に #208 とライブプレビュー2件を追記

SPEC: 20260805-0100_aspect-ratio-agnostic-slide-canvas
Refs: #208
@ShotaroKataoka ShotaroKataoka changed the title fix: 16:9以外のカスタムテンプレート(4:3等)に対応し、ライブプレビューの既存2課題も修正 fix: support non-16:9 custom templates (4:3 etc.) and fix two pre-existing live preview issues Aug 5, 2026
@ShotaroKataoka ShotaroKataoka added blog:skip ブログ対象外 bug Something isn't working labels Aug 5, 2026
@ShotaroKataoka
ShotaroKataoka merged commit 7d49249 into main Aug 5, 2026
17 of 18 checks passed
@ShotaroKataoka
ShotaroKataoka deleted the fix/aspect-ratio-agnostic-canvas-208 branch August 5, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blog:skip ブログ対象外 bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: 4:3 など 16:9 以外のカスタムテンプレートで生成すると、レイアウト崩れ・プレビュー見切れが発生する

1 participant