Skip to content

OGP強化と招待ページのリニューアル#142

Merged
sugiwe merged 8 commits into
mainfrom
feature/ogp-and-invitation-improvements
Jun 13, 2026
Merged

OGP強化と招待ページのリニューアル#142
sugiwe merged 8 commits into
mainfrom
feature/ogp-and-invitation-improvements

Conversation

@sugiwe

@sugiwe sugiwe commented Jun 13, 2026

Copy link
Copy Markdown
Owner

概要

OGPメタタグの強化と招待ページの全面リニューアルを実施しました。

変更内容

Phase 1: OGP強化

交換日記詳細ページ

  • meta_type: "article" を追加
  • twitter_card: "summary" を追加

投稿詳細ページ

  • twitter_card を条件分岐で追加
    • 画像付き投稿: summary_large_image
    • 画像なし: summary

Phase 2: 招待ページ改善

デザイン刷新

  • 交換日記のカバーアート表示を追加
  • 参加者一覧(アバター付き)を追加
  • より魅力的で見やすいレイアウトに変更

OGP設定

  • meta_title, meta_description, meta_image, meta_type, twitter_card を追加
  • noindexは既存のコントローラー設定を維持

プレビュー機能

  • 公開スレッドの場合、最新投稿3件をプレビュー表示
  • 投稿のサムネイル、タイトル、本文冒頭、著者、日付を表示

UI改善

  • ページタイトルに交換日記名を動的に表示(「[タイトル]への招待」)
  • ボタンのテキストをより明確に変更
  • レスポンシブ対応

その他の改善

  • 交換日記の説明文で改行を保持するように修正(whitespace-pre-wrap
    • 交換日記詳細ページ
    • 招待ページ

関連ドキュメント

スクリーンショット

(必要に応じて後ほど追加)

確認事項

  • ローカルで動作確認
  • Twitter Card Validator で OGP確認
  • 招待ページのデザイン確認(ログイン前後)
  • 公開/非公開スレッドでの招待ページ表示確認

🤖 Generated with Claude Code

sugiwe and others added 2 commits June 13, 2026 17:29
OGP強化と招待ページのリニューアルを実施しました。

## Phase 1: OGP強化

### 交換日記詳細ページ
- `meta_type: "article"` を追加
- `twitter_card: "summary"` を追加

### 投稿詳細ページ
- `twitter_card` を条件分岐で追加
  - 画像付き投稿: `summary_large_image`
  - 画像なし: `summary`

## Phase 2: 招待ページ改善

### デザイン刷新
- 交換日記のカバーアート表示を追加
- 参加者一覧(アバター付き)を追加
- より魅力的で見やすいレイアウトに変更

### OGP設定
- `meta_title`, `meta_description`, `meta_image`, `meta_type`, `twitter_card` を追加
- noindexは既存のコントローラー設定を維持

### プレビュー機能
- 公開スレッドの場合、最新投稿3件をプレビュー表示
- 投稿のサムネイル、タイトル、本文冒頭、著者、日付を表示

### UI改善
- ページタイトルに交換日記名を動的に表示
- ボタンのテキストをより明確に変更
- レスポンシブ対応

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
交換日記の説明文で改行を保持するように修正しました。

## 変更内容

- 交換日記詳細ページの説明文に `whitespace-pre-wrap` を追加
- 招待ページの説明文に `whitespace-pre-wrap` を追加

これにより、複数行の説明文が見やすく表示されるようになります。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sugiwe sugiwe self-assigned this Jun 13, 2026

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request enhances the OGP (Open Graph Protocol) meta tags across several views and enriches the invitation page with cover art, participant lists, and recent post previews. It also introduces two planning documents outlining the prioritization of OGP, invitation pages, and upcoming features like PWA and mentions. The review feedback highlights several areas for improvement: addressing a potential N+1 query when loading recent posts on the invitation page, updating system tests to match the new UI copy, truncating long thread descriptions in meta tags, and correcting a file path typo in the documentation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.


/ 最新投稿プレビュー(公開スレッドの場合)
- if @invitation.thread.free? || @invitation.thread.paid?
- recent_posts = @invitation.thread.posts.published.order(published_at: :desc).limit(3)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

最近の投稿プレビューを表示するループ処理において、各投稿のユーザー情報(post.user.display_name)やサムネイル画像(post.thumbnail.attached?)にアクセスしているため、N+1クエリが発生します。\n\nincludes(:user, thumbnail_attachment: :blob) を追加して、関連レコードを事前に一括ロード(Eager Loading)することをお勧めします。

    - recent_posts = @invitation.thread.posts.published.includes(:user, thumbnail_attachment: :blob).order(published_at: :desc).limit(3)

Comment on lines +2 to +5
- content_for :meta_title, "#{@invitation.thread.title}への招待 - coconikki"
- content_for :meta_description, @invitation.thread.description.present? ? @invitation.thread.description : "#{@invitation.invited_by.display_name}さんがあなたを交換日記「#{@invitation.thread.title}」に招待しています。"
- content_for :meta_type, "article"
- content_for :twitter_card, "summary"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

招待ページのタイトル、ヘッダー、ボタン、およびリンクのテキストが大幅に変更されたため、既存のシステムテスト(spec/system/invitations_spec.rb)が失敗する可能性があります。\n\n具体的には、以下のテストケースが影響を受けます:\n- expect(page).to have_title("招待 - #{correspondence_thread.title} - coconikki") -> タイトルが "[タイトル]への招待 - coconikki" に変更されたため失敗します。\n- expect(page).to have_content("交換日記への招待") -> ヘッダーが 「[タイトル]」への招待 に変更されたため失敗します。\n- expect(page).to have_button("参加する") -> ボタンが "この交換日記に参加する" に変更されたため失敗します。\n- expect(page).to have_link("断る", href: root_path) -> リンクが "今回は見送る" に変更されたため失敗します。\n\nspec/system/invitations_spec.rb のテストコードも新しいUIの文言に合わせて更新してください。

| 有効期限:無期限
/ メタタグ設定
- content_for :meta_title, "#{@invitation.thread.title}への招待 - coconikki"
- content_for :meta_description, @invitation.thread.description.present? ? @invitation.thread.description : "#{@invitation.invited_by.display_name}さんがあなたを交換日記「#{@invitation.thread.title}」に招待しています。"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

交換日記の説明文(@invitation.thread.description)が長い場合、メタ説明文(meta_description)が検索エンジンの推奨文字数(一般的に120〜160文字程度)を超えてしまう可能性があります。\n\ntruncate ヘルパーを使用して、適切な長さに切り詰めることをお勧めします。

- content_for :meta_description, @invitation.thread.description.present? ? truncate(@invitation.thread.description, length: 120) : "#{@invitation.invited_by.display_name}さんがあなたを交換日記「#{@invitation.thread.title}」に招待しています。"

Comment thread app/views/threads/show.html.slim Outdated
@@ -1,6 +1,8 @@
/ メタタグ設定
- content_for :meta_title, "#{@thread.title} - coconikki"
- content_for :meta_description, @thread.description.present? ? @thread.description : "#{@thread.title} の投稿一覧です。"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

交換日記の説明文(@thread.description)が長い場合にメタ説明文が長くなりすぎないよう、truncate ヘルパーを使用して適切な長さに切り詰めることをお勧めします。

- content_for :meta_description, @thread.description.present? ? truncate(@thread.description, length: 120) : "#{@thread.title} の投稿一覧です。"

- `ThreadsController#show` で必要な情報を用意

2. **投稿詳細ページのOGP実装**
- `app/views/posts/show.html.slim` にメタタグ設定を追加

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

ドキュメント内のファイルパスが app/views/posts/show.html.slim となっていますが、実際のパスは app/views/threads/posts/show.html.slim です。将来のメンテナンス時の混乱を防ぐため、正しいパスに修正することをお勧めします。

Suggested change
- `app/views/posts/show.html.slim` にメタタグ設定を追加
- app/views/threads/posts/show.html.slim にメタタグ設定を追加

sugiwe and others added 3 commits June 13, 2026 17:38
招待ページのUIリニューアルに合わせてテストを修正しました。

## 変更内容

- タイトルフォーマット: "招待 - [タイトル] - coconikki" → "[タイトル]への招待 - coconikki"
- ページ見出し: "交換日記への招待" → "「[タイトル]」への招待"
- ボタンテキスト: "参加する" → "この交換日記に参加する"
- リンクテキスト: "断る" → "今回は見送る"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Brakemanを最新版にアップデートしてCI scan_rubyを修正しました。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Geminiコードレビューの指摘に対応しました。

## 変更内容

1. **N+1クエリの解消**
   - 招待ページの最新投稿取得に `includes(:user, thumbnail_attachment: :blob)` を追加

2. **メタ説明文の長さ制限**
   - 交換日記詳細ページ: `truncate(@thread.description, length: 120)` を追加
   - 招待ページ: `truncate(@invitation.thread.description, length: 120)` を追加
   - 検索エンジンの推奨文字数(120〜160文字)に合わせて調整

3. **ドキュメントの修正**
   - ファイルパスの誤りを修正: `app/views/posts/show.html.slim` → `app/views/threads/posts/show.html.slim`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sugiwe

sugiwe commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request enhances the OGP (Open Graph Protocol) metadata across thread, post, and invitation pages, and significantly redesigns the invitation landing page to display cover art, participants, and recent posts. It also includes planning documentation for OGP enhancements and future feature priorities. The review feedback highlights several improvement opportunities on the new invitation page: preventing potential NoMethodError crashes by handling nil or deleted users in the participant list, optimizing performance by using resized image representations for post thumbnails, and utilizing the existing extract_plain_text helper to strip markdown syntax from truncated post previews.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +36 to +37
- @invitation.thread.memberships.includes(:user).each do |membership|
.flex.items-center.gap-2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

参加者一覧を表示する際、退会済み(ソフトデリートされた)ユーザーや、何らかの理由で user レコードが nil になっているメンバーシップが存在する場合、membership.user.username などの呼び出しで NoMethodError が発生して画面全体がクラッシュする危険性があります。\n\nまた、招待ページに退会済みユーザーがアクティブな参加者として表示されるのを防ぐため、nil チェックおよび deleted? チェックを行ってスキップすることをお勧めします。

        - @invitation.thread.memberships.includes(:user).each do |membership|\n          - next if membership.user.nil? || membership.user.deleted?\n          .flex.items-center.gap-2

Comment on lines +53 to +54
- if post.thumbnail.attached?
= image_tag post.thumbnail, class: "w-full h-full object-cover", alt: post.display_title

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

投稿のサムネイル画像(post.thumbnail)をそのまま image_tag で表示すると、ユーザーがアップロードしたオリジナルの大容量画像がそのままダウンロードされてしまい、モバイル端末などでパフォーマンス低下や通信量の無駄遣いにつながります。\n\n表示サイズは w-12 h-12(48x48px)と非常に小さいため、representation(resize_to_limit: [100, 100]) を使用してリサイズされた軽量な画像を読み込むように改善することをお勧めします。また、画像が正常に処理可能か確認するために representable? のチェックも追加するとより安全です。

                  - if post.thumbnail.attached? && post.thumbnail.representable?\n                    = image_tag post.thumbnail.representation(resize_to_limit: [100, 100]), class: "w-full h-full object-cover", alt: post.display_title

.text-xl 📮
.flex-1.min-w-0
.text-sm.font-medium.text-gray-900.truncate = post.display_title
.text-xs.text-gray-500.line-clamp-2.mt-1 = truncate(post.body, length: 80)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

post.body はマークダウン形式のテキストであるため、通常の truncate を使用すると **太字**[リンク](url) などのマークダウン記法がそのまま露出したり、途中で切れて表示が崩れたりする可能性があります。\n\nすでに MetaTagsHelper に用意されている extract_plain_text ヘルパーメソッドを使用することで、マークダウン記法を除去した綺麗なプレーンテキストとして要約を表示できます。

                  .text-xs.text-gray-500.line-clamp-2.mt-1 = extract_plain_text(post.body, max_length: 80)

2回目のGeminiコードレビューの指摘に対応しました。

## 変更内容

1. **参加者一覧の安全性向上**
   - 退会済みユーザーやnilユーザーをスキップする処理を追加
   - `next if membership.user.nil? || membership.user.deleted?`
   - NoMethodErrorのクラッシュを防止

2. **投稿サムネイルのパフォーマンス最適化**
   - オリジナル画像ではなくリサイズ済み画像を使用
   - `post.thumbnail.representation(resize_to_limit: [100, 100])`
   - 48x48pxの表示サイズに対して100x100pxの軽量画像を配信
   - `representable?` チェックも追加

3. **投稿本文プレビューの改善**
   - マークダウン記法を除去してプレーンテキスト化
   - `truncate(post.body, length: 80)` → `extract_plain_text(post.body, max_length: 80)`
   - 既存のヘルパーメソッドを活用

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sugiwe

sugiwe commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request enhances OGP metadata across threads and posts, redesigns the invitation page to be more engaging, adds documentation regarding OGP improvements and future feature priorities, and updates system tests. The review feedback identifies several potential runtime errors (such as NoMethodError and UrlGenerationError) on the invitation page if the inviting user or post author is deleted or nil, and recommends adding defensive checks and safe navigation operators to ensure robustness.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +16 to +18
p.text-gray-600.text-sm
= link_to "@#{@invitation.invited_by.username}", user_path(@invitation.invited_by.username), class: "text-blue-600 hover:underline"
| さんがあなたを招待しています

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

招待者が退会している場合、@invitation.invited_by.usernamenil または空文字になり、user_path の呼び出しで ActionController::UrlGenerationError が発生してページ全体が500エラーになる危険性があります。
また、退会済みユーザーのプロフィールへのリンクを表示しないように制御する必要があります。

    p.text-gray-600.text-sm
      - if @invitation.invited_by.nil? || @invitation.invited_by.deleted?
        | 退会済みユーザーさんがあなたを招待しています
      - else
        = link_to "@#{@invitation.invited_by.username}", user_path(@invitation.invited_by.username), class: "text-blue-600 hover:underline"
        | さんがあなたを招待しています

| 有効期限:無期限
/ メタタグ設定
- content_for :meta_title, "#{@invitation.thread.title}への招待 - coconikki"
- content_for :meta_description, @invitation.thread.description.present? ? truncate(@invitation.thread.description, length: 120) : "#{@invitation.invited_by.display_name}さんがあなたを交換日記「#{@invitation.thread.title}」に招待しています。"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

招待者が退会(ソフトデリートなど)している場合や、何らかの理由で invited_bynil の場合に、@invitation.invited_by.display_nameNoMethodError が発生する可能性があります。
安全のために、invited_by の存在および削除ステータスをチェックする防衛的な実装にすることをお勧めします。

- inviter_name = (@invitation.invited_by.nil? || @invitation.invited_by.deleted?) ? "退会済みユーザー" : @invitation.invited_by.display_name
- content_for :meta_description, @invitation.thread.description.present? ? truncate(@invitation.thread.description, length: 120) : "#{inviter_name}さんがあなたを交換日記「#{@invitation.thread.title}」に招待しています。"

Comment on lines +62 to +64
= post.user.display_name
| ·
= post.display_published_at.strftime("%Y/%m/%d")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

投稿の作成者が退会している場合や、公開日時が設定されていないイレギュラーなケースにおいて、post.userpost.display_published_atnil になり NoMethodError が発生する可能性があります。
安全のためにぼっち演算子(&.)を使用し、フォールバックを用意することをお勧めします。

                    = post.user&.display_name || "退会済みユーザー"
                    |  ·
                    = post.display_published_at&.strftime("%Y/%m/%d")

3回目のGeminiコードレビューの指摘に対応しました。

## 変更内容

1. **招待者が退会済みの場合の対応**
   - メタ説明文: 招待者名を安全に取得(退会済みの場合は「退会済みユーザー」)
   - 招待ヘッダー: 退会済みユーザーの場合はリンクを表示せず「退会済みユーザーさんがあなたを招待しています」と表示
   - `NoMethodError` と `UrlGenerationError` のクラッシュを防止

2. **投稿プレビューの安全性向上**
   - ぼっち演算子(`&.`)を使用して安全にメソッド呼び出し
   - `post.user&.display_name || "退会済みユーザー"`
   - `post.display_published_at&.strftime("%Y/%m/%d")`
   - 退会済みユーザーやnilの場合のフォールバック処理

## 防御的プログラミング

すべてのユーザー参照箇所で退会済み・nilの可能性を考慮し、
ページクラッシュを防ぐ堅牢な実装になりました。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sugiwe

sugiwe commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request enhances the OGP metadata across thread and post views, redesigns the invitation page to include cover art, participant lists, and recent post previews, and adds planning documentation for future feature priorities. The feedback suggests ordering the participant list on the invitation page by position to ensure consistency with the thread details page.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

.mb-6
.text-sm.font-medium.text-gray-700.mb-3.text-center 参加者
.flex.flex-wrap.items-center.justify-center.gap-3
- @invitation.thread.memberships.includes(:user).each do |membership|

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

参加者一覧の表示順が、交換日記詳細ページ(order(:position))と不整合になる可能性があります。一貫した表示順を保つために、ここでも order(:position) を明示的に指定することをお勧めします。

- @invitation.thread.memberships.includes(:user).order(:position).each do |membership|

- 交換日記詳細ページと同様にposition順でメンバーを表示
- 一貫したUI体験を提供
@sugiwe sugiwe merged commit 7529018 into main Jun 13, 2026
4 checks passed
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.

1 participant