統合フィードアイテム データ設計書
最終更新: 2026-09-30
1. 概要
データ取得層を、Server Componentでの直接取得から、Route Handler(BFF)+ TanStack Query(@tanstack/react-query)によるクライアントサイドキャッシュ層に置き換える場合の共通基盤設計。本書は次の4点を一元管理する。
| 内容 | 章 |
|---|---|
| 各ドメイン・機能への適用判定ログ(適用した/適用しなかったかの判断。理由は§6) | §3 |
| 共通実装パターン(Route Handlerのキャッシュ戦略・TanStack Query導入・サーバー側プリフェッチ) | §4 |
適用ドメインごとの個別設計(SNS新着投稿のUnifiedFeedItem・エンドポイント) |
§5 |
| 「適用しない」と判定した各ドメインの理由(備考) | §6 |
採用要否の判断基準(適用基準)はデータ設計書(全体概要) §4を正とする。新しい機能・画面のデータ取得層を設計する際は、採用の要否にかかわらず、判定結果を§3に追記する(「適用しない」場合は判定理由を§6に追記する)。読む順序は「§3の一覧で判定を確認 → 適用ならば§4・§5で設計内容を確認 → 適用しない理由を知りたい場合のみ§6を参照」を想定する。§5の個別設計は、判定が「適用」となったドメインのうちスキーマ・エンドポイントの設計が本書に置かれているものだけを扱う(ライブ・フェス情報はライブ・フェス機能 テーブル設計書 §6に置く)。
2. 背景・目的
| 目的 | 詳細 |
|---|---|
| UI層とデータ取得層の分離 | 画面コンポーネントがデータ取得・同期判定・整形ロジックと密結合になっている状態(Fat Component)を解消する。具体例はSNS新着投稿(§5-1) |
| クライアントサイドキャッシュの導入 | Blob/DB専用のサーバー関数をAPI層(Route Handler)経由でクライアントから呼び出し可能にし、TanStack Queryでキャッシュ・再取得を管理する |
| 汎用的なデータ取得基盤の確立 | 複数画面・複数形状のデータ取得に共通して使える基盤として、データ設計書(全体概要) §4の適用基準を満たす領域に同じパターンを用いる(§3参照)。全ドメインへ機械的に展開することは目的としない |
3. 適用判定ログ(ドメイン別)
データ設計書(全体概要) §4の適用基準(同じデータを複数の画面がクライアント側で必要としているか/異なる形状のデータを1つの表示に統合する必要があるか)を各ドメイン・機能に当てはめた判定結果を一元管理する実績ログ。判定が「適用」のドメインの実装詳細は§4・§5または各ドメインの個別データ設計書に、判定が「適用しない」の理由は§6に置く。
| 対象 | 判定 | 概要・参照先 |
|---|---|---|
| SNS新着投稿情報(トップ画面の新着投稿セクション) | 適用 | Ameba・Instagram・TikTok・YouTubeの4種の異なる形状のデータを、判別共用体(UnifiedFeedItem)として1つの表示に統合する。本基盤の初導入で、実装済。スキーマ・エンドポイント詳細 → §5(MorningStatusApp#1569) |
| メンバー情報(Vercel Blob) | 適用 | GET /api/membersを/members・/member-mapの複数画面がクライアント側で必要としている。実装済(MorningStatusApp#1640・PR #1645) |
| ライブ・フェス情報(Neon DB) | 適用 | /lives/prefecture/[code]のみ対象。この画面の地図・会場数の集計のみが、公演(lives)・フェス出演(festivals)という異なる形状のデータを1つの表示に統合している(一覧は別セクション)。他画面(/lives・/festivals・/lives/[id]・/festivals/[id]・/lives/map・/lives/map/[region])は単一形状を単一画面が使うのみ、または件数の数値集計のみのため対象外。実装済。スキーマ・エンドポイント詳細 → ライブ・フェス機能 テーブル設計書 §6(MorningStatusApp#1636) |
| SNS個別画面(Instagram・TikTok・YouTube・Ameba各画面) | 適用しない | 各画面はいずれも単一画面のみが使用。理由 → §6-1(MorningStatusApp#1635・#1653) |
| リリース情報(Neon DB) | 適用しない | 単一画面が使う単一形状のデータ。理由 → §6-2(MorningStatusApp#1639) |
| ユニット情報(Vercel Blob) | 適用しない | 単一画面が使う単一形状のデータ。理由 → §6-3(MorningStatusApp#1641) |
| ラジオ情報(Neon DB) | 適用しない | 一覧・番組詳細・放送回詳細の3階層構成だが、いずれも単一画面が使う単一形状のデータ。理由 → §6-4(MorningStatusApp#1637) |
| イベント情報(Neon DB) | 適用しない | /timelineのイベント取得は動的フィルタ駆動のクエリで、本基盤への単純な置き換えでは済まない。理由 → §6-5(MorningStatusApp#1638・#1643) |
| 世代マトリックス(メンバー: Vercel Blob/子供の生年: Neon DB) | 適用しない | Server Component内の派生データ算出であり、認証境界の都合もある。理由 → §6-6(MorningStatusApp#1632、morning-status-blume#138) |
| 過去のこの日セクション(トップ画面、Neon DB) | 適用しない | unstable_cacheでラップされたServer Component専用関数内で完結する。理由 → §6-7(MorningStatusApp#1644) |
| ソロアーティスト情報(Vercel Blob) | 適用しない | ソロアーティスト関連の各画面が対象。理由 → §6-9(morning-status-blume#167) |
| プレイリスト情報(Blob→Neon移行後) | 適用しない | /playlists・/playlists/[id]・/members/[id]選曲セクション・/songs/[id]掲載情報セクションはいずれも単一画面が使う。理由 → §6-8(MorningStatusApp#1677, morning-status-blume#166) |
4. 共通実装パターン
判定が「適用」となったドメインに共通する実装方針。
4-1. キャッシュ戦略(Route Handler)
新設するRoute Handler群は、独自のキャッシュ戦略を持たせず、内部で呼び出す既存lib関数のfetch設定(next: { revalidate: ... })をそのまま継承する(例: getDailyNewPostsFromBlob()はrevalidate: 1800)。Route Handler側でcache: "no-store"等を独自に指定しない。
これは、既存のGET /api/membersが独自にcache: "no-store"を指定した結果、同じBlobデータに対して経路(直接呼び出し vs Route Handler経由)でキャッシュ戦略が異なり、パッチスクリプト実行直後に「片方の画面では反映済、片方では古いまま」という食い違いが起きる問題が知られているため(開発ノート参照)。同じ混乱を新設APIで再発させないよう、既存関数の設定に統一する。
4-2. 依存追加
package.jsonに@tanstack/react-queryを追加する(SNS新着投稿機能で初導入)。
4-3. QueryClientProviderの設置
app/providers.tsxを新規作成し、クライアントコンポーネント("use client")としてQueryClientProviderを生成する。app/layout.tsxの<body>内で<Providers>{children}</Providers>のように全体をラップする(ヘッダー・フッターを含む<body>全体を対象とし、非SNS領域への展開時にも同じProviderを再利用できるようにする)。
// app/providers.tsx(イメージ)
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}
4-4. サーバー側プリフェッチ
適用画面のServer Componentで、Next.js App Routerの典型パターンに従い、サーバー側でprefetchQuery→dehydrate→HydrationBoundaryを用いてクライアントに渡す。これにより初回表示時のちらつき・ローディングスピナーを回避する。クライアント側のコンポーネントはuseQueryでRoute Handlerからデータを取得・キャッシュする。
SNS新着投稿では、トップ画面(app/page.tsx、NewPostsSectionの呼び出し元)でこのパターンを用いる。
5. SNS新着投稿(Ameba・Instagram・TikTok・YouTube)の個別設計
トップ画面の新着投稿セクション(components/NewPostsSection.tsx)における、Ameba・Instagram・TikTok・YouTube 4種のSNSデータ取得の設計。適用判定は§3を参照。
対応する実装Issue: MorningStatusApp#1569
5-1. 背景
導入前のNewPostsSectionは完全なサーバーコンポーネントとして実装されており、Blob/DB直接アクセス関数をawaitで直列に呼び出し、UI層とデータ取得・整形ロジックが密結合になっていた。NewPostsSection.tsx(857行)がSNS4種の同期判定・フィルタリング・遡及判定・ソート・UI描画を一括で担う「Fat Component」状態だった。本設計では、各SNSのデータ取得をAPI層(Route Handler)に切り出し、クライアント側からTanStack QueryのuseQueryで取得・キャッシュする構成に変更する(共通実装パターンは§4)。
5-2. 統合フィードアイテム(UnifiedFeedItem)スキーマ(判別共用体の定義、types/feed.ts 想定)
sourceフィールドを判別キーとする判別共用体(Discriminated Union)として定義する。
const FeedItemBaseSchema = z.object({
memberId: z.string(),
capturedAt: z.string(), // 収集日時(新着判定の基準。投稿日時ではない)
isRetroactive: z.boolean(), // 遡及収集判定
});
export const AmebaFeedItemSchema = FeedItemBaseSchema.extend({
source: z.literal("ameba"),
blogUrl: z.string().url(),
title: z.string(),
publishedAtLabel: z.string().nullable(), // 遡及時のみ実投稿日時ラベルを表示
lastUpdated: z.string(),
});
export const InstagramFeedItemSchema = FeedItemBaseSchema.extend({
source: z.literal("instagram"),
postUrl: z.string().url(),
thumbnailUrl: z.string().url().optional(),
isLive: z.boolean().optional(),
isReel: z.boolean().optional(),
caption: z.string(),
// 公式Instagramアカウントの投稿はmemberIdが公式アカウントの疑似IDであり、membersロースターに
// 対応するMemberレコードを持たないため、表示名はjoinで解決できない(実装時に判明、#1569)
memberName: z.string(),
});
export const TikTokFeedItemSchema = FeedItemBaseSchema.extend({
source: z.literal("tiktok"),
memberId: z.string().optional(), // OGメンバーのみ。公式アカウントの場合はundefined
postUrl: z.string().url(),
thumbnailUrl: z.string().url().optional(),
imageBlobUrl: z.string().url().optional(), // TikTok CDNサムネイルの期限切れ対策の代替画像(実装時に判明、#1569)
caption: z.string(),
accountId: z.string().optional(),
channelType: z.enum(["official", "og"]).optional(),
createdAt: z.string().optional(), // 実投稿日時。遡及収集時の投稿日時ラベル表示に使用(実装時に判明、#1569)
mentionedMemberIds: z.array(z.string()).optional(), // 公式グループアカウント投稿のタグ付けメンバー(実装時に判明、#1569)
});
export const YoutubeFeedItemSchema = FeedItemBaseSchema.extend({
source: z.literal("youtube"),
memberId: z.string().optional(), // OGメンバーのみ。公式チャンネルの場合はundefined
videoId: z.string(),
title: z.string(),
publishedAt: z.string(),
channelType: z.enum(["official", "og"]),
});
export const UnifiedFeedItemSchema = z.discriminatedUnion("source", [
AmebaFeedItemSchema,
InstagramFeedItemSchema,
TikTokFeedItemSchema,
YoutubeFeedItemSchema,
]);
export type UnifiedFeedItem = z.infer<typeof UnifiedFeedItemSchema>;
5-3. 既存型からのマッピングルール
| SNS | 既存の型・データソース | UnifiedFeedItemへのマッピング |
|---|---|---|
| Ameba | Member.statusHistory[]/latestStatus(StatusEntry、types/member.ts)。専用の投稿一覧取得関数を持たず、membersとAmebaSyncStatusから動的算出する(§5-5参照) |
blogUrl ← hasOfficialAmebaBlog(member)(member.officialSnsにAmebaブログのactiveエントリがあるか)でメンバーを絞り込んだ上でfindSupportedBlogUrl(entry.sources)、title ← entry.content.split("\n")[0]、lastUpdated ← entry.lastUpdated、capturedAt ← entry.capturedAt、isRetroactive ← isRetroactivePost(entry.lastUpdated, amebaSyncStatus.previousAutoSyncedAt)、publishedAtLabel ← 遡及時のみformatJST(entry.lastUpdated) |
DailyNewPost(types/instagram.ts、InstagramPostSchemaをmemberNameで拡張) |
postUrl・thumbnailUrl・isLive・isReel・caption・capturedAt・memberId・memberNameはそのまま対応。isRetroactiveは既存に相当フィールドなし(要調整、下記備考参照) |
|
| TikTok | TikTokPost(types/tiktok.ts) |
postUrl・thumbnailUrl・imageBlobUrl・caption・capturedAt・accountId・channelType・memberId・createdAt・mentionedMemberIdsはそのまま対応。memberIdはchannelType: 'og'のみ存在し、公式アカウントの投稿ではundefined(§5-2参照) |
| YouTube | YoutubePost(types/youtube.ts) |
videoId・title・publishedAt・capturedAt・channelType・memberIdはそのまま対応。memberIdはchannelType: 'og'のみ存在し、公式チャンネルの投稿ではundefined(§5-2参照) |
備考: InstagramのDailyNewPostには遡及収集判定に相当するフィールドが現状存在しない。実装(#1569)で確認した結果、既存の新着判定ロジック(instagramPostsAll = rawDailyNewPosts.filter(...))にも遡及判定は含まれていなかったため、isRetroactive: false固定とした。
備考: memberIdからmembersロースターへのjoinで表示名を解決する方式は、公式アカウント(TikTok/InstagramのaccountIdに相当する疑似ID)がそのmemberId値として使われるケースで破綻する(疑似IDに対応するMemberレコードは存在しないため)。InstagramのmemberNameはこの疑似IDケースの表示名を保持するために実装時に追加した非正規化フィールドで、当初案(memberIdからのjoin一本化)から実装時に修正した経緯がある。TikTok・YouTubeは公式アカウント表示にAPIレスポンス外の固定ラベル(OfficialTikTokAccount.name・「モーニング娘。公式」)を使うため同種の問題は生じない。
備考: findSupportedBlogUrl(sources)はsources配列からAmeba URLを探索するだけで、それが本人の公式登録ブログかどうかは検証しない。officialSnsを見ずにこの関数の戻り値だけで「Ameba投稿」と判定すると、公式Amebaブログ未登録のメンバーが一般Web検索で他人のameblo.jp URLを拾った際に誤判定してしまう(MorningStatusApp #1628で実例が確認され修正済)。hasOfficialAmebaBlogによるメンバー単位の事前フィルタは、この誤判定を避けるための必須の前段ステップである。
5-4. エンドポイント一覧
| エンドポイント | 責務 | 内部で呼ぶ既存関数 |
|---|---|---|
GET /api/feed/ameba |
membersとAmebaSyncStatusから新着ウィンドウ判定・フィルタリングを行いAmebaFeedItem[]を返す |
getMembersFromBlob()、getAmebaSyncStatusFromBlob()(lib/blob.ts) |
GET /api/feed/instagram |
DailyNewPost[]をInstagramFeedItem[]に変換して返す |
getDailyNewPostsFromBlob()(lib/blob.ts) |
GET /api/feed/tiktok |
TikTokPost[]をTikTokFeedItem[]に変換して返す |
getRecentTikTokPosts()(lib/tiktok.ts) |
GET /api/feed/youtube |
YoutubePost[]をYoutubeFeedItem[]に変換して返す |
getRecentYoutubePosts()(lib/youtube.ts) |
各エンドポイントはUnifiedFeedItem[](該当SNSのバリアントのみ)を返す。official/active/og等の表示ブロック分類・ソート順は、既存実装と同様にUI層(クライアント側)の責務として残し、Route Handler側では行わない。キャッシュ戦略は§4-1に従う。
5-5. Ameba専用の非対称な設計
TikTok/YouTubeはgetRecentXPosts()が投稿一覧を独立して返すデータソースだが、Amebaには投稿一覧を返す専用関数が存在しない。現行のNewPostsSection.tsxでは、hasOfficialAmebaBlog(member)(member.officialSnsにAmebaブログのactiveエントリがあるか)で対象メンバーを絞り込んだ上で、members配列のstatusHistory/latestStatusをAmebaSyncStatusの同期ウィンドウ([前々回の定期実行時刻, 直近の実行時刻])でフィルタし、isRetroactivePost()で遡及収集を判定する処理を都度実行している。このhasOfficialAmebaBlogによる事前フィルタは、sources内のURLだけで「Ameba投稿」を判定してしまう誤判定(MorningStatusApp #1628)を避けるために必須であり、Route Handlerへの移植時も省略してはならない。
GET /api/feed/amebaは、この「members取得+ウィンドウ計算+フィルタリング」ロジックを内部にカプセル化する。単純な既存関数のラップでは済まないため、実装時は現行の該当ロジック(NewPostsSection.tsxのAmebaセクション、L98-145付近)をRoute Handler側に移植する。
6. 適用しない理由(備考)
§3で「適用しない」と判定した各ドメインについて、判定理由の詳細をここにまとめる。
6-1. SNS個別画面(Instagram・TikTok・YouTube・Ameba各画面)
各画面(/instagram/[accountId]・/tiktok系・/youtube系・/ameba)は現状いずれも単一画面のみが使用(MorningStatusApp#1635)。
/members/[id]のSNS投稿セクション(#1642のSub-Issue #1653)での利用も検討したが、同セクションが必要とするのは「メンバー個人の全投稿履歴」(例: InstagramはgetInstagramPostsFromBlob(memberId)が返すinstagram/{memberId}/posts.json)であり、既存のBFF層(GET /api/feed/instagram等)が扱う「当日時点の新着投稿ダイジェスト」(getDailyNewPostsFromBlob()が返すinstagram/daily-new-posts.json)とはデータソース自体が異なることが判明した。TikTok(getTikTokPosts()全件 vs getRecentTikTokPosts()直近同期ウィンドウのみ)・YouTube・Amebaも同様の非対称があり、既存エンドポイントの流用はできない。
加えて、同セクションはInstagram/TikTok/YouTube/Amebaの投稿をUnifiedFeedItemのような1つの判別共用体に統合表示するのではなく、InstagramSection・TikTokSection・YoutubeSection・LatestStatusSectionという独立したセクションのまま個別にレンダリングしており(表示順序のみ日付で並び替える)、形状統合の適用基準にも該当しない。以上より、/members/[id]のSNS投稿セクションも適用しないと判断した(MorningStatusApp#1653)
6-2. リリース情報(Neon DB)
単一画面が使う単一形状のデータ。Release型は既にNeon DB専業(旧版の「Vercel Blob / Neon DB」表記は実態と乖離していたため改訂履歴1.2で訂正、MorningStatusApp#1639)
6-3. ユニット情報(Vercel Blob)
単一画面が使う単一形状のデータ(MorningStatusApp#1641)
6-4. ラジオ情報(Neon DB)
一覧・番組詳細・放送回詳細の3階層構成だが、いずれも単一画面が使う単一形状のデータで判別共用体は不要(MorningStatusApp#1637)
6-5. イベント情報(Neon DB)
/events一覧は存在せず/events/[id]単独。/timelineのイベント取得は動的フィルタ駆動のクエリで、本基盤への単純な置き換えでは済まないため対象外(MorningStatusApp#1638・#1643)
6-6. 世代マトリックス(メンバー: Vercel Blob/子供の生年: Neon DB)
世代マトリックス画面のみが使い、他の画面は子供の生年を必要としない。メンバーと子供の生年は、判別共用体として1つの配列に統合するような異種の要素ではなく、Server Component内で結合して年×メンバーのグリッド(派生データ)を算出する入力にすぎない(平均年齢画面の「Server Componentで計算 → 描画」と同型)。グリッドの描画にクライアント側での再取得・キャッシュも要らない。メンバー情報は上記のとおりBFF化済だが、それはメンバー一覧・関係性マップがクライアント側で必要とするためで、本画面は同じデータをServer Componentで直接取得して差し支えない。加えて、子供の生年はシークレットモードの認証後にのみ扱うデータのため、BFFにするとRoute Handler・サーバー側プリフェッチ・dehydrateのそれぞれで認証確認が必要になり、子供の生年を返す経路が増える。Server Componentなら認証判定後にのみ取得する1経路で済む(MorningStatusApp#1632、morning-status-blume#138)
6-7. 過去のこの日セクション(トップ画面、Neon DB)
トップ画面のこのセクションのみが使う。ライブ・フェス・ラジオ・リリースの詳細をOnThisDayEvent(lib/on-this-day.ts)にマージしているが、この統合はunstable_cacheでラップされたServer Component専用関数getOnThisDayEvents()内で完結しており、結果をpropsとしてそのままレンダリングするだけで、クライアントコンポーネントがfetchして消費する経路がない。UnifiedFeedItemは判別共用体をクライアント側がuseQueryで取得し種別ごとに描画を切り替えるためのものだが、本セクションにはその用途がない。元データ(ライブ・フェス等)の編集時はON_THIS_DAY_CACHE_TAGで該当キャッシュを明示的に破棄する運用のため(例: フェス詳細の編集保存時、MorningStatusApp#1652)、既存のサーバー側キャッシュで鮮度は担保できており、クライアント側での再取得・再キャッシュは不要(世代マトリックスと同型)(MorningStatusApp#1644)
6-8. プレイリスト情報(Blob→Neon移行後)
/playlists・/playlists/[id]・/members/[id]選曲セクション・/songs/[id]掲載情報セクションはいずれもServer Componentでの直接取得のままで、クライアント側の再取得・複数画面での共有取得は不要。/playlists/[id]はplaylists・releases・members・unitsを結合するが、これは「結合して派生データを算出する」パターン(世代マトリックス画面等と同型)であり、判別共用体としての統合(適用基準②)には該当しない(MorningStatusApp#1677、morning-status-blume#166)
6-9. ソロアーティスト情報(Vercel Blob)
/solos・/solos/[id]・/members/[id]のソロ活動セクション・/releases/[id]・/songs/[id]・/playlists/[id]はいずれもServer Componentで取得する単一形状のデータで、クライアント側の再取得・複数の形状のデータの統合は不要。ユニット情報(§6-3)と同型(morning-status-blume#167)
見送った対象も、将来複数画面から同じデータが必要とされるようになった場合は再検討する。上記の検討結果は各Issueに「対応不要と判断しクローズ」として記録している(MorningStatusApp#1635・#1637・#1638・#1639・#1641・#1643・#1644・#1646・#1653・#1677)。
7. 関連設計書
| 設計書 | 本書との関係 |
|---|---|
| Amebaブログ機能 データ設計書 | StatusEntry・AmebaSyncStatusの詳細、遡及判定ロジック |
| Instagram投稿機能 データ設計書 | DailyNewPostの詳細 |
| TikTok投稿機能 データ設計書 | TikTokPostの詳細 |
| YouTube投稿一覧機能 データ設計書 | YoutubePostの詳細 |
| データ設計書(全体概要) | エンティティ一覧への登録、データ取得層の設計方針(適用基準)(§4) |
| ライブ・フェス機能 テーブル設計書 | ライブ・フェス情報への展開時のスキーマ・エンドポイント設計(§6) |
| 世代マトリックス 画面設計書 | §3で「適用しない」と判定した機能の画面設計(理由の詳細は§6-6、Server Componentでの取得は同設計書§6) |
改訂履歴
| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.8 | 2026-09-30 | ソロアーティスト情報(Vercel Blob)の適用判定を§3・§6-9に追記。「適用しない」と判定した(morning-status-blume#167) |
| 1.7 | 2026-09-29 | プレイリスト情報(Blob→Neon移行後)の適用判定をthink-issueで検討した結果を§3・§6-8に追記。「適用しない」と判定した(MorningStatusApp#1677、morning-status-blume#166) |
| 1.6 | 2026-09-22 | /members/[id]のSNS投稿セクション(MorningStatusApp#1642のSub-Issue #1653)への適用をthink-issueで検討した結果を§6-1に追記。既存BFF層(新着投稿ダイジェスト)と同セクションが必要とするデータ(メンバー個人の全投稿履歴)はデータソース自体が異なり流用不可であること、各SNS種別が独立したセクションのままで形状統合もしていないことから、適用しないと判断した(MorningStatusApp#1653) |
| 1.5 | 2026-09-22 | 過去のこの日セクション(トップ画面)の判定「適用しない」の行を追加した(MorningStatusApp#1644)。タイムライン画面(/timeline)の判定行(イベント情報)にIssue番号を追記した(MorningStatusApp#1643)。あわせて、§3の適用判定ログが「適用しない」の長い理由を抱えたまま設計内容(§4・§5)より前に置かれ、一覧→設計内容→備考の順で読める構成になっていなかったため、§3を対象・判定・短い参照のみの一覧表に絞り、「適用しない」の理由は新設した§6「適用しない理由(備考)」に移設した(旧§6 関連設計書は§7に繰り下げ) |
| 1.4 | 2026-09-20 | 章構成を再編。SNS新着投稿を軸にした「SNS先行導入の設計(§1〜§5)+SNS以外への展開ログ(§6)」の二段構成を解消し、「適用判定ログ(§3)→共通実装パターン(§4)→SNS新着投稿の個別設計(§5)」に改めた。判定ログは新規機能の設計時に最初に確認・追記する位置へ前出しし、その旨を§1に明記した。あわせて、ログにSNS新着投稿(トップ画面の新着投稿セクション)自体の判定「適用」の行を追加し(従来は「SNS個別画面: 適用しない」の行のみで初導入の判定が読み取れなかった)、世代マトリックスの判定「適用しない」を追加した。旧§3〜§5は新§5・§4に対応する(1.3以前の版の記載にある章番号は旧構成のもの)(morning-status-blume#138、MorningStatusApp#1632) |
| 1.3 | 2026-09-19 | §6の適用基準をデータ設計書(全体概要) §4に昇格(全ドメイン共通の判断基準として一本化)し、本節は展開状況(実績ログ)のみを残す形に整理。ライブ・フェス情報の判定を「適用予定」から「適用(実装済、/lives/prefecture/[code]のみ対象)」に更新。あわせて§1・§2の「他領域への展開」の記述を§4の適用基準に沿う表現に改め、§6のライブ・フェス情報の統合箇所を地図・会場数の集計と明記し、§7にライブ・フェス機能 テーブル設計書を追加(MorningStatusApp#1636) |
| 1.2 | 2026-09-18 | §6「拡張の方向性」に適用基準(複数画面での利用の有無/形状統合の要否)を明記し、各展開候補の判定結果を追記。リリース情報の「Vercel Blob / Neon DB」表記を「Neon DB専業」に訂正(MorningStatusApp#1635・#1637・#1638・#1639・#1641・#1646のthink-issue結果を反映) |
| 1.1 | 2026-09-17 | 実装(#1569)で判明したスキーマ不足を反映。InstagramFeedItemSchemaにmemberName、TikTokFeedItemSchemaにimageBlobUrl・createdAt・mentionedMemberIdsを追加 |
| 1.0 | 2026-09-17 | 初版作成(#90) |