HARUMI
戻る

2026 / 01

MakeUp — ふたりのための関係性 OS

Personal product個人開発・運用

成果単一のイベント配信層を作り、新機能が永続化・リアルタイム更新・Web Push を毎回実装し直さずに再利用できるようにしました。

概要

恋人・夫婦・友人・家族——親しい二人のためのフルスタックアプリ。共有カレンダー、気分共有、謝罪、催促——すべての機能がイベントバス経由で通知を自動的に得られる設計。

背景

各機能から同じ 3 つの通知経路へ届けながら、配信コードの重複を避ける必要がありました。

システム

One Kafka topic, one consumer, three delivery pathsMoodCalendarReminderAnniversarymakeup-eventsKAFKA TOPICEventConsumerPersistSSEWeb Push01 TOPIC01 CONSUMER03 DELIVERY PATHS
MakeUp card-appearance editor, drawn abstractlyCARD APPEARANCEPREVIEW · NO FILL SELECTEDUPLOAD IMAGELOCAL PREVIEW06 / 08

課題

新機能(気分、心願、記念日、催促)はすべて同じ 3 つの処理——通知の永続化、開いているタブへの SSE 配信、アプリが開いていない場合の Web Push——を必要としていました。機能ごとに個別実装すると同じ配線を何度も繰り返すことになります。

調査

最初の仮説

隠れたモーダルは z-index を上げれば直ると考えました。

単一の Kafka トピック(makeup-events)が全てのドメインイベントを運びます。1 つのコンシューマーが発信者情報を付与し、永続化し、SSE で配信し、Web Push サービスを呼び出す——新機能は eventPublisher.publish() を呼ぶだけで通知スタック全体が手に入ります。

リマインダーの重複排除

後から追加したカレンダーの期限切れ項目と記念日を毎日スキャンするジョブも、別経路を作らず同じパイプに乗せました。(発生源, 基準日) をキーにした小さなリマインダーログテーブルで重複排除しており、「まだ期限切れ」の継続通知と一回だけの記念日通知は、キーが違うだけの同じ仕組みです。

証拠

document.elementsFromPoint(x, y)

描画順を調べると、opacity が作った stacking context の中にモーダルが閉じ込められていました。

判断

配信は 1 つのイベントコンシューマーに集約し、モーダルは document.body へ直接 portal しました。

結果

同じ流れで実際の本番バグも修正しました。フロー画面下部に浮かぶモーダルが、ボトムナビゲーションの下に隠れて描画されていた問題です。原因は z-index の数値ではなく、別の場所にあったページ遷移用フェードラッパーの opacity アニメーションが暗黙的に独自の CSS スタッキングコンテキストを作り、内部の fixed 要素を z-index に関係なく閉じ込めていたことでした。ヘッドレスブラウザの document.elementsFromPoint() で確認し、モーダルを document.body へ直接ポータルすることで解決しました。

次に変えること

現在の制約: リマインダーは遅延イベントではなく、定期スキャンに依存しています。

次の一手: 通知経路ごとの配信失敗を観測できるようにします。

関連する記録

opacity が fixed 要素を閉じ込めていた

技術構成

Spring Boot · Next.js PWA · PostgreSQL · Kafka · Web Push

次のプロジェクト

NOEMA — 内省ライフシステム