@aiquants/daily-report 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,112 @@
2
2
 
3
3
  All notable changes to `@aiquants/daily-report` are documented here.
4
4
 
5
+ ## 0.34.0 (2026-10-07)
6
+
7
+ 0.33.0 の続き: コメントの作者を ID で決めること、誤りの知らせを時間で消さず何も覆わない流れの中に置くことと、モバイルのオーバーレイの中に描くこと、自分を外す再試行ボタンのフォーカスとストリームの状態の読み上げ、端の行の `Home` / `End`、照合用 ID の衝突と消えた日報の削除、期限のある台帳と操作ごとの錠、1/8 の倍数の画素密度の装置の画素の格子、action と API のワイヤの契約の公開と ID の 1 つの定義域、キャッシュを迂回する読み取りの合わせ方と上限、閲覧者ごとのストリームの枠、JSON の応答の記録、対を持たないサロゲートのファイル名。どれも README の該当の節が今の振る舞いを述べる。
8
+
9
+ ### Fixed
10
+
11
+ - **コメントの作者は ID で決める**: SSE で届いたコメントは、作者の ID が表示中ユーザーの ID (`userId`。`encodeUserId` のポートの値) と等しいときだけ閲覧者自身のもの。投稿中の仮のコメントの id を本物の id へ解決するのは、その投稿自身の答えだけ。
12
+ 以前は本文の一致で決めたので、ほかの利用者が投稿中のコメントと同じ本文を書くと、そのコメントを自分のもの (`isMine: true`) としてキャッシュし、投稿中の仮の id を相手のコメントの id へ解決した。その後の投稿中のコメントの削除は相手のコメントへ向かって `Unauthorized` で断られ、自分のコメントは残って誤りを知らせた。自分の別のタブや端末のコメントは他人のもの (`isMine: false`) になり、読み直すまで削除できなかった。
13
+ - **誤りの知らせはモバイルのオーバーレイを開いている間も操作できる**: 内蔵の知らせ (`role="alert"`) は、オーバーレイを開いている間はダイアログの中 (日報と閉じるボタンの間) に描き、閉じた後はページの置き場へ戻る。以前は知らせをプロバイダーの子の隣に描いたので、オーバーレイのモーダルな `<dialog>` の外で不活性になり、読み上げの木にも無く、押すことも閉じることもできなかった (パッケージの知らせを使うホストでは、オーバーレイの中のどの失敗も見えないままだった)。
14
+ - **誤りの知らせは時間で消えず、何も覆わない**: 知らせは利用者が閉じるボタンか、中にフォーカスがある間の `Escape` で消すまで残り (WCAG 2.2.3)、新しい失敗は文言を置き換える。`Escape` は知らせだけを消し、オーバーレイを閉じない。フォーカスを持ったまま消えるときは、来た元へ、無ければビューの行カーソルへ、それも無ければ知らせを置いたダイアログへ戻す。
15
+ 知らせは流れの中の置き場 (ページの下・オーバーレイの中) に描くので、フォーカスを持つ行にもボタンにも重ならない。以前の知らせは 8 秒で消え (フォーカスを持っていれば body へ落ちた)、`Escape` は効かず、下端に固定したトーストとして末尾に揃えた DetailList の行の 28 px とアウトラインの下の縁を覆った。
16
+ - **自分を外す再試行ボタンはフォーカスを落とさず、ストリームの状態は読み上げる**: 見出しの進捗の札は局面ごとに中身だけを替える 1 つの入れ物で、ページの読み込み中と読み込みの失敗は中身だけを替える 1 つの状態の面。どちらの再試行も、押されて自分が外れる前に入れ物か面へフォーカスを渡し、面がフォーカスを持ったまま本体へ入れ替わるとビューの行カーソル (無ければページの状態の領域) へ渡す。
17
+ ページの常設の見えない状態の領域が、読み込み中と再接続中を `role="status"` で、失敗を `role="alert"` で知らせる。以前はどちらの再試行もフォーカスを body へ落とし、データ未受信の読み込みの失敗は読み上げの領域を持たず、読み込み中の唯一の領域 (再接続中の札) は同じ確定で外れた (WCAG 2.4.3、4.1.3)。
18
+ - **選択されていない端の行の `Home` / `End`**: フォーカスがもう先頭 (末尾) の行にあり、その行が選択されていなければ、`Home` (`End`) はその行をその場で選ぶ (両ビュー)。以前は何もしなかったので、選択中の日報を消して末尾の行にフォーカスだけが残ると、`End` を押しても側面ペインは選択を促す表示のままだった。矢印と `PageUp` / `PageDown` は、端で動かなければ今までどおり何も選ばない。
19
+ - **同じミリ秒の 2 つの操作は別の照合用 ID を持つ**: 楽観的な日報とコメントの仮の id (その 10 進の文字が照合用 ID) は、ページに 1 つの列から、前に引いた値より必ず小さく、壁時計のミリ秒の負の数以下の値を引く (`drawTemporaryId`)。以前は `-Date.now()` だったので、同じミリ秒の 2 つの操作が 1 つの照合用 ID を分け合い、エコーと失敗を別の操作に結び付けることがあった。
20
+ - **もう無い日報の削除は済んだものとみなす**: 日報の削除に 404 が返ったら、巻き戻しも誤りの知らせもせずに、キャッシュと編集の項目を捨てる (一覧の行は外したまま)。ほかの操作の 404 は今までどおり巻き戻して知らせる。
21
+ - **キャッシュを待つ SSE の知らせは期限で必ず刈る**: 自分の操作のエコーの抑止と、キャッシュをまだ持たない日報への知らせの待ちは、どちらも期限のある 1 つの台帳 (`createExpiringLedger`) が持ち、どの日報の足し・読みでも期限の過ぎた項目を先頭から外して、待ちのキャッシュの購読もやめる。以前の待ちは、同じ日報の次の知らせ・キャッシュの書き込み・日報の削除・アンマウントでしか期限を確かめなかったので、キャッシュされない 50 件の日報への知らせが 50 件の購読を残し、期限の後も 51 件が残った。
22
+ - **操作の錠は操作ごと**: `acquireMutationLock` は錠ごとの返し方を返し、返し方はその錠だけを 1 回だけ返す。SSE のコメントの変更は錠に触れずに変更の時刻だけを記録し (`markReportMutated`)、作成の後の 1 秒の錠は後の錠で延びるだけ。以前は日報ごとに 1 つの錠で、SSE のコメントや重なった操作が進行中の操作の錠や作成の後の錠を外した。
23
+ - **1/8 の倍数の画素密度でも行とタイルは装置の画素に乗る**: 装置の画素の格子の定義域は 1 から 3 までの 1/8 の倍数のすべて (412 px 幅の多くの Android の端末の既定 2.625 を含む)。中身が高さを決める箱 (DetailList の行の本体・誤りの知らせ) と添付のタイルの寄せは、格子のブロックの刻み (カスタムプロパティ `--aqdr-lattice-block`。1/4 の倍数で 4 px、1/8 の奇数倍で 8 px) へ切り上げ、ホバーの浮き上がりは 12 個の画素密度で整数の装置の画素へ上書きする。
24
+ 以前は 1/4 の倍数だけを定義域にしたので、2.625 では 4 の奇数倍の行やタイルの段の下のすべてが装置の画素の半分ずれ (980 px の行の下端が 6,478.5 装置画素、オーバーレイのタイルの段の送りが 188 CSS px = 493.5 装置画素)、面の縁が 2 行の装置の画素へにじんだ。
25
+ - **対を持たないサロゲートを含むファイル名の添付も配る**: `Content-Disposition` の `filename*` は名前の整った形 (対を持たないサロゲートは U+FFFD) の UTF-8 を符号化し、行から決まるヘッダーの部品は読み取りの前に作る。以前は符号化が `URIError` を投げ、原本を上限まで読んだ後に 500 (`URI malformed`) で答えたので、取込元の切り詰めでサロゲートの対が割れた名前の添付は、どの要求でも配れなかった。
26
+ - **JSON の応答は要求ごとの記録を書かない**: `jsonResponseWithETag` はログを書かない純粋な組み立てで、JSON の答えの跡は API の経路が工場のロガーへ書く `debug` の 1 行 (`<状態> endpoint=<名前> etag_match=<true|false>`) だけ。要求のヘッダーの値も、エンドポイントより前の答え (401・404・405) も書かない。
27
+ 以前はどの JSON の応答も、未認証の 401 を含めて、モジュールの console のロガーへ `info` の 1 行を `If-None-Match` の値ごと書いたので (16,000 バイトの `If-None-Match` で 16,125 バイトの行)、未認証の送り手が行の数も大きさも決められ、ホストのロガーでは行き先も量も変えられなかった。
28
+
29
+ ### Changed
30
+
31
+ - **キャッシュを迂回する読み取りはキーごとに合わせる**: `SqlResultCache` の迂回の取得 (`forceRefresh`) は、キーごとに走る取得 1 本と待つ取得 1 本までで、同時に届いた迂回の読み取りはいくつでも多くとも 2 回の取得で答え、どの呼び出しも届いた後に始まった取得の結果を受け取る。走る迂回の取得には迂回しない読み手も相乗りし、その間に無効化されたら結果をバケットに書かない。以前の迂回は取得を合わせず登録もしなかったので、同時の 20 本の迂回は 20 回の問い合わせになった。
32
+ - **action のレートの上限は共有の留まる時間から導く**: 側面ペインの自動既読の留まる時間 (`AUTO_READ_DWELL_MS` = 500 ms) をクライアントとサーバーが 1 つの定義から読み、上限は留まるペインの余裕 5 × ⌈60,000 ÷ 500⌉ = 600 (値は変わらない)。留まる時間を縮めれば上限も上がる (以前はサーバーが 500 を書き写していたので、クライアントだけを縮めると、留まった複数のペインの自動既読がどの spec も通ったまま 429 になった)。上限はホストの設定にしない。
33
+ - **合わない答えは合わなかった欄を名乗る**: パッケージのクライアントは、送った操作の答えでない action の答えを `[daily-report] <intent> answered a body that is not its result (<パス>: <code>; …)` で、エンドポイントのスキーマに合わない答えを `… does not match its schema (<パス>: <code>; …)` で拒む (受け取った値は書かない)。以前はどちらもパスを名乗らなかった。
34
+ - **ids のストリームのクエリは 1 組の符号化と解釈**: クライアントの再開と強制の再取得の URL も、サーバーの解釈も、共有の表のクエリの名前を 1 組の関数で書き・読む。片方だけの改名は往復で落ちる (以前はクライアントが 4 つの名前を字面で書いたので、改名はどの spec も通ったまま、どの再開も先頭からの全量の再送にした)。
35
+ - **開発者向け**:
36
+ - ID のワイヤの定義域は `src/shared/ids.ts` (`isPositiveSafeInteger`・`dailyReportIdSchema`) の 1 つで、クエリとフォームの文字の解釈 (`parseCanonicalPositiveId`・`parseCanonicalNonNegativeInteger`・`parseWireBoolean`・`parseForceRefreshParam`) は `src/server/wire-params.ts` から `src/shared/wire-text.ts` へ移った。SSE のメッセージのどの ID の欄も同じ定義域で読む (以前の素の `z.number()` は 0・負の数・小数を通した)。合わない値の説明は `src/shared/wire-mismatch.ts` (`describeWireMismatch`)。
37
+ - サーバーの action の 200 の答えは、指示から組む 1 つの部品 (`answerActionCommand`) だけが組む。
38
+ - 同時実行のゲートは `src/server/attachment-delivery/concurrency-gate.ts` から `src/server/concurrency-gate.ts` へ移り (名前と引数の形は同じで、枠を取らない確かめ `canAcquire(owner)` を足した)、過負荷の 503 の `Retry-After` (`retryAfterHeaders`) は `src/server/response.ts` へ、整数の設定の検証 (`requireIntegerSetting`・`resolveIntegerSetting`) は `src/shared/config-errors.ts` へ移った。`src/server/rate-limit.ts` はどの機能のフォルダーも読まず、拒否の理由の名前は各経路が自分で決める (`RateLimitReason` は無い)。
39
+ - 追加した行の網羅率の検査と値の置き換えの検出は、リポジトリの共有のモジュール (`.config/scripts/lib/changed-lines-coverage.mjs`・`.config/scripts/lib/no-fallback.mjs`) に 1 つずつあり、パッケージの `scripts/` は自分の免除と例外のファイルを渡す薄い入口。値の置き換えの検出は右辺の関数の呼び出し・値の作成・タグ付きテンプレートも数え (`a ?? f()` と `a || f()`)、レビューした例外は `scripts/no-fallback-exceptions.json` に置く。
40
+ - docstring とコメントの言語の検査は本番のコードを違反 0 に保ち、基準 (`docstring-baseline.json`) に本番のコードの項目を許さない (数を数えるのは spec とテスト用の部品だけ)。
41
+ - 共有エントリ (公開した名前) とサーバーエントリ (迂回の合わせ方・ストリームの枠) のバンドルの基準を、見直した増加として記録した。
42
+
43
+ ### Added
44
+
45
+ - 共有エントリ (`@aiquants/daily-report`) が action と API のワイヤの契約を公開する: `DAILY_REPORT_ACTION_INTENTS`・`DAILY_REPORT_ACTION_FIELDS`・`encodeDailyReportActionCommand`・`parseDailyReportActionResult` (`{ result }` か、合わなかった欄の説明の `{ mismatch }` を返す)・`DAILY_REPORT_API_ENDPOINTS`・`DAILY_REPORT_API_QUERY_PARAMS`・`DAILY_REPORT_CLIENT_TEMP_ID_PATTERN` と、型の `DailyReportActionIntent`・`DailyReportActionCommand`・`DailyReportActionResult`・`DailyReportActionResultOf`・`DailyReportActionFailure`。
46
+ パッケージのクライアントの外から action と API を呼ぶホストは、手で書き写した 2 つ目の契約の代わりにこれを読む。0.33.0 の Breaking の移行の手引きが答えを読む型として挙げた `ActionResult` は、この `DailyReportActionResult` (以前は公開していなかった)。
47
+ - server の `readDailyReportSseStreamEntry` (Redis のストリームのエントリ 1 件を、共有リーダーと同じく 1 度だけ読む。自分のリーダーを渡すホストのため)。
48
+ - キャッシュを迂回する読み取りの閲覧者ごとの上限: `business-date`・`report`・ids のストリームの `forceRefresh=true` は、閲覧者 1 人あたり 1 分に 60 件まで (ハンドラー工場ごと、つまりワーカーのプロセスごと。認証が返す外部の利用者 ID で数える)。上限はパッケージのクライアント自身の迂回 (ビュー 5 つ × 強制の再走査 2 回 × 要求 6 回) から導く。
49
+ 超えた要求は引数の 400 の後、どのサービスの呼び出しよりも前に 429 (`{"error":{"message":"Too many requests"}}`、`Retry-After: 60`) で断る。行は閲覧者の窓ごとに最初の `429 endpoint=<名前> reason=refresh_rate_limit` と、窓の終わりの `refresh_rate_limit_suppressed count=<N> since=<…> route=api` の 2 行まで (warn。既定のロガーの接頭辞は `[DailyReportAPI]`。利用者 ID を書かない)。
50
+ - 閲覧者ごとのストリームの枠: 閲覧者 1 人がワーカーのプロセス 1 つで同時に持てる SSE と ids のストリームは合わせて 32 本 (`DAILY_REPORT_STREAMS_PER_VIEWER`: ビュー 1 つの 2 本 × 16 ビュー)。枠をすべて持つ閲覧者の次のストリームは、本体の仕事を始めずに 503 (`Retry-After: 5`。SSE は本文 `Too many streams`、ids のストリームは `{"error":{"message":"Too many streams"}}`) で断り、枠はストリームが終わる後始末で返す。
51
+ `HEAD` は枠を取らずに確かめるだけ。行は閲覧者の窓ごとに最初の `503 <sse|ids-stream> viewer=<内部 ID> reason=streams_per_viewer` と、窓の終わりの `streams_per_viewer_suppressed count=<N> since=<…> viewer=<内部 ID> routes=sse:<n>,ids-stream:<n>` の 2 行まで (warn。既定のロガーの接頭辞は `[DailyReportStreams]`)。以前は 1 人の閲覧者の 2,000 本の同時の SSE をどれも受け、それぞれが 60 秒ごとに可視集合を解決し直した (毎秒約 33 回の認可の問い合わせ)。
52
+
53
+ ### Breaking
54
+
55
+ - action の 200 の答えの日報の ID (`reportHubId`) と消したコメントの ID (`deletedCommentId`) は、SSE のイベントと同じ JSON の数 (1 から 2^53 − 1 までの整数)。以前は 10 進の文字だった。
56
+ Migration: 答えを自分で読むホストは、2 つの ID を数として読む (`parseDailyReportActionResult` で読めば型が合う)。答えを返す代役 (テストの stub など) は ID を数で返す。
57
+ - server の `jsonResponseWithETag(request, cookie, payload, status = 200)` は 5 番目の引数 (ロガー) を持たず、ログを書かない。
58
+ Migration: ロガーを渡していた呼び出しは引数を外し、必要な記録は呼び出し側が自分のロガーで書く。
59
+ - キャッシュを迂回する読み取りの閲覧者ごとの上限 (上の Added): 1 人の閲覧者が 1 つのワーカーへ 1 分に 60 件を超えて `forceRefresh=true` を送ると 429。
60
+ Migration: パッケージのクライアント以外から `forceRefresh=true` を送るホストは 429 を失敗として扱い、`Retry-After` の後に送り直す。
61
+ - 閲覧者ごとのストリームの枠 (上の Added): 1 人の閲覧者が 1 つのワーカーで 32 本を超えて SSE と ids のストリームを同時に開くと 503。
62
+ Migration: パッケージのクライアント以外からストリームを開くホストは、使い終えたストリームを閉じ、503 を一時的な失敗として `Retry-After` の後に開き直す。
63
+ - ピアの `@aiquants/virtualscroll` の下限を 3.14.0 へ上げた (`peer-floors.json`)。両ビューが 3.14.0 の変更に頼る所は無いが、この版の試験はすべて 3.14.0 の上で通したので、公開する範囲 (`^3.14.0`) と文書の下限をそれに合わせた。
64
+ Migration: `@aiquants/virtualscroll` を 3.14.0 以降へ上げる。
65
+
66
+ ## 0.33.0 (2026-10-07)
67
+
68
+ 0.32.0 の続き: 一覧の末尾の近くの削除で錨の日報が跳ばないこと、action の照合用 ID を 2 つの正準の形だけにすること、SSE のエントリをプロセスで 1 度だけ読むことと 1 回の読み取りをバイトの予算で縛ること、action の閲覧者ごとのレートの上限、ids ストリームのアイテムの publish をトランジションの優先度で届けることと、見出しの件数とビューの本体を配置の境界にすること、action と JSON のエンドポイントの共有のワイヤの契約、後で呼ばれるタイマーを持たない期限の記録、原本の HEAD の大きさの契約。どれも README の該当の節が今の振る舞いを述べる。
69
+
70
+ ### Fixed
71
+
72
+ - **一覧の末尾の近くで窓より前の日報が消えても、錨の日報は跳ばない**: 末尾の近くで窓より前の日報が消えて中身が縮むと、スクロールの領域は位置を小さくなった最大位置へ動かす (クランプ)。両ビューのスクロールの錨は、VirtualScroll がそのクランプを `onScrollAdjust` の `"clamp"` で知らせると (`@aiquants/virtualscroll` 3.13.0)、錨を記録したまま保ち、記録した位置だけをクランプの `delta` だけ動かすので、一覧の変化の後に錨の日報を同じ px へ戻す。
73
+ 以前はクランプを利用者のスクロールと数えて動いた眺めを保ったので、DetailList は末尾から 42 px 上で 302 px、末尾ちょうどで 344 px 跳んだ (一覧の途中では跳ばない)。`"clamp"` を知らせない版の VirtualScroll の上では、クランプは知らされず以前と同じに振る舞う。
74
+ - **action の照合用 ID は 2 つの形だけ**: `clientTempId` は、パッケージのクライアントが作る小文字の UUID (`crypto.randomUUID()`) と、先頭に 0 を持たない 16 桁までの負の 10 進の整数 (`String(-1 * Date.now())`) だけを受け、ほかは 400 (`Invalid clientTempId`。無いか空なら従来どおり `clientTempId required`) で、どのサービスも呼ばない。SSE のどのメッセージの照合用 ID も同じ形で読み、形の外の ID を持つエントリは配らない。
75
+ 以前は空でない文字列ならフォームの上限 (1 MiB) まで受けたので、約 1 MiB の ID を付けたスターの切り替えが 200 で答え、約 1 MiB の状態のイベントを永続の SSE のストリームへ書いて、接続中のすべての閲覧者と再接続の追いつきへ届けた。
76
+ - **原本の HEAD も大きさの上限の契約を確かめる**: 読み取りポートが HEAD に `attachments.maxBytes` を超える `size` を申告したら、同じ実体の GET と同じポートの契約違反の 500 (`reason=port_contract code=over_max_bytes`、error) で答え、`Content-Length` を付けない。以前は HEAD だけが申告をそのまま `Content-Length` にして 200 で答えた。
77
+ - **宣言の無い上限超えの本文の運ばれ方を文書が正しく述べる**: 宣言した長さが上限を超える本文には 413 が届くが、宣言の無い (チャンク転送の) 本文と長さを偽る本文は、数えた長さが上限を超えたところで要求の本文を取り消し、Node のアダプターでは接続が閉じる (413 は届かない。413 を届けるには、上限の無い大きさの残りを読み切ることになる)。振る舞いは変わらず、数えた拒否は従来どおり warn の行に残る。以前の README と docstring は、どちらにも 413 が届くと述べていた。
78
+ - **後で呼ばれるタイマーを予約しない**: 作成した日報の錠 (1 秒)、自分の操作のエコーの抑止 (30 秒) と、キャッシュをまだ持たない日報への SSE の知らせの待ち (30 秒) は、どれも単調な時計の期限で読み、タイマーを使わない。待ちの購読はアンマウントの確定で解く。以前は錠を外す 1 秒のタイマーとエコーごとの 30 秒のタイマーを張り、プロバイダーがアンマウントした後にも走りえた (待ちの期限のタイマーと購読は受動的な副作用の後始末で止めていた)。
79
+ - **クライアントは action の答えと JSON のエンドポイントの答えを厳格に読む**: action の答えは送った操作の答えの形 (共有の `parseActionResult`) でなければ失敗として巻き戻し、日報の ID を送られたままの 10 進の文字で読む (以前は手書きの型で数と読み、`String()` / `Number()` で食い違いを隠した)。2xx でない答えは状態を持つ誤りで拒む。`report` と `business-date` の答えは厳格なスキーマで読み、合わなければ `Error` で拒む (以前は型の主張と `?? []` で読み、文字列で拒んだ)。
80
+ - **CommonJS のバンドルに死んだホットモジュールの分岐と、ビルドの警告が無い**: ids ストリームのセッションのホットモジュールの後始末は `import.meta.hot?.dispose(destroySession)` の 1 行で、CommonJS の形式はビルドの時に `import.meta.hot` を `undefined` に置き換えて畳む。以前の CommonJS のバンドルは常に偽になる判定を持ち、ビルドのたびに `import.meta` の警告を 3 つ出した。
81
+
82
+ ### Changed
83
+
84
+ - **SSE のエントリはプロセスで 1 度だけ読む**: 共有リーダーはライブのエントリ 1 件を読んだときに 1 度だけ解釈し検証して、凍結した同じ値を全接続へ渡す。接続ごとの仕事は読み終えた値の絞り込みと、メッセージごとに 1 度だけ作るフレームの字面の選択だけで、書く字面は以前と 1 バイトも違わない。以前は接続ごとに解釈・検証・直列化し直したので、1 MiB のイベントで接続 1 つあたり約 1.6 ms、200 接続で約 311 ms をイベントループで使った。
85
+ - **SSE の 1 回の読み取りはバイトの予算で縛る**: 共有リーダーの `XREAD` と catch-up の 1 ページの件数は、64 MiB の予算を 1 件のイベントの上限で割った 10 件 (`DAILY_REPORT_SSE_CATCH_UP_BATCH`)。以前の 100 件と 1,000 件は件数だけの縛りで、最大のイベントばかりのページは約 5.9 GiB を溜めえた。小さなイベント 1,000 件の catch-up は 113 往復になる (往復の時間 0 で約 95 ms、1 ms で約 225 ms)。
86
+ - **ids ストリームのアイテムの publish はキーより後に描く**: アクションのプロバイダーは一覧を 1 つの購読で追い、版か落ち着きが変わったときだけ `startTransition` の中で描き直すので、行の導きとビューの行の数はトランジションの優先度で描かれ、ストリームの間に押したキーは publish の描画より先に確定する。ページの根・SSE の接続・進捗の札・行の集合の大きさは、読む項目だけを持つ見え方を読み、アイテムの publish では描き直さない (本番の React で、4 回の publish でページの根も SSE の接続の部品も 0 回)。公開の `useDailyReportIdsStream` は今までどおり状態の全体を返す。
87
+ - **見出しの件数とビューの本体は配置の境界**: 見せている間に変わる件数の文字 (総件数と走査中の件数) は、幅を見えない寸法の札が取る閉じ込めた箱に置くので、文字の変化は箱の中だけを配置し直す。走査中の件数は publish された値をそのまま見せる (補間しない。総件数は従来どおり補間し、動きを減らす設定に従う)。両ビューの根要素の中に、大きさ・配置・スタイルを閉じ込めたビューの本体の箱があり、行の数やスクロールバーの変化の配置はそこから始まる。List のモバイルのオーバーレイは本体の外に置く。以前は ids ストリームの間、40 回のキーの長押しごとに文書の根からの配置が 34〜51 回 (35〜54 ms) 起きた (ストリームの無いときは 0〜1 回)。
88
+ - **新しいコメントのフォームは隠しの欄を持たない**: 送信は投稿の処理だけが運ぶので、使われない `intent`・`reportHubId`・`businessDate` の隠しの欄とそのための props を除いた。本文の欄は欄自身のウィンドウの `HTMLInputElement` として読む。
89
+ - **開発者向け**: action と JSON のエンドポイントのワイヤはサーバーとクライアントが共有する定義から読む (`src/shared/action-wire.ts`: 操作の名前・欄の名前・指示とその符号化・操作ごとの答えとその厳格な解釈。`src/shared/api-endpoints.ts`: エンドポイントとクエリの引数の名前)。照合用 ID の形は `src/shared/client-temp-id.ts` の 1 つ。SSE のエントリの 1 度だけの読み取りとフレームの字面は `src/server/sse-entry.ts`、読み取りの予算は `src/server/payload-limits.ts` (`SSE_READ_BUDGET_BYTES`・`SSE_READ_ENTRY_COUNT`)。
90
+ レートの制限とバケットは `src/server/rate-limit.ts` へ移り (`attachment-delivery/rate-limiter.ts` は無い)、添付の経路と action が同じ 1 つの仕組みを使う。`pnpm run verify` は網羅率の段の後に、前の公開のタグから足した `src` の行がどれかの spec で走ることを確かめ (`scripts/check-changed-lines-coverage.mjs`。免除は `scripts/changed-lines-coverage-exemptions.json`)、ビルドの警告はどれもビルドを落とす (`scripts/lib/build-warnings.mjs`)。
91
+ クライアントの後で呼ばれるコールバック (タイマー・フレーム・リスナー・監視) を張る場所は、どれも理由付きで `src/client/deferred-callback-scope.spec.ts` に載る。action の `executeAction` は共有の指示を受け、`keepLock` の選択肢は無い。
92
+
93
+ ### Added
94
+
95
+ - server の `parseClientTempId` と、それだけが作る型 `DailyReportClientTempId` (照合用 ID の解釈。サービスを直接呼ぶホストが ID を読む。下の Breaking)。
96
+ - action の閲覧者ごとのレートの上限: 1 分に 600 件 (ハンドラー工場ごと、つまりワーカーのプロセスごと。認証が返す外部の利用者 ID で数える)。超えた要求は本文を読む前に 429 (`{"error":"Too many requests"}`、`Retry-After: 60`) で断り、どのサービスも呼ばない。行は閲覧者の窓ごとに最初の `429 action reason=rate_limit` と、窓の終わりの `rate_limit_suppressed count=<N> since=<…> route=action` の 2 行まで (warn。利用者 ID を書かない)。上限は、留まった側面ペインの自動既読 (ペイン 1 つで 1 分に 120 件) の 5 つ分。
97
+
98
+ ### Breaking
99
+
100
+ - action の照合用 ID は 2 つの形だけ (上の Fixed)。server のサービスの書き込み (`setStarStatus`・`setReadStatus`・`addComment`・`deleteComment`・`createDailyReport`・`updateDailyReport`・`publishDailyReport`・`deleteDailyReport`) は照合用 ID を `DailyReportClientTempId` でだけ受ける (以前は `string`)。SSE のストリームの、形の外の照合用 ID を持つエントリは配らない。
101
+ Migration: パッケージのクライアント以外から action を呼ぶホストは、照合用 ID を小文字の UUID (`crypto.randomUUID()`) か、先頭に 0 を持たない 16 桁までの負の整数で送る。サービスを直接呼ぶホスト (結合試験など) は、ID を `parseClientTempId` で読んでから渡す (どちらの形でもなければ `null`)。
102
+ - server の `StreamEntry` (共有リーダーのポート `DailyReportSseReaderPort` が渡すエントリ) は `{ id, message }` で、`message` は 1 度だけ読んだメッセージ (`{ parsed, published, text }`: スキーマが検証したメッセージ・書かれたままの JSON の値・その字面) か、正しいメッセージを持たないエントリの `null` (以前は `message` がエントリの欄の `Record<string, string>`)。
103
+ Migration: 自分のリーダーを渡すホストは、エントリ 1 件の `data` の欄を JSON として解釈して `dailyReportSseMessageSchema` で検証し、読めなければ `message: null` にして、同じ値をすべての購読者へ渡す。
104
+ - action の `deleteComment` の 200 の答えは、ほかの操作と同じく `intent` (`"deleteComment"`) を持つ。
105
+ Migration: 答えを自分で読むホストは、答えの欄を共有の形 (`ActionResult`) で読む (知らない欄を落とす読み方なら変更は要らない)。
106
+ - action の閲覧者ごとのレートの上限 (上の Added): 1 人の閲覧者が 1 つのワーカーへ 1 分に 600 件を超えて送ると 429。
107
+ Migration: パッケージのクライアント以外から action を呼ぶホストは 429 を失敗として扱い、`Retry-After` の後に送り直す。
108
+ - ピアの `@aiquants/virtualscroll` の下限を 3.13.0 へ上げた (`peer-floors.json`)。一覧の末尾の近くで窓より前の日報が消えても錨の日報が跳ばない修正 (上の Fixed) は、3.13.0 がペインのクランプを `onScrollAdjust` の `"clamp"` で知らせることに頼る。
109
+ Migration: `@aiquants/virtualscroll` を 3.13.0 以降へ上げる。
110
+
5
111
  ## 0.32.0 (2026-10-07)
6
112
 
7
113
  0.31.0 の続き: 添付の設定を 1 つの任意の入れ子のブロック (`attachments`) にまとめること、action の本文の上限と、送られた値 (件名・本文・コメント・真偽値) と `forceRefresh` を 1 つの厳格な解釈で読むこと、SSE のイベント 1 件の上限、ids ストリームのスナップショットトークンを取得ごとに 1 回だけ作ることと publish が一覧を写さないこと、アクションのプロバイダーの操作の値を描画をまたいで同じに保つこと、削除の印の付いた行の外し漏れ、件数の補間が動きを減らす設定に従うこと。どれも README の該当の節が今の契約を記す。