Purpose: Discovery findings and architectural rationale for the news-inappnotification feature.
news-inappnotificationCronService 抽象クラスが server/service/cron.ts に存在。NewsCronService extends CronService のみで cron 基盤が利用可能InfiniteScroll コンポーネントが client/components/InfiniteScroll.tsx に存在。SWRInfiniteResponse を受け取る汎用実装で再利用可能Sidebar/InAppNotification/InAppNotification.tsx が state を管理。フィルタ追加はここへの useState 追加で対応できるuseSWRxInAppNotifications は useSWR(ページネーション)ベース。無限スクロールのために useSWRInfinite 版(useSWRINFx prefix)を新設する必要があるserver/models/in-app-notification.ts, server/routes/apiv3/in-app-notification.ts, server/service/in-app-notification.tsuser フィールドが必須で、配信時点で全ユーザー分のドキュメントを生成するstatus フィールド(UNOPENED/OPENED)は per-user ドキュメントが存在することを前提としており、配信時点でのドキュメント生成が不可避targetModel と action が enum 制約を持ち、ニュースの externalId 管理に使えないsnapshot フィールドにニュース本文を格納した場合、ユーザー数分の本文コピーが発生するserver/service/cron.ts, server/service/access-token/access-token-deletion-cron.tsabstract getCronSchedule(): string と abstract executeJob(): Promise<void> を実装するだけでよいnode-cron を使用。スケジュール変更は getCronSchedule() のオーバーライドで対応startCron() を呼ぶだけで cron が開始されるNewsCronService の実装は最小限で済むclient/components/InfiniteScroll.tsx, stores/page-listing.tsxInfiniteScroll コンポーネントは SWRInfiniteResponse を props で受け取る汎用コンポーネントIntersectionObserver でセンチネル要素を監視し、setSize(size + 1) でページ追加useSWRInfinite のキー命名規則: useSWRINFx* prefixInAppNotificationSubstance.tsx に // TODO: Infinite scroll implemented コメントあり。今回の実装でこの TODO を解消するuseSWRINFxNews と useSWRINFxInAppNotifications を新設し、既存の InfiniteScroll コンポーネントをそのまま利用するSidebar/InAppNotification/InAppNotification.tsx, Jotai atom パターンuseState で管理され、prop として子コンポーネントに渡しているuseState で十分useState で 'all' | 'news' | 'notifications' を管理するuseState で統一するcreatedAt 順、NewsItem は publishedAt 順useSWRInfinite で別々に取得し、各ページのデータをマージしてソートuseSWRInfinite で管理し、表示時にマージするpublic/static/locales/ja_JP/commons.jsonin_app_notification 名前空間に既存キーが存在(only_unread, no_notification 等)ja_JP, en_US, zh_CN, ko_KR, fr_FRnews, all, notifications, no_news)を追加する| Option | Description | Strengths | Risks | Notes |
|---|---|---|---|---|
| サーバーサイドマージ | DB の aggregate で通知+ニュースを JOIN してソート | クライアントが単純 | 異なるモデルの JOIN は複雑、ページング境界の処理が難しい | 採用しない |
| クライアントサイドマージ | 別 API で取得しクライアントで日時ソート | 各 API が独立してシンプル | 「すべて」時は2回 API コール | 採用 |
| ニュース専用ページ | /me/news 等の別ページにニュースを表示 |
実装シンプル | 導線が分散、要件 5.1 に不合致 | 採用しない |
useSWRxInAppNotifications は useSWR ベース(ページネーション)useSWRInfinite ベースの新 hook に切り替えuseSWRINFxInAppNotifications を新設(Option 2)InfiniteScroll コンポーネントは SWRInfiniteResponse を要求する。既存 TODO コメントも無限スクロール実装を示唆しているuseSWRxInAppNotifications は InAppNotificationPage.tsx でも使われているため、両方を維持するNEWS_FEED_URL のバリデーションで https:// を強制growiVersionRegExps の regex が不正な場合 — try-catch でキャッチし、そのアイテムをスキップしてログ記録useSWRInfinite パターン