サンドボックスの@メンションにアバター画像を追加し、スタイルリグレッションを防止
サンドボックス環境の@メンションにアバター画像を導入し、メンション付きの画像スタイリングのリグレッションを早期に検出できる体制を整えました。合わせて、メンションチップのCSSも視覚的に整理されています。
背景
メンション機能における画像スタイリングのリグレッションが繰り返し発生していました。サンドボックス(test/dummy アプリ)の@メンションプロンプトアイテムは、これまでイニシャル文字列のみを表示する簡素な実装であったため、実際のアバター画像を伴うレイアウト崩れを事前に確認する手段がありませんでした。
PR本文では「特にメンションにおける画像スタイリングのリグレッションがいくつか発生した」と明示されており、本PRはその再発防止を目的としています。サンドボックスを本番に近い状態に保つことで、新機能開発時にスタイル上の問題を早期発見できるようにしています。
技術的な変更
アバター画像のサンドボックスへの組み込み
test/dummy/app/assets/images/ に user-1.jpg〜user-5.jpg の5枚のユーザーアバター画像が追加されました。これらの画像を各プロンプトアイテムテンプレートで参照するよう、2つのパーシャルが更新されています。
people/_prompt_item.html.erb では、menu テンプレート(ドロップダウン表示)と editor テンプレート(エディタ内埋め込み)の両方にアバター画像が追加されました。ランダム選択によって毎回異なる画像が使われるため、複数パターンの表示を確認しやすくなっています:
<% avatar_id = 1 + rand(5) %>
<% user_avatar = asset_path("user-#{avatar_id}.jpg") %>
<template type="menu">
<span class="person person--prompt-item">
<img src="<%= user_avatar %>" class="person--avatar" alt="">
<span class="person--name"><%= person.name %></span>
</span>
</template>
<template type="editor">
<span class="person person--inline">
<img src="<%= user_avatar %>" class="person--avatar" alt="">
<span class="person--name"><%= person.name %></span>
</span>
</template>
groups/_prompt_item.html.erb では、グループメンション(editor テンプレート)にも user-1.jpg が固定で追加されました。グループのメニュー表示のイニシャルは "GR" から "G" に短縮されています。
.person--avatar スタイルの刷新
test/dummy/app/assets/stylesheets/sandbox.css において、.person--avatar のスタイルが <img> タグを含む実装に対応する形に整理されました。
変更前:
.person {
display: inline-flex;
align-items: center;
gap: 0.5rem;
.person--avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background-color: oklch(70% 0.15 260);
color: white;
font-size: 0.5rem;
font-weight: 600;
flex-shrink: 0;
}
&.person--inline {
background-color: oklch(0.7 0.175 240 / 15%);
border-radius: 0.25rem;
padding: 0 0.25rem;
}
}
変更後:
.person {
display: inline;
.person--avatar {
aspect-ratio: 1;
background-color: var(--lexxy-color-accent-dark);
block-size: 1lh;
border-radius: 50%;
display: inline-flex;
inline-size: 1lh;
vertical-align: text-bottom;
&:not(img) {
align-items: center;
color: var(--lexxy-color-ink-inverted);
font-weight: 600;
justify-content: center;
text-align: center;
}
}
&.person--inline {
background-color: oklch(50% 0 0 / 0.1);
border-radius: 20px;
box-shadow: 0 0 0 2px oklch(50% 0 0 / 0.1);
padding-inline-end: 0.25ch;
.node--selected & {
background-color: var(--lexxy-focus-ring-color);
box-shadow: 0 0 0 2px var(--lexxy-focus-ring-color);
}
}
}
サイズ指定が width/height の固定ピクセルから block-size: 1lh / inline-size: 1lh のフォント行高基準に変わり、テキストサイズに追従するようになりました。&:not(img) セレクタでイニシャル表示(<span>)と画像(<img>)のスタイルを分岐させており、どちらの要素型でも .person--avatar クラスを共用できる設計です。
lexxy-editor.css のメンションチップ修正
app/assets/stylesheets/lexxy-editor.css では、メンションノードの削除ボタン周りのスタイルが調整されました。border-start-end-radius: 0 と border-end-end-radius: 0(メンションチップとボタンを接合するための角丸の打ち消し)が削除され、代わりに inset-inline-start: 0 が lexxy-node-delete-button に追加されています。また、ボタン内の border-radius が var(--floating-tools-radius) から 50% に変更され、削除ボタンが常に円形になりました。
設計判断
サイズの基準をフォント行高(1lh)に統一する アプローチが採用されました。固定ピクセルによるサイズ指定では、テキストサイズの変動に対してアバターが追従できないという問題がありましたが、lh 単位を使うことでインラインコンテキストにおける自然なスケーリングが実現されています。
&:not(img) を使ったセレクタ分岐は、イニシャル表示と画像表示を同一クラスで管理しながら、それぞれに適したスタイルを当てるシンプルな実装です。<img> 要素に align-items: center などのフレックス配置プロパティを適用しても意味がないため、<span> 等の非画像要素のみに絞っています。また、カラー値もハードコードされた oklch(70% 0.15 260) からCSSカスタムプロパティ(var(--lexxy-color-accent-dark))に切り替えられており、テーマとの一貫性が高まっています。
まとめ
この変更は、サンドボックスを実際のユースケースに近い状態に保つことでスタイルリグレッションの早期発見を可能にするものです。アバター画像の導入に合わせてCSSの実装も整理されており、img と span を同一クラスで扱える柔軟な設計が確立されています。