i260920-01(利用者からの提案)の実装計画。一度サーバー本体 (server.mjs・store.mjs・time.mjs)だけ先に直して動作確認したところ、既存のテストが連鎖的に壊れることが分かった。その洗い出しと対策を先に固めてから、あらためて実装する。
初めての接続 (カーソルが無い状態)の起点を、現行の「参加した時点 (getMaxSeq)」から、「その日の 0 時」と「6 時間前」のうち古い方に変える。深夜早い時間帯 (例: 0 時台)に初めて繋いだ場合、「その日の 0 時」だけだと直近 1 時間程度しか遡らない。6 時間前を下限として併記することで、日をまたいだ直後でも一定の幅を確保しつつ、日中は「その日の 0 時」で当日分をまとめて拾える形になる。
server.mjs の getCursor(...) ?? getMaxSeq(...) という起点ロジック (handleJoin・handlePoll の 2 か所)。getMaxSeq(roomId) の代わりに、新しい起点 (「0 時」と「6 時間前」の古い方より前の最後の msg_seq)を返す関数に差し替える。
| 関連する既存課題 | 内容 |
|---|---|
| i260909-01(済) | 現行の「参加した時点=getMaxSeq」という設計そのものを扱った課題。読んでいても初回は踏むという報告を受け、wait 起動時の案内 (recent の見本を出す)を足す形で対応済み。今回の提案は、この設計自体を変える恒久対策の位置づけ |
| i260908-01(済) | getCursor(...) ?? getMaxSeq(...) という起点ロジックの実装箇所を扱ったバグ修正。今回変更するのは同じ箇所 |
テストスイート全体が「初参加=いまから (未来分のみ)」という前提でルームを使い回している。実装を入れた瞬間、既存テストが連鎖的に壊れた。public ルームだけでもテスト内で数百件の発言がすでに積まれており、新しい参加者が「6 時間前・当日 0 時のどちらか古い方」から読むと、他のテストが積んだ過去分をすべて未読として受け取ってしまう。
実際に確認できた壊れ方の例 (tests/server.test.mjs)。
| テスト名 | 壊れ方 |
|---|---|
| since を省略すると、参加した時点から待つ | 「過去のぶんは返らない」の前提が崩れ、それまでのテストが積んだ 27 件が pending/messages に出る |
| 位置はルームごとに別々 | 同様に「dev は初めてなので今から」が崩れる |
この 2 件以外にも波及する可能性が高い(未確認)。新規 connector_id で join/poll を呼び「空で返る」ことを前提にしているテストは、テストファイル全体でいくつあるか数え直す必要がある。
これまでの初回起点 (getMaxSeq=いま)は、時刻に依存しない値だったため、テスト同士が同じ public ルームを共有していても互いに干渉しなかった。今回の変更は時刻ベースの起点を持ち込むため、「同じルームを使い回すテスト設計」自体と相性が悪い。
案を 2 つ比較する。
| 案 | 内容 | 長所 | 短所 |
|---|---|---|---|
| A (推奨) テストごとに専用ルーム | 初回起点の影響を受けるテストは、room_id をテスト固有の使い捨て名にする (他のテストの発言と混ざらない) | 本番のロジックには手を入れない。テストの独立性が上がる (本来望ましい設計) | 該当テストの洗い出しと書き換えが要る (件数次第で作業量が増える) |
| B テスト用に基準時刻を差し替え可能にする | initialSeq が使う「いま」を注入可能にし、テストから「6 時間より前」を明示的に作る | ルームの使い回しを変えずに済む | 本番コードにテスト専用の分岐が増える。「同期 API は〜」と同種の、プロダクションコードへのテスト都合の混入になりやすい |
A を採用 (利用者の承認済み)。理由: 本番ロジックを単純に保てること、そして「テストが同じルームを共有して過去ログに依存する」設計自体が、今回の機能追加とは無関係にも脆さを抱えていたため (ついでに直す価値がある)。
tests/server.test.mjsを通しで読み、「新規 connector_id で参加 → 空を期待する」テストを洗い出すroom_id を明示していないもの (=既定の public を使っているもの)に、そのテスト専用の room_id を明示する。命名は既存の使い捨てルームの慣習 (sandbox-other・sandbox-archived)に合わせ、sandbox-cursor-* にした(sandbox-cursor-empty・sandbox-cursor-recent・sandbox-cursor-room2)public・dev など複数テストで共有されているルーム名は、よほど「共有されていることを検証したい」テスト (在席・複数ルーム待受け等)以外では使わない方針にするルームを分けただけでは足りなかった。「初参加は空」と主張していた既存テストの前提自体が、今回の機能追加 (直近分は初参加でも届く)と正面から矛盾する。ルームを分離したうえで、アサーションを新しい仕様 (直近の投稿は初参加でも届く)に合わせて書き換えた。「空のルームでは、初めての参加でも何も届かない」 (本当に何も無い場合)と、「直近に投稿された分は、初めての参加でも届く」 (i260920-01 の本題)の 2 本に分けている。
| ファイル | 変えること |
|---|---|
src/server/time.mjs | jstTodayMidnight() を追加 (その日の 0 時を nowJst と同じ書式で返す) |
src/server/store.mjs | getSeqBefore(roomId, ts) を追加 (ts より前の最後の msg_seq。1 件も無ければ 0) |
src/server/server.mjs | initialSeq(roomId) を追加し、handleJoin・handlePoll の getMaxSeq(...) フォールバックを置き換える |
tests/server.test.mjs・tests/client-wait.test.mjs | 2 章の対策 A に沿って、影響を受けるテストのルームを使い捨て名にする |
notes/40_issues/issues.html | 実装後、i260920-01 を完了に更新 |
境界 (ちょうど 0 時・ちょうど 6 時間前)専用の単体テストは見送った。時刻を差し替え可能にするテスト専用フックを本番コードに足すと、3 章で退けた「案 B」 (テスト都合の分岐を本番に混入させる)に逆戻りするため。getSeqBefore 自体は ts を素直に受け取る関数なので、境界の正しさ (sent_at < ts)はコード読解で確認できる。