Skip to content

feat(picker): ピッカーのセルにマニフェストのサムネイルを描き、ホバーに説明を加える - #69

Merged
GeneralD merged 5 commits into
mainfrom
feat/effect-picker-thumbnails
Oct 30, 2026
Merged

GeneralD merged 5 commits into
mainfrom
feat/effect-picker-thumbnails

Conversation

@GeneralD

Copy link
Copy Markdown
Owner

agent type scope closes commits

概要

エフェクトピッカー(#65)のセルに、マニフェストの thumbnail(#66 で読み込み済み)を描きます。サムネイルが無い、またはデコードできないエフェクトは、これまで通りプレースホルダーのタイルを描きます。あわせて、セルへのホバーにマニフェストの description を足します。これで #12 の「サムネイル付きグリッド」「ホバーで短い説明」が揃うため、#12 を閉じます。

ライブプレビュー(上部のカメラ映像)は #68(Follow-up of #12)に切り出しました。ホストアプリにプレビューを購読する経路が無く(CameraInteractor に PreviewView を attach する口が無い)、先にその設計判断が要るためです。この PR には入っていません。

変更点

  • 7ea416b feat(entity): EffectArtwork に image(EffectThumbnail, fallback: EffectPlaceholder) を追加。モノグラムと色相はエントリから決まるため、画像が描けない場合のタイルを case に持たせた
  • 2d84070 feat(presenter): エントリが thumbnail を持つとき、セルのアートワークに画像(とフォールバックのタイル)を選ぶ
  • 64bc873 feat(picker): セルがサムネイルを描き、デコードできなければタイルに戻る。デコード結果の判断は Presenters の resolveArtwork、実際のデコードは AppRouter の EffectThumbnailDecoding に置き、Views にはクロージャで渡す
  • f775147 feat(catalog): EffectCatalogEntry.description を足し、パッケージの manifest.description をホバーに運ぶ(前後の空白を除き、空なら出さず、280 文字で … 切り詰め)
  • 1877801 docs(picker): ARCHITECTURE / AGENTS / README / dev-verification.md を実装に合わせて更新(未来形だった記述の現在形化、判断と既知の限界の追記を含む)

設計判断

  • デコード失敗のフォールバックの置き場所: 当初案の「Views に resolver を置く」は採りませんでした。Views は coverage-ignored で、テストターゲットを持たず、宣言だけを置く決まりです(.claude/rules/coverage-ignored-modules.md)。判断(デコードして、失敗したらタイル)は Presenters の汎用関数 resolveArtwork に置いてテストし、CGImage を作る呼び出しだけを AppRouter の EffectThumbnailDecoding(coverage 対象)に置きました。AppRouter が Views にクロージャで渡します。Import allow-list(Presenters は AppKit 不可)にも合います。
  • NSImage(data:) ではなく NSBitmapImageRep: 署名だけが正しい壊れたバイト列に対し、NSImage(data:) は nil ではなくサイズ 0 の画像を返し、セルが空になります。NSBitmapImageRep(data:)?.cgImage は、試した不正入力のすべてで nil でした(手元で計測)。
  • ピクセル数の上限(2048 × 2048): データ側は 1 MiB・署名までしか見ないため、小さなファイルが巨大な画像に展開され得ます。20000 × 20000 のグレー PNG(1.7 MB)で、描画時に約 400 MB・0.6 秒を計測しました。上限を超えたものはタイルにします。ImageIO のサムネイル API なら展開自体を抑えられますが、ImageIO は Views レーンの import allow-list にありません。代替案と既知の限界は docs/ARCHITECTURE.md に書きました(デコード結果のキャッシュは持たず、描画のたびにデコードする点も既知の限界として明記)。依頼にない追加なので、不要ならこの 1 ファイルとそのテストを外せます。
  • ホバー説明をこの PR に含めた理由: エフェクト選択 UI: Snap Camera 風サムネイルグリッドピッカー #12 の提案に「ホバーで短い説明」があり、feat: carry the manifest thumbnail through to the effect catalog (#12, data half) #66 のデータ側は description をカタログまで運んでいませんでした。サムネイルと同じセル・同じ EffectCatalogEntry の拡張で、別 PR にすると同じ箇所を二度触るため、ここに吸収しました。280 文字の上限は、長い説明でツールチップが肥大しないようにするための選択です(依頼にない追加)。
  • プレビューが エフェクトピッカー: ホストアプリのライブプレビュー #68 である理由: セルの静止画とは違い、ホストアプリにはプレビューを購読する経路自体がありません。経路の候補(PreviewSink の再利用か、フレーム経路への追加か)は設計判断を先に要し、サムネイルとは独立に出せるため分けました。

受け入れ基準の対応表

#12 の受け入れ基準 対応
ピッカーを開くとインストール済み .lens がサムネイル付きグリッドで並ぶ 画像の選択: EffectPickerPresenterTests の「an effect whose entry carries a thumbnail gets the picture…」(PNG / JPEG)、デコード判断: EffectArtworkResolutionTests、実デコード: EffectThumbnailDecodingTests。同梱の Ripple の thumbnail.png が読めることは ExamplePackagesTests
クリックで仮想カメラの出力が切り替わる 選択が Interactor に渡ることは EffectPickerPresenterTests の「select goes to the Interactor…」(#65)。実機での目視(クリックして Zoom などの出力が変わること)は未実施。dev-verification.md の Lane 1「Effect picker」行は「not yet run」のまま
thumbnail の無い .lens でもクラッシュせずプレースホルダーが出る EffectPickerPresenterTests の「an effect without a thumbnail… gets the tile alone」、壊れたバイト列・途中で切れた PNG・空・ピクセル超過は EffectThumbnailDecodingTests で nil、EffectArtworkResolutionTests でタイルに戻る
選択中のエフェクトとメニューバーの選択状態が常に一致する 本 PR では変更なし。EffectPickerPresenterTests の「the picker's chosen cell is the menu's checked row, through every change of choice」(#65)が引き続き緑
EffectPickerPresenter と EffectPickerGeometry のテストが決定的に緑 make test(下記)
make check / make lint 緑 下記

テスト

ローカルのゲート(push 前にすべて実行):

  • make format: PASS(差分なし)
  • make lint: PASS
  • make check: PASS
  • make build: PASS
  • make build-dev-harness: PASS
  • make test: PASS — Test run with 1044 tests in 139 suites passed ... with 12 known issues(12 件の known issue は main でも同じ)
  • make test-gpu: PASS — Test run with 27 tests in 6 suites passed

追加・変更したテスト:

  • EffectPickerPresenterTests: サムネイル(PNG / JPEG / 差し替え)、サムネイル無し、読み込み失敗、ホバー説明(空白除去・280 文字・壊れたパッケージでは無視)
  • EffectArtworkResolutionTests(新規): デコード成功・失敗・placeholder はデコードしない
  • EffectThumbnailDecodingTests(新規): 有効な PNG / JPEG、署名のみ・ゴミ・途中切れ・空は nil、2048 × 2048 と 4096 × 1024 は可、2049 × 2049 と 4097 × 1024 は nil
  • EffectPackageRepositoryTests / EffectCatalogIntegrationTests / ExamplePackagesTests: description がマニフェストからエントリまで届くこと

画面の確認としては、オフスクリーンで描画して確認しました(コミット外の使い捨て)。Ripple の画像が選択中のセルに描かれ、途中で切れた PNG を持つエントリが「TP」のタイルに戻り、読み込み失敗のセルが暗くなることを見ています。ただしこれは実機のウィンドウ・クリックの確認ではありません。

入れていないもの

  • ライブプレビュー: エフェクトピッカー: ホストアプリのライブプレビュー #68
  • 実機(署名済み .app を /Applications に入れた状態)でのクリックによる出力切り替えの目視: 未実施。.claude/rules/dev-verification.md の Lane 1「Effect picker」行の測定値は「not yet run」のまま
  • サムネイルのデコード結果のキャッシュ(既知の限界として ARCHITECTURE に記載)
  • version.txt の更新(マージ時に行う運用)

Closes #12

The case carries the package's EffectThumbnail and the placeholder tile the cell draws if the bytes do not decode. The picker's view draws the fallback for it until its image arm lands.
…try has one

pickerCell gives the image artwork, with the entry's own tile to fall back on, when EffectCatalogEntry.thumbnail is set, and the tile alone otherwise. Tests cover an entry with a picture in each format, one without, the bundled effect, a broken package, and a picture that is replaced or removed.
…it does not decode

resolveArtwork (Presenters, generic over the decoded image) decides what a cell draws: the decoded picture, or the tile the artwork carries when the decoder answers nil. EffectThumbnailDecoding (AppRouter, where AppKit glue is covered) is the live decoder: NSBitmapImageRep rather than NSImage(data:), which answers an image of no size for a bare signature, and a 2048 x 2048 pixel limit against a small PNG that claims a huge picture. The views only map the resolution to pixels, and receive the decoder from EffectPickerWindow.
EffectCatalogEntry gains description (last init parameter, default nil), filled from the manifest for a ready package and nil for the bundled effect and a broken package. The picker's hover shows it on a line after the name and author, without the whitespace around it, cut at 280 characters, and not at all when blank. Tests cover the Repository, the Presenter's wording, and the loopback; Ripple's description is pinned beside its thumbnail.
…ription

Key Design Decisions: Thumbnails and Effect picker (the slot is filled, the decode fallback and its limits, the description, the live preview left to #68), plus the layer rows, the Entity row, the physical abstraction table row and the delivery stages. Lane 1's Effect picker row now says what to look for (not yet run on a device). README: the manifest text, the roadmap row and the pre-alpha note. AGENTS.md follows.

The stale claims (a placeholder for now, no thumbnails yet, the hover without a description, the view half still to come) were found by grepping the repository for their words.
Copilot AI lite review requested due to automatic review settings October 30, 2026 02:28
@GeneralD GeneralD self-assigned this Oct 30, 2026
@coderabbitai

coderabbitai Bot commented Oct 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a9f996f8-c559-4e4a-bc72-4baec79f734c
Commits

Reviewing files that changed from the base of the PR and between 2f8e658 and 1877801.

Files selected for processing: 23
  • .claude/rules/dev-verification.md
  • AGENTS.md
  • README.md
  • Sources/AppRouter/EffectPickerWindow.swift
  • Sources/AppRouter/EffectThumbnailDecoding.swift
  • Sources/EffectPackageRepository/EffectPackageRepositoryImpl.swift
  • Sources/Entity/EffectCatalog.swift
  • Sources/Entity/EffectManifest.swift
  • Sources/Entity/EffectPickerCell.swift
  • Sources/Presenters/EffectArtworkResolution.swift
  • Sources/Presenters/EffectPickerPresenter.swift
  • Sources/Presenters/EffectPickerText.swift
  • Sources/Views/EffectArtworkView.swift
  • Sources/Views/EffectPickerCellView.swift
  • Sources/Views/EffectPickerView.swift
  • Tests/AppRouterTests/EffectThumbnailDecodingTests.swift
  • Tests/EffectPackageDataSourceTests/ExamplePackagesTests.swift
  • Tests/EffectPackageRepositoryTests/EffectPackageRepositoryTests.swift
  • Tests/FramePathIntegrationTests/EffectCatalogIntegrationTests.swift
  • Tests/PresentersTests/EffectArtworkResolutionTests.swift
  • Tests/PresentersTests/EffectPickerPresenterTests.swift
  • Tests/TestDoubles/EffectFixtures.swift
  • docs/ARCHITECTURE.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@codecov

codecov Bot commented Oct 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@GeneralD
GeneralD merged commit 36d8a43 into main Oct 30, 2026
8 checks passed
@GeneralD
GeneralD deleted the feat/effect-picker-thumbnails branch October 30, 2026 02:33
@GeneralD

GeneralD commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

agent follow-up

マージ後レビューの指摘と 0.24.0 への bump は #70 で扱いました。本文の訂正として、「外せる」のはデコーダ全体ではなく maxPixels の判定とそのテストだけです。

GeneralD added a commit that referenced this pull request Nov 3, 2026
fix(picker): #69 のマージ後レビュー指摘への対応と 0.24.0 への bump
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

エフェクト選択 UI: Snap Camera 風サムネイルグリッドピッカー

2 participants