@aiquants/daily-report 0.39.0 → 0.41.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 +242 -1
- package/README.md +3163 -1046
- package/dist/client.d.mts +60 -125
- package/dist/client.d.ts +60 -125
- package/dist/client.js +7 -6
- package/dist/client.mjs +7 -6
- package/dist/{comment-adapter-BZhlIqmC.d.ts → comment-adapter-BTXFkMSz.d.ts} +2 -27
- package/dist/{comment-adapter-BX8N5FjO.d.mts → comment-adapter-wejvIziS.d.mts} +2 -27
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{search-marks-wire-CZRU4q3h.d.mts → search-marks-wire-BSS2d1t6.d.mts} +12 -1
- package/dist/{search-marks-wire-CZRU4q3h.d.ts → search-marks-wire-BSS2d1t6.d.ts} +12 -1
- package/dist/server.d.mts +15 -38
- package/dist/server.d.ts +15 -38
- package/dist/server.js +10 -10
- package/dist/server.mjs +10 -10
- package/dist/sse-schema-CUiQKk8K.d.mts +286 -0
- package/dist/sse-schema-CUiQKk8K.d.ts +286 -0
- package/dist/styles/daily-report.standalone.css +1 -1
- package/package.json +11 -10
- package/dist/sse-schema-DxXYBhDJ.d.mts +0 -2556
- package/dist/sse-schema-DxXYBhDJ.d.ts +0 -2556
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,247 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@aiquants/daily-report` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.41.0 (2026-10-11)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **SSE のストリームのキーの組み立て `buildDailyReportSseStreamKey(streamKey)`** (server エントリ): サービスが SSE のエントリを書いて読む Redis Stream のキー (基のキーにワイヤ形式の版を付けた `<streamKey>:v3`) を作る。ストリームへ直接書き読みするホスト (結合試験など) は、版を書き写さずにこれでキーを作る (版を上げたときに、書き写したキーだけが古い版に残らない)。
|
|
10
|
+
- **`DailyReportRowBatch.idAt(i)`・`businessDateAt(i)`・`sourceTypeAt(i)`** (client エントリ): 行のまとまりの行の ID・営業日・区分を、行のオブジェクトを作らずに 1 つずつ読む (下の Breaking)。
|
|
11
|
+
- **一覧全体の公開の読み口 `readDailyReportIdsStreamRows()`** (client エントリ): セッションの一覧を最後の publish の時点で行のまとまり 1 つへ写して返す (セッションが無ければ空のまとまり)。
|
|
12
|
+
publish の状態は一覧を運ばず変化だけを運ぶので、32 の変化の履歴より遅れた購読者は一覧をこれで読み直す (セッションのクライアントのクラスは公開しない。下の Breaking)。
|
|
13
|
+
- **検索の印の答えの厳格な読み手 `readDailyReportSearchMarksAnswer(payload, expected)` と、要求が求めるものの型 `DailyReportSearchMarksExpectation`** (共有エントリ): パッケージのクライアントが使う読み手そのもので、
|
|
14
|
+
`search-marks` を自分で呼ぶホスト (結合試験など) は答えを型の主張で読まずにこれで読む (頼んだ ID・見せている答えのキーワードの数・日報が当たったキーワードの組を持つかを渡し、合わない答えは null)。
|
|
15
|
+
- **厳格な読み手が受けた行の列の型 `DailyReportIdsStreamRows`** (共有エントリ): `readIdsStreamChunkLine` の答えの `rows` の型で、`forEachIdsStreamRow` が受ける唯一の列 (下の Breaking)。
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **ids の行は読み手が確かめた値で読む** (下の Breaking): 共有エントリの厳格な読み手 `readIdsStreamChunkLine(line)` は、受けた行を `DailyReportIdsStreamChunk` で答え、壊れた行には null を答える。答えは行の列 (`rows`。読んだ行そのもので写さず、型は受けた列の `DailyReportIdsStreamRows`)・`isLast` と、任意の項目をクライアントが採る規則で読んだ値を持つ:
|
|
20
|
+
再開のカーソル (`resumeCursor`。サーバーが読み返すカーソルだけで、ほかは null)・`total` (0 以上の安全な整数だけで、ほかは null)・`snapshot` (トークンの形だけで、ほかは null)・`provisional` と `restarted` (真偽の `true` だけが true)・`streamAnchor` (無い・値・受け取った値を持つ形の誤り)。任意の項目の形の誤りでは行を拒まない。答えのオブジェクトは行ごとに 1 つで、行のオブジェクトは作らない。
|
|
21
|
+
クライアントは行をこの読み手で 1 度だけ読み、答えの項目だけを使う (ワイヤの値を 6 か所で確かめ直さない)。再開の規則 (`isResumableIdsStreamCursor`・`isIdsStreamSnapshotToken`) は `src/shared/ids-stream.ts` に 1 つだけ置き、サーバーの再開の解釈 (`readIdsStreamQuery`) と行の読み手が共有する。
|
|
22
|
+
以前の `isIdsStreamChunkLine` は行を `DailyReportIdsStreamChunkLine` へ絞り込むのに任意の 5 つの項目を確かめず、`total: "9"`・`restarted: "no"`・`provisional: 0`・`snapshot: 42`・`streamAnchor: 7` の行も確かめた型のまま通した。
|
|
23
|
+
- **営業日の符号は 32 ビット**: 常駐の行・変化の行のまとまり・プロバイダーの並びの営業日の符号は `Uint32Array` (行あたり 2 バイト増え、11 万行で約 220 KB) で、ワイヤが運べるどの営業日 (0001-01-01〜9999-12-31 の 3,652,059 日) にも符号がある。
|
|
24
|
+
以前は 16 ビットの符号が 65,535 日で尽き、閲覧者の行がそれより多くの別々の営業日を持つと (書き手 1 人が別々の日付の日報を作るだけで起きる)、どの ids のストリームも同じカーソルで試みを繰り返して `failed` で終わった。区分の符号は今までどおり 255 種類まで (区分はサーバーの連携が決め、閲覧者の操作では増えない)。
|
|
25
|
+
- **SQL 結果キャッシュの見積もりは保持するヒープの上界**: `estimateRecordsBytes` は、たどった配列・オブジェクト・バイナリの view ごとに 32 バイトの頭、持つ値ごとに 8 バイトの欄、文字ごとに 24 バイトの頭と UTF-16 の単位ごとに 2 バイト、31 ビットの整数でない数 (小数・大きな整数・−0・NaN) ごとに 16 バイトの箱、view のバイト数を数える。
|
|
26
|
+
Node 24 の実測で、見積もりは 11 万行の ids のバケットの保持するヒープの 1.31 倍、詳細の形の行の 1.09〜1.16 倍、長い本文の 1.00 倍 (日本語)〜2.00 倍 (ASCII)。以前は頭と箱を数えず、よくあるバケットを 1.70〜1.89 倍少なく見積もった (既定の 512 MiB でワーカーあたり 0.9〜1 GB を持ちえた)。
|
|
27
|
+
上限 (`cacheMaxBytes`) はキャッシュ 1 つ、つまりプロセスごと: ワーカーを N 個持つホストは N 倍までを持つので、ワーカーの数を知って決める。
|
|
28
|
+
- **単独で置いたストリームのバッジは、空のまま置いた領域で知らせる**: `DailyReportIdsStreamStatus` は `DailyReportPage` の中に無いとき (ページの中では、ページの常設の読み上げが知らせる)、入れ物の隣にパッケージの読み上げの部品 (`data-testid="daily-report-ids-stream-status-announcer"`) を置く。空のままマウントした `role="status"` と `role="alert"` の領域で、変わるのは文字だけ。
|
|
29
|
+
再接続中 (`labels.streamRetrying`) は状態、ストリームの失敗 (`labels.streamFailed`) は警告で知らせ、マウントの時点の文字は書かない。局面ごとの札は読み上げの領域ではない。ページの常設の読み上げ (`daily-report-stream-announcer`) も同じ部品 (`src/client/ui/live-announcer.tsx`)。
|
|
30
|
+
以前は、単独で置いたバッジの再接続中と失敗の札が、局面の変化で文字を持ったまま現れる `role="status"` で、読み上げられないことがあり (WCAG 4.1.3)、失敗をページの警告より弱い状態として伝えた。
|
|
31
|
+
- **デスクトップの側面ペインのスクロールバーはビューと同じ配色**: ビューの休みのつまみと溝の組 (つまみ slate-500、溝 slate-100・暗色 slate-900。明 4.35・暗 3.74) を 2 つの独自プロパティ (`--aqdr-scroll-bar-thumb`・`--aqdr-scroll-bar-track`) で 1 度だけ名指し、VirtualScroll のバーの配色と、デスクトップの側面ペインのスクロールの箱のブラウザー自身のバーの `scrollbar-color` をその上に組む。
|
|
32
|
+
同じ中身を描くモバイルのオーバーレイの箱はブラウザー自身のバーのまま (組のつまみが、390×844・DPR 3 で右の列の添付のタイルのフォーカスの輪の右の縁の上に描かれるため)。
|
|
33
|
+
以前は側面ペインのブラウザーのバーが既定の灰色 (暗色で #2c2c2c / #9f9f9f、明色で #fcfcfc / #8b8b8b) のまま、隣の List の slate のバーと同じ画面に並んだ。バーの幅と §3 の幅は変わらず、Safari 17.4 は `scrollbar-color` を読まずに既定のバーを描く。
|
|
34
|
+
- **行の同一性の控えは 2 つの世代**: 位置で読む行のオブジェクトの控えは 512 行の世代 2 つ (`REPORT_ROW_IDENTITY_CAPACITY` = 1,024) で、若い世代で当たる読み取りは何も書かず、古い世代で当たった行を若い世代へ移し、若い世代が 512 行になったら古い世代と入れ替える。
|
|
35
|
+
両ビューの行のキー (`getItemKey`) は行のオブジェクトを通さずに ID の列を読む (`reportIdAt`)。同じ窓の描き直しは最初の読み取りの後に控えを書かず、窓の行の控えの読み取りは描画あたり 2 回から 1 回になった。
|
|
36
|
+
Node での計測 (11 万行・60 行の窓・押し続けあたり 40 キー・キーあたり描画 1 回): 押し続けあたりの確保と時間は List が 266.8 KiB・0.47 ms から 6.6 KiB・0.066 ms、DetailList が 395.7 KiB・0.69 ms から 6.6 KiB・0.13 ms。以前は読むたびに `Map` の項目を消して足し直し、V8 が表を作り直し続けた。
|
|
37
|
+
- **詳細はその場で読む 1 つの厳格な読み手で読む** (下の Breaking): 日報の詳細とその部品 (面談者・レガシー形式のコメント・ラベル・投稿したコメント) は 1 つの読み手 (`src/shared/detail-reader.ts`) が JSON の値を置かれた場所のまま確かめ、契約どおりなら同じ値を答える。
|
|
38
|
+
`dailyReportDetailSchema` はこの読み手の上に組み、SSE のメッセージ・action の答え・読み取りのエンドポイントの答えは同じ規則で読む。必須の鍵は補わず (`attachments` と添付の `hasThumbnail` も。下の Breaking)、知らない鍵の除去だけが変わる道のオブジェクトを新しく作り、渡した値は書き換えない。
|
|
39
|
+
合わない値の問題は zod の言葉 (`invalid_type`・`invalid_enum_value`・ID のスキーマの問題) で今までと同じパスと順に答え、`describeWireMismatch` の文も変わらない。
|
|
40
|
+
Node での計測 (3.5 KB の日報 30 件): 日報あたり `{ reports }` の答えは 34〜40 µs・50.6〜57.3 KiB から 10.8〜11.6 µs・8.6 KiB (`JSON.parse` だけで 7.0〜7.5 µs・6.7 KiB)、SSE の更新は 42〜44 µs・58.5 KiB から 16.5〜17.4 µs・11.7 KiB。以前は zod の `z.object` が日報ごとに木を丸ごと写した。
|
|
41
|
+
- **検索の印は値の除去・行の分割・行の分け方の中でも区切る**: 印の歩みは値の除去・行の分割・行 1 つの分け方を、索引の文字の処理の段で進める版 (`stripInvisibleSearchCharactersMappedSteps`・`forEachSearchLineSteps`・`planSearchLineSteps`) で、見積もり 0.5 ms 以内の段ずつ進める。
|
|
42
|
+
段の版の結果は 1 回で行う版とどこで区切っても同じで、索引の保存と正規化の版は 1 回で行う版のまま (正規化の版の文字は変わらない)。行ごとに決まった仕事の見積もり (2 µs) も払うので、空の行だけの値でも計器が時計を読む。見積もり (ns) は除去 30・行の分割 200 (単位あたり)・行 1 つ 2,000・分け方 90 (仕事の単位あたり)。
|
|
43
|
+
100 万単位の値の最長の区切り (壁時計、ホストの負荷 0.4): 改行だけで 604 ms から 12.6 ms、見えない文字と改行の 50 万行で 326 ms から 9.4 ms、1 字の行の 50 万行で 61 ms から 10.9 ms、文脈によらない切れ目の無い全角の `AB` の行で 51 ms から 5.2 ms、空白の多い行で 69 ms から 8.0 ms。
|
|
44
|
+
行の多い値では歩み全体の時間は増える (改行だけの 100 万単位で 0.7 秒・2 つの区切りから 2.4 秒・458 の区切り。行あたりの段の用意の費用)。
|
|
45
|
+
- **印の 429 の待ちは 60 秒まで**: 検索の印の 429 は、`Retry-After` が 60 秒 (`SEARCH_MARKS_MAX_RETRY_AFTER_MS`。サーバーのレートのバケットの窓 1 つ) の内ならその後に 1 回だけ頼み直し、`Retry-After` が無いか超える 429 は時計を置かずに、その epoch の間はその ID を印なしにする。
|
|
46
|
+
以前はどの `Retry-After` もそのまま時計へ渡したので、2,147,484 秒以上の待ちは時計が溢れてすぐに頼み直し、大きな待ちの間は記録を持ち続けた。
|
|
47
|
+
- **日報を運ばない後回しの答えは拒む**: 検索の印の答えの読み手 (`readDailyReportSearchMarksAnswer`) は、日報を 1 件も運ばずに ID を後回しにする答えを丸ごと拒む (サーバーは最初の日報をいつも答えるので取り決めの破れで、頼み直しても前へ進まない)。以前は受け、store は前へ進まない答えとして扱った。
|
|
48
|
+
- **後から届く日報のカードは後追いの描画で載せる**: List のカードと DetailList の本体は読み込んだ日報を `useDeferredValue` を通して描くので、読み込みの答えや SSE の更新で届いた日報のカードは、それを運んだ更新の中ではなく、キーの押下が割り込める後追いの描画で載る。
|
|
49
|
+
その描画が確定するまで、カードをまだ描いていない行はスケルトンのまま `aria-busy="true"` (描くのを待つ読み込み済みの日報は不在ではない)。遅らせるのは描くことだけで、判断は読み込んだ日報そのものを読む:
|
|
50
|
+
消えた日報の行を外す安全弁と、DetailList の本体の下書きか・自分の日報か・下書きを開く編集 (編集のボタンは確定した最後の読み込んだ日報を読み、無ければ何もしない)。
|
|
51
|
+
性能の試験で、答えを見せた List の押し続けの予算を超えるフレームは、行 110 からで 2 から 1、80,000 からで 3 から 1 になり、どちらの条件も目標を満たした。
|
|
52
|
+
- **ID でまとめた詳細の読み手の後始末**: 詳細の読み手 (`useDailyReportDetail`) は、ID でまとめた読み込みを始めた実行だけが中断の合図の持ち主を作り、前もって作った 1 つの理由で止める。キャッシュの日報・営業日の読み込み・走らなかった待ちの再取得は何も作らず、何も止めない。
|
|
53
|
+
性能の試験の押し続けあたりの後始末は 8.5〜8.7 ms から 0.73〜1.06 ms (理由を渡さない中断が呼ぶたびにスタックごと作っていた `AbortError` の例外と、押し続けあたり 460〜822 KB の確保が無くなった)。
|
|
54
|
+
- **検索の帯の件数は見出しの 1 つだけ**: 当たりの件数は見出しの件数 (`renderHeader` が受ける注記の中) にだけ出し、検索窓は件数を画面に書かない。検索窓の読み上げの領域は 1 つ
|
|
55
|
+
(`aria-live="polite"`。画面に出さない) で、検索中・件数 (見せている一覧の大きさ。ストリームが答えの日報を消せば下がる)・当たり無し・失敗を読む。注記を描かないホストでは件数は画面に出ない (総数と同じ)。
|
|
56
|
+
以前は見出しと検索窓の状態の行が同じ件数を 2 度描き、390 px の電話で入力欄が約 90 px 狭まった。
|
|
57
|
+
- **検索の帯の失敗の行と欄の中の待ちの印**: 失敗 (検索語が正しくない・混み合っている・使えない・そのほか) は入力欄の下の失敗の行で見せ (読み上げの領域ではない)、入力欄の説明の先頭に入る。
|
|
58
|
+
入力欄が `aria-invalid` を持つのは検索語が正しくないときだけ。検索中は入力欄の中の自分の場所に回る印 (読み上げから隠し、動きを減らす設定では回らない) を描き、消去のボタンは隠れずに押せる。
|
|
59
|
+
入力欄は終わりの側に 24 px の場所を 2 つ空けておくので、検索の状態で入力欄の幅も文字の位置も変わらない (320 px の幅でも空の入力欄は全角 4 文字以上を持つ)。
|
|
60
|
+
- **検索の帯の切り替えは幅を変えない**: AND/OR と絞り込みの切り替えは、今の文字と見えない取り置きの文字 (もう一方の文字、最も多い値の数の文字) を 1 つの格子の升に重ね、広い方の幅を
|
|
61
|
+
格子の最小の幅 (56 px・104 px) で切り上げる。押しても数が増えても入力欄の縁は動かず、既定の文言では入力欄の始まりと終わりが 4 px の格子に乗る。取り置きの文字は名前にも文字の内容にも入らない。
|
|
62
|
+
以前は AND (50 px) と OR (41 px) の幅の差で、押すたびに入力欄の始まりが 9 px 動き、DPR 1.25 で縁がにじんだ (2.91:1)。
|
|
63
|
+
- **検索の答えの一覧は変わった日報から更新する**: 答えを見せている間の一覧は、publish の変化が答えの日報の項目を変えなければ作り直さない (性能の試験の押し続けあたりの確保は、一覧の導きが 827〜1,095 KB から 0、行の並びの読み取りが 448 KB から 0)。
|
|
64
|
+
- **ID でまとめた詳細の要求は行の出入りが止んでから**: 答えを見せている間の行の詳細の ID は、行の出入りが止んだ後の最初のアニメーションフレームで送り手へ並ぶ。速いスクロールの途中で
|
|
65
|
+
載って外れる行の要求を始めず、止まった窓の行の待ちは 1 フレームまで。
|
|
66
|
+
- **`SqlResultCache` の上限は省ける**: コンストラクターの `defaultTtlMs`・`maxBuckets`・`maxBytes` はどれも省け、省けばクラスの既定値 (60,000 ms・4,096・512 MiB。`createDailyReportServer` の
|
|
67
|
+
`cacheDefaultTtlMs`・`cacheMaxBuckets`・`cacheMaxBytes` と同じ) で、誤りはファサードの設定の名前を名乗る (`[daily-report] cacheMaxBytes must be an integer >= 1; got 0`)。ファサードは値をそのまま渡し、検証は 1 度だけ。
|
|
68
|
+
- **`knownSourceTypes` は凍結した配列**: `createDailyReportServer` の `knownSourceTypes` は、パッケージ自身の区分 `Internal` を先頭に外部ソースの区分を順に並べた凍結した配列で、サービスの
|
|
69
|
+
`searchFilterSourceTypes` と同じ配列。
|
|
70
|
+
- **開発者向け**:
|
|
71
|
+
- バンドルの増加の台帳はファイルごとの理由と、出荷した版を持つ: 項目は `{ recordedAfter, release, file, from, to, deltaPercent, reason }` (`recordedAfter` は記録した時の `package.json` の版、`release` は出荷する版で、公開のスクリプトが刻むまでは `"Unreleased"`)。
|
|
72
|
+
`--reason` はファイルごと (`--reason '<file>=<text>'`。繰り返す) で、理由の無い増えたファイル・予算の対象でないファイルの理由・同じファイルの 2 つの理由は何も書かずに exit 2。公開のスクリプトは `pnpm version` の直後に `--stamp-release` で `"Unreleased"` を新しい版に変え、`prepublishOnly` の `--released` は `"Unreleased"` の残る台帳で公開を止める。
|
|
73
|
+
既存の項目は `version` を `recordedAfter` へ改め、その項目を含む最初の公開のタグから `release` を付けた (0.35.0〜0.40.0)。
|
|
74
|
+
- 読み上げの領域の守り (`src/client/ui/live-regions.spec.ts`): 出荷するクライアントで読み上げの役割 (`status`・`alert`・`log`・`marquee`・`timer`)・`aria-live`・`<output>` を書くのは、パッケージの読み上げの部品 (`src/client/ui/live-announcer.tsx`) と、理由を付けてレビューした常設の 3 つの領域 (知らせの警告の領域・検索窓と絞り込みの行の状態の文字) だけ。
|
|
75
|
+
- ref の初期値の守り (`src/client/hooks/lazy-ref-initializers.guard.spec.ts`): 出荷するクライアントのどの `useRef` の引数も、描画のたびに何かを作らない (リポジトリの共有の走査 `.config/scripts/lib/lazy-ref-initializers.mjs`。`@aiquants/virtualscroll` の守りも同じものを使い、例外の一覧は無い)。
|
|
76
|
+
描画のたびに配列・`Map`・オブジェクトを作っていた ref (誤りの文脈・コメント・action の文脈・DetailList の高さ・検索窓・表示中の印の store・絞り込みの候補) は `useLazyRef` かモジュールの定数になった。
|
|
77
|
+
- 決まったクラスの重ね合わせの守り (`src/client/ui/class-merge-sites.guard.spec.ts`) は例外の一覧を持たない (検索窓と絞り込みの行の 3 か所はモジュールの定数)。種から決まる乱数の守り (`src/client/test-helpers/seeded-random-copies.guard.spec.ts`) は `src/server` も読み、サーバーの spec の生成器の写しは `createSeededRandom` を使う。
|
|
78
|
+
- 内部の `resolveTodayBusinessDateKey` は、不正な `Date` を現在時刻で埋めずに拒む (`[daily-report] resolveTodayBusinessDateKey needs a valid Date`)。
|
|
79
|
+
- 文書のガードは関心ごとの 8 つのファイル (`src/readme-examples.guard.spec.ts`・`src/readme-identifiers.guard.spec.ts`・`src/spec-citations.guard.spec.ts`・`src/testing-spec-index.guard.spec.ts`・
|
|
80
|
+
`src/peer-floor-sentences.guard.spec.ts`・`src/doc-schema-names.guard.spec.ts`・`src/plan-open-decisions.guard.spec.ts`・`src/docs-current-state.guard.spec.ts`) で、網羅率を測らない `guards` のプロジェクトで走る。
|
|
81
|
+
コードブロック・その外の行・インラインコードはリポジトリの 1 つの読み手 (`.config/scripts/lib/markdown-blocks.mjs`。CommonMark の規則で読み、閉じていないコードブロックは開く行を名乗って落ちる。
|
|
82
|
+
`src/markdown-blocks.guard.spec.ts` が確かめる) で、経緯を語る行はリポジトリの 1 つの今の状態の確かめ (`.config/scripts/lib/markdown-current-state.mjs`: 版か日付を持つ見出し・地の文の
|
|
83
|
+
パッケージの版・昔を語る語・レビューの周・実行の ID・issue の参照) で読む。README と仕様書はコードブロックの外で経緯を語らず (版ごとの変更はこの変更履歴、経緯は計画書)、
|
|
84
|
+
README はインラインコードで入口が公開しない SCREAMING_SNAKE と `DailyReport` で始まる識別子を名指さない (README はホストの契約で、内部の識別子は仕様書が持つ)。
|
|
85
|
+
以前は `src/docs-examples.spec.ts` の 1 つのファイルが 7 つのガードとコードブロックの読み手の 3 つの写しを持った。
|
|
86
|
+
- README はホストの契約だけを持つ: 目次・アンカーを持つ節 (設定・読み取りのポート・描画のポート・sharp の描画・`hasThumbnail`・縮小画像の経路・ログの水準・429 の行・
|
|
87
|
+
文字の検索の各部)・Server wiring の下の Streaming routes・The API route・Request values・Realtime architecture。パッケージの開発の門 (型の検査・網羅率・ガード・バンドルの予算・
|
|
88
|
+
公開の検査) はテストの仕様書 (§1.4) へ移し、内部の識別子と理由は設計の仕様書 (00) と検索の仕様書 (03) が 1 度だけ持つ。
|
|
89
|
+
- ゲートのスクリプトはリポジトリの共有のモジュール (`.config/scripts/lib` の `count-ratchet.mjs`・`ratchet-docstrings.mjs`・`ratchet-coverage.mjs`・`gate-cli.mjs`・`source-corpus.mjs`) を
|
|
90
|
+
`@aiquants/virtualscroll` と共有し、パッケージの `scripts/` はパッケージの値を渡す短い入口 (根はリポジトリの唯一の実装と 1 バイトも違わない `scripts/lib/repository-root.mjs` で見つける)。
|
|
91
|
+
- spec は置き場で 3 つのプロジェクトに入る (`vitest.config.ts`): 網羅率を測る `node` (`src` の直下・`src/server`・`src/shared`) と `jsdom` (`src/client`)、網羅率を測らない `guards`
|
|
92
|
+
(本番の処理を読み込まない `*.guard.spec.ts` と、乱択の軌跡の組の `*.traces.spec.ts`)。環境は置き場で決まり、違う環境が要る spec だけが 1 行目に指定を持つ。`verify` と `test:coverage` は
|
|
93
|
+
網羅率を測るプロジェクトを先に、`guards` を後に走らせる。
|
|
94
|
+
- モジュールの置き場: サービスは合成の根 `src/server/service.ts` と関心ごとの `src/server/service/` (設定・SSE の書き込み・hub の写し・添付の記録・読み取り・閲覧者の状態・コメント・
|
|
95
|
+
日報の書き込み)、アクションのプロバイダーの部品は `src/client/contexts/action/`、行は `src/client/rows/`、選択は `src/client/selection/`、ID のまとめは `src/client/batching/`。公開名は変わらない。
|
|
96
|
+
- 整数の設定のガード (`src/config-integer-settings.guard.spec.ts`): 公開の `*Config` の型のどの `number` の欄も、1 つの整数の検証でちょうど 1 度だけ読む。
|
|
97
|
+
- zod の読み込みのガード (`src/zod-imports.guard.spec.ts`): `src` のどのソースも zod を `zod/v3` の入口からだけ読む。
|
|
98
|
+
|
|
99
|
+
### Fixed
|
|
100
|
+
|
|
101
|
+
- **zod 4 でも公開の宣言が型検査を通り、食い違いの code が同じ**: パッケージは zod を `zod/v3` の入口からだけ読む。zod 4.4.3 で共有エントリの宣言の型検査が 47 の誤りで落ち、
|
|
102
|
+
食い違いの code が変わっていた (peer の範囲は zod 4 を受けていた)。
|
|
103
|
+
- **収めた後に掛かった文字の間隔の上書きも、次のフレームで収め直す (WCAG 1.4.12)**: 添付の収めたファイル名の見える文字列は中身の幅の箱 (`width: fit-content`) で、名前の列の幅の観測器がその描いた幅も見る (`observeFittedNameWidth`)。
|
|
104
|
+
列へ最後に渡した列幅より広く描かれた文字列があれば、次のフレームでその列へ同じ列幅をもう一度渡し (溢れた文字列がいくつでも列ごとに 1 回)、列の名前は書体と間隔を読み直して収め直される。切り取りは無く、窓をずらすスクロールの 1 段の描き直しは今までどおり (Chromium 148 で、素の行 1 行・添付のある行 2 行)。
|
|
105
|
+
以前は、収めた後に掛かった上書き (ブックマークレットや利用者のスタイル) は列幅を変えないので次の収め直しまで残り、名前が列から 11〜64 px はみ出して隣のタイルに重なった (Chromium 148 の探り。アプリの見た目の契約では 22〜57 px)。今は 2 フレーム目から列の中に収まる。
|
|
106
|
+
上書きが掛かったフレームだけは名前を広く描き、外した上書きは次の収め直しまで名前を短いまま残す (README の **File name**)。
|
|
107
|
+
- **見出しの件数は総件数と当たりの件数で同じ幅**: 見出しの件数は検索の答えの有無のどちらでも同じ箱に描き、箱の幅は総件数と当たりの件数の最も広い文字 (総件数と答えの件数の大きいほうの桁数の最大の数で作る) の広い方が取るので、狭い幅で注記が折り返しても、答えを見せる・解除するたびに検索の帯とビューが上下に動かない (答えの件数が総件数より桁が多いときを除く)。以前は当たりの件数 (`0 件ヒット`) が総件数 (`総件数: 228,223 件`) より狭く、320 px の幅では注記が 2 行から 1 行に減って見出しが 12 px 縮み、検索の帯が上へ動いた。
|
|
108
|
+
|
|
109
|
+
### Breaking
|
|
110
|
+
|
|
111
|
+
- **`isIdsStreamChunkLine` は `readIdsStreamChunkLine` に**: 共有エントリの行の読み手は真偽の絞り込みでなく、確かめた値 (`DailyReportIdsStreamChunk`) か null を答える。別名は無い。
|
|
112
|
+
Migration: `if (isIdsStreamChunkLine(line)) { … line.total … }` は `const chunk = readIdsStreamChunkLine(line)` と `if (chunk !== null) { … chunk.total … }` に書き換え、任意の項目は答えの値 (`resumeCursor`・`total`・`snapshot`・`provisional`・`restarted`・`streamAnchor`) だけを読み、行は `forEachIdsStreamRow(chunk.rows, visit)` でたどる。
|
|
113
|
+
- **`DailyReportRowBatch` は読み口の型で、列を持たない**: client エントリの `DailyReportRowBatch` はクラスでなく読み口 (`length`・`idAt`・`itemAt`・`items`・`businessDateAt`・`sourceTypeAt`) で、
|
|
114
|
+
`ids` の列も `businessDateCodes` と `sourceTypeCodes` (ページの中で値が初めて現れた順に配る符号) も無い (パッケージが共有する書ける列を渡さない)。0 から `length` − 1 の整数でない位置の読み取りは
|
|
115
|
+
投げる (`[daily-report] A batch of <n> rows has no row <i>`)。
|
|
116
|
+
Migration: ID は `idAt(i)`、営業日と区分は `businessDateAt(i)`・`sourceTypeAt(i)` か `itemAt(i)` で読む (`batch.ids[i]` は `batch.idAt(i)`)。符号を読んでいたホストは値で読む。
|
|
117
|
+
- **詳細のスキーマは `ZodObject` でない**: `dailyReportDetailSchema` は `z.ZodType<T, z.ZodTypeDef, unknown>` (読み手の上に組んだ変換) で、`.shape`・`.extend`・`.pick` は無い (部品のスキーマはもう公開しない。下の項)。
|
|
118
|
+
拒んだ詳細の問題は打ち切りの印 (`fatal: true`) を持ち、後の `.refine` や `.transform` へ値を渡さない。受ける値・出力・問題のパスと符号は今までどおり。
|
|
119
|
+
Migration: スキーマの形を広げたり選んだりしていたホストは、自分のスキーマを書く (詳細の型は `DailyReportDetail` ほか)。
|
|
120
|
+
- **zod の peer の下限は 3.25.76**: peer の範囲は `>=3.25.76 <5.0.0` (パッケージは zod を `zod/v3` の入口からだけ読み、その入口を持つ版から)。
|
|
121
|
+
Migration: zod 3.25.76 以上 (zod 4 を含む) を入れる。
|
|
122
|
+
- **サービスの数の設定は 1 以上の整数**: `idsTtlMs`・`businessDateTtlMs`・`streamMaxLen` は省いたときだけ既定値で、0・負の数・`NaN`・小数・2^53 以上は `createDailyReportService` と
|
|
123
|
+
`createDailyReportServer` で鍵を名乗る `RangeError` になる (`[daily-report] idsTtlMs must be an integer >= 1; got 0`)。以前はそのままキャッシュの TTL と Redis の MAXLEN へ渡った。
|
|
124
|
+
Migration: 3 つを 1 以上の整数で渡すか省く。
|
|
125
|
+
- **大小文字によらず同じ区分の外部ソースは作成で止まる**: 2 つの外部ソースの `sourceType` が大小文字によらず同じ (同じ字面も) か、1 つが `Internal` と大小文字によらず同じなら、
|
|
126
|
+
`createDailyReportService` と `createDailyReportServer` は `RangeError` で止まる (`[daily-report] externalSources[1].sourceType must be a source type that differs, ignoring case, from …; got …`)。
|
|
127
|
+
以前は後の区分を絞り込みの候補から黙って落とした。Migration: 区分の名前を大小文字によらず別々にする。
|
|
128
|
+
- **`DailyReportService` の 5 つの欄を外した**: `streamMaxLen` と、閲覧の範囲を当てない hub の部品 `findDailyReportHubById`・`updateDailyReportHub`・`updateDailyReportInternal`・`deleteDailyReportLabel` は無い。
|
|
129
|
+
Migration: ストリームの上限は自分の設定 (`streamMaxLen`) から読み、日報は公開のサービスの読み取りと書き込み (`getDailyReportDetailById`・`updateDailyReport` ほか) で扱う。
|
|
130
|
+
- **SSE のエントリの区分と詳細の添付の欄は必須**: SSE のメッセージのスキーマは `comment-add`・`comment-delete`・`report-delete` の `sourceType` (文字) を求め、詳細の読み手は `attachments` と
|
|
131
|
+
添付の `hasThumbnail` を既定値で補わない (欠ければ合わない値)。版を焼き込んだストリームのキー (`<streamKey>:v3`) の前の形のエントリは読まれない。
|
|
132
|
+
Migration: 自分でエントリを書くホスト (結合試験など) は、3 つの知らせに日報の `sourceType` を、詳細に `attachments` と添付ごとの `hasThumbnail` を書く。
|
|
133
|
+
- **`forEachIdsStreamRow` は厳格な読み手が受けた列だけを受ける**: 引数は `DailyReportIdsStreamRows` (`readIdsStreamChunkLine` の答えの `rows`) で、`encodeIdsStreamRows` の出力や
|
|
134
|
+
手で組んだ列 (`DailyReportIdsStreamRowColumns`) は型検査で渡せない。Migration: 行は `readIdsStreamChunkLine(line)` で読み、受けた答えの `rows` をたどる。
|
|
135
|
+
- **ids のストリームのクライアントのクラスを公開しない**: client エントリの `DailyReportIdsStreamClient` と `DailyReportIdsStreamClientOptions` は無い (ホストが作ると 2 本目のストリームを開く)。
|
|
136
|
+
Migration: 一覧全体は `readDailyReportIdsStreamRows()`、状態は `useDailyReportIdsStream()`、読み直しは `resyncDailyReportIdsStream()` で扱う。
|
|
137
|
+
- **共有エントリはメッセージと詳細のスキーマを 1 つずつだけ公開する**: メッセージごとのスキーマ (`connectedMessageSchema`・`reportCreateMessageSchema`・`reportUpdateMessageSchema`・
|
|
138
|
+
`reportPublishMessageSchema`・`reportDeleteMessageSchema`・`statusUpdateMessageSchema`・`commentAddMessageSchema`・`commentDeleteMessageSchema`)・部品のスキーマ
|
|
139
|
+
(`dailyReportInterviewerSchema`・`dailyReportExternalCommentSchema`・`dailyReportLabelDefSchema`・`dailyReportPostedCommentSchema`)・`mergeComments`・`isDailyReportSseStreamId` は無い。
|
|
140
|
+
server エントリの `DailyReportHubRow` と `DailyReportInternalRow` も無い (公開のどの宣言も参照しない)。
|
|
141
|
+
Migration: メッセージは `dailyReportSseMessageSchema` で読んで `type` で分け、詳細は `dailyReportDetailSchema` で読む。コメントは `useDailyReportComments` が合わせた並びを読む。
|
|
142
|
+
- **型の公開名はどれも `DailyReport` を名乗る**: server エントリの `ExternalReportFields`・`StreamEntry`・`SharpLike` は `DailyReportExternalReportFields`・`DailyReportSseStreamEntry`・`DailyReportSharpLike`、
|
|
143
|
+
client エントリの `SourceTypeConfig` は `DailyReportSourceTypeConfig`。Migration: 型の読み込みの名前を改める (別名は無い)。
|
|
144
|
+
|
|
145
|
+
## 0.40.0 (2026-10-09)
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
|
|
149
|
+
- **ids のストリームの行の部品を入口から出す**: 共有エントリの `forEachIdsStreamRow(line, visit)` (厳格な読み手 `isIdsStreamChunkLine` が受けた行の行を、連ごとにストリーム順でたどって ID・営業日・区分を渡す。行のオブジェクトは作らない) とその列の型 `DailyReportIdsStreamRowColumns`、server エントリの `encodeIdsStreamRows(items, start, end)` (ストリーム順の行の範囲を行の列へ符号化する。クライアントの束に入れないよう server エントリだけに置く)。ストリームを自分で読み書きするホスト (統合テスト・性能の試験の模擬) は列を書き写さない。
|
|
150
|
+
- **`DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT`** (共有エントリ): パッケージのクライアントが API の基点ごとに同時に送る、まとめて読む要求 (`reports?ids=` と `search-marks`) の数 (2)。サーバーは閲覧者の取り分をこの数から決め、ホストの試験は書き写さずに読む。
|
|
151
|
+
- **`DailyReportIdsStreamClient.readRows()` と `DailyReportRowBatch`** (client エントリ): `readRows()` は最後の publish の時点の一覧を、行のまとまり 1 つ (`DailyReportRowBatch`: `length`・`ids`・営業日と区分の符号の型付き配列・行のオブジェクトをそのつど作る `itemAt(i)` と `items()`) へ写して返す。`readItems()` は同じ一覧を行のオブジェクトで返す。
|
|
152
|
+
- **SQL 結果キャッシュの見積もったバイト数の上限 `cacheMaxBytes`** (`createDailyReportServer` の任意の設定。1 以上の整数で、既定 512 MiB): バケットの数の上限 (`cacheMaxBuckets`) と並ぶ 2 つ目の上限 (下の Changed)。
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- **検索の答えの行の詳細は、送る時に決めたまとまりで読む**: 検索の答えを見せている間の ID でまとめた詳細 (`reports?ids=`) は、検索の印と同じ需要で決める送り手 (`createIdBatchScheduler`) で送る。まとまりは送る時に、まだ待つ行の ID から新しい順に 100 件ずつ、API の基点ごとに同時に 2 本まで (止めた要求も fetch が落ち着くまで枠を持つ)。要求は `no-cache` で、要求ごとの合図を持つ。
|
|
157
|
+
行が外れると、送る前のその ID は送らず、どの ID にも行の残らない要求は止め、戻った行は頼み直す (止めた要求が落ち着く前に戻っても、止めた要求の拒否はその行に届かない)。答えは今までどおり日報ごとのキャッシュにだけ入り、2xx の答えに無い日報だけが null。
|
|
158
|
+
以前は、頼まれた順の待ち行列で古い行から送り、送る前に外れた行の ID も送り、どの行も待たなくなった要求も最後まで読んだ。
|
|
159
|
+
- **絞り込みの候補は `no-cache` で読む**: `search-facets` の要求は `no-store` でなく `no-cache` で送る。ブラウザーは前の答えの ETag で確かめ直し (`If-None-Match`)、変わらなければ 304 を受けて自分のキャッシュの 200 を返すので、変わらない候補の本文は送り直されない (同じ答えの候補の配列は今までどおり同じまま)。
|
|
160
|
+
- **開発者向け**: 検索の選択の規則 (`search/search-selection.ts`) も行を `rowAt` で読み、`src/client/row-reads.spec.ts` が許す直の `.at(` は `rowAt` 自身の 1 つだけになった。`Retry-After` の読み取りは 1 つ (`retryAfterMsOf`、`src/client/utils/retry-after.ts`) で、検索の印と ID でまとめた詳細が共有する。
|
|
161
|
+
- **検索の 4 つの経路は 1 つの部品で組み、クライアントが離れた要求にはどれも本文の無い 503 で答える**: `search`・`reports`・`search-marks`・`search-facets` は 1 つの部品 (`searchRouteHandler`) が同じ順
|
|
162
|
+
(検索の設定が無ければ 404 → 引数の 400 → 利用者の解決 (内部ユーザーが無ければ受け入れもデータの問い合わせも無しの空の答え) → 要求のシグナルでの受け入れ → 読み取り → 答え。枠はどの結末でも返す) で組む。
|
|
163
|
+
受け入れを待つ間・枠を渡された時・読み取りの後に中断された要求には、どの経路も答えの本文を組まず行も書かずに、本文の無い `Cache-Control: no-store` の 503 (`Retry-After` 付き、ETag 無し) で答え、
|
|
164
|
+
枠を渡された時にもう中断されていれば読み取りを走らせない。読み取りがほかの誤りで失敗すれば今までどおり 500。サービスへ要求のシグナルを渡すのは今までどおり検索の印だけ。
|
|
165
|
+
以前は検索の印だけがそう答え、検索・ID でまとめた詳細・絞り込みの候補は、止めた要求に 429 で答え、読み取りの後に離れた要求の答えも組んだ。
|
|
166
|
+
- **正規化の版はサービスの生成のときに計算を始め、順番を譲りながら計算する**: 検索の設定を持つサービスは生成のときに正規化の版 (プロセスで 1 つ、約 1 秒) の計算を始め、計算は見本の各段の前で
|
|
167
|
+
イベントループに順番を譲る (`computeSearchNormalizerVersionYielding`。版は今までとバイトまで同じ)。保存と突き合わせは、トランザクションの前にその終わりを待つ。
|
|
168
|
+
計算が失敗すれば、待っていた保存はその誤りで失敗し (元の表も索引も書かない)、次の保存が計算し直す (`createSearchNormalizerVersionReader`)。
|
|
169
|
+
以前は、プロセスの最初の保存か突き合わせが同期で計算し、その間 (約 1 秒) イベントループを止めた。
|
|
170
|
+
- **論理削除は索引を書き直さない**: 削除した日報は検索・印・ID でまとめた詳細・絞り込みの候補のどの読み取りの範囲にも入らないので、論理削除は日報の鍵も索引の書き直しも無しに、
|
|
171
|
+
日報の行だけをトランザクションの中で書く。索引を書き直す保存の経路は 5 つ (作成・更新・公開・コメントの追加と削除)。削除の取り消しはパッケージの外の書き込みなので、ほかの外の書き込みと同じく突き合わせが後を追う。
|
|
172
|
+
- **開発者向け**: 検索の印の待ちの上限 `SEARCH_MARKS_WAIT_MS` を `BATCHED_READ_WAIT_MS` (2,000 ms。ID でまとめた詳細と検索の印が分け合う) へ改名し、検索の待ちの上限 `SEARCH_WAIT_MS` (3,200 ms) を足した
|
|
173
|
+
(どちらも `src/server/search/search-limiter.ts`。パッケージの公開の入口には出ない)。中断された要求の答えの部品は `answerAbandonedSearchRequest` (4 つの経路で共有)。
|
|
174
|
+
- **ids のストリームの行は列で持つ**: クライアントの常駐の行は、ID (`Float64Array`)・営業日と区分の符号 (`Uint16Array`・`Uint8Array`。ページに 1 つの符号の表)・ID から位置を引くヒープの外の開番地法の表 1 本 (`Int32Array`、埋まりは半分まで、後ろ詰めの削除)・位置ごとの印の 1 バイト (生きている行・走査が観測した行) で持ち、行ごとのオブジェクトも `Map` と `Set` の項目も持たない。
|
|
175
|
+
publish の変化の行も列のまとまり (`DailyReportRowBatch`) で運び (下の Breaking)、プロバイダーの並びの塊も列で持つ。行のオブジェクトは位置で読むときだけ、ID ごとの同一性の控え (1,024 件、最後に読んだ順) を通して作るので、載っている行は publish をまたいで同じオブジェクトで、値の変わった行だけが新しいオブジェクトになる。
|
|
176
|
+
228,000 行を流してプロバイダーの行を導いても JavaScript のヒープの増えは 8 MB 未満で、4,000 行の publish の確保はバッファー 5 つほど。以前は行ごとにオブジェクト・`Map` の項目・`Set` の項目・塊の枠を持ち (228,000 行で 21.6 MB、publish ごとのバッファーは約 114)、押し続けの間のごみ集めが常駐の行の数で伸びた (性能の試験で、押し続けごとのごみ集めは D80kA で 18.8 ms から 12.9 ms、L80kA で 10.7 ms から 0 ms)。
|
|
177
|
+
- **プロバイダーは変化 1 つをそのまま当てはめる**: 1 回の描画が受けた変化が 1 つなら畳み直さず (クライアントが組んだ正味の変化は ID で互いに素の組なので、畳み直しても同じ)、2 つ以上のときだけ 1 つに畳む (`foldItemsChanges`)。4,000 行の publish の導きは 1.07〜1.19 ms から 0.29〜0.56 ms。
|
|
178
|
+
- **ids の行の厳格な読み手はより厳しい**: `isIdsStreamChunkLine` は、隣り合う等しいキー・行の中の同じ ID の 2 度目 (同じ営業日でも違う営業日でも)・同じ営業日の隣り合う 2 つの連 (正準でない連。符号化は書かない) も拒む。`isIdsStreamOrdered` も隣り合う等しいキーを並んでいないとする (ID の重なりは行の読み手が拒む)。
|
|
179
|
+
- **200 でない JSON の答えはどれも `no-store`**: API の経路の 200 でない答え (400・401・404・405・429・500・503。経路がエンドポイントを知る前の 401 と 404、ストリームの本体より前の断り、検索の離れた要求の本文の無い 503、キャッシュの取りこぼしの 503 を含む) は、行の方針によらず ETag を持たない `Cache-Control: no-store` (`jsonResponderByStatus`)。行の方針 (`etag` か `no-store`) は 200 にだけ当たる。経路の跡の `debug` の行は ETag を持つ答えだけが `etag_match` を書く。
|
|
180
|
+
以前は `etag` の行の断りと経路の 401・404 が、304 になりえない本文のハッシュの ETag と `must-revalidate` を持ち、同じ 400 が行ごとに 2 通りのキャッシュの意味を持った (`API_ROUTE_JSON_CACHE` は削除)。
|
|
181
|
+
- **検索の印は区切りで計算し、答えに予算がある** (下の Breaking): 欄の印は続きから進める歩み (`createSearchMarkWalk`) で求め、要求の欄が分け合う計器で、見積もり 8 ms か単調な時計で 5 ms を過ぎたら次の段を始めず (区切りの最初の段はいつも走る)、`setImmediate` で順番を譲ってシグナルを確かめてから続ける (欄の途中でも)。位置つきの正規化は連なりの境目が落ちる片だけに当てる。
|
|
182
|
+
答え 1 つは範囲 500,000・印を付ける生の単位 4,000,000 まで (`DAILY_REPORT_SEARCH_MARKS_BUDGET`) で、最初の日報はいつも答え、超える日報から後は印を付けずに `deferred` に並べる。欄 1 つの範囲が 10,000 を超えれば範囲を載せずに密 (`dense: true`) と答える。
|
|
183
|
+
100 万単位の欄の最長の同期の区切りは 574〜794 ms から 6〜14 ms、`ab` を 50 万回並べた欄を `a` で探す答えは欄あたり 7.9 MB の JSON から密になり (その欄の日報 100 件の要求は 4 件を答えて 96 件を後回しにする 718 B)、よくある 100 件の窓の CPU は 18 % 減。
|
|
184
|
+
値の除去と行の分割、1 行の分け方 (`planSearchLine`) は索引の文字の処理の 1 回の呼び出しのままで、最も遅い内容の 100 万単位では 1 つで 45〜150 ms かかりうる (README の **Slices**)。
|
|
185
|
+
- **一致の印は後回しの ID を頼み直し、密の欄を素で描く**: 印の store は、送った epoch のうちに届いて日報を 1 件以上運んだ答えの `deferred` を、行がまだ購読していれば同じ送り手で頼み直し、購読の外れた ID は頼んでいないものに戻す (日報を運ばない後回しは頼み直さない)。密の欄は印を塗らずに素で描き、行の文はその欄を打った語の当たりとして名指し、`表示外で一致` には数えない。
|
|
186
|
+
- **印の要求はまとめの窓で送る**: 行が購読した最初の ID は、窓の外ならすぐ (同じ処理の購読と一緒に) 送り手へ渡して 240 ms の窓 (`SEARCH_MARKS_BATCH_WINDOW_MS`) を開き、窓の中の購読は窓の終わりにまとめて渡す。押し続けのキーが先読みの端に載せる行は見えるまで約 0.5 秒あるので、印は見える前に届く。性能の試験の押し続けごとの要求は 9 本から 4〜5 本。
|
|
187
|
+
- **載る行の印の手間**: 行の葉は行の印が届くまで集計へ登録せず (行の最初の描画の登録は 12 から 0)、集計は要約を読むときに作って、聞き手へは処理の終わりに 1 回だけ知らせる (印が届いた確定の知らせは行ごとに 13 から 1)。
|
|
188
|
+
- **行の要素を減らす (画素は同じ)**: DetailList の本文カードは見出しと本体を直に並べ (間を空けるだけの包みを外す)、メタデータの列は組を `div` で包まずに `dl` の直下の `dt` と `dd` で描き (組の間 8 px と見出し語と値の間 4 px は自分の余白)、日時も削除の枠も無いコメントは右の並びを描かず、コメントのフォームは自分で末尾へ寄って寄せるだけの包みを持たず、List のカードの営業日の札は日付の文字を直に持つ。
|
|
189
|
+
行の要素は List のカードで 20 から 19、DetailList の本文カードで 125 から 113 (答えを見せていれば 142 から 130)。Chromium の描画は dpr 1・1.25・2、明暗、幅 952 px と 560 px で画素が同じ。
|
|
190
|
+
- **クラスは描くたびに重ねない (残り)**: `Button`・`Input`・`Textarea`・`Label` はバリアント・大きさ・呼び出し側のクラスの組ごとに 1 回だけ重ね (`createClassMerger`。DetailList の自分の下書きの行が開く編集で行あたり 7 回)、添付の節のリンク・タイル・行・見出しのクラスはモジュールの定数、大きさの注記と名前のクラスは呼び出し側のクラスごとに 1 回 (描き直しで 13 回から 0 回)。決まったクラスの重ね合わせはモジュールの直下にだけ書く (`src/client/ui/class-merge-sites.spec.ts` が出荷するクライアントのソースを型検査器で読む)。
|
|
191
|
+
- **SQL 結果キャッシュは見積もったバイト数でも縛る**: `SqlResultCache` はバケットの数 (`maxBuckets`) に加えて見積もったバイト数 (`maxBytes`。`estimateRecordsBytes`: 値ごとに 8 バイト、文字は UTF-16 の単位ごとに 2 バイト、バイナリの view はそのバイト数。上限を超えたところで止める) の合計でも、最も古く書いたバケットから捨て、1 つで上限を超える結果は持たない (読み取りには返す)。
|
|
192
|
+
日報の本文とコメントの長さは書き手が決めるので、バケットの数だけでは営業日 1 つのバケットが何百 MB にもなりえた。
|
|
193
|
+
- **下書きかは詳細の `isDraft` で決める** (下の Breaking): どの詳細 (HTTP の答え・action の答え・SSE の中身) も、サーバーが下書きの 1 つの定義 (SQL の中でラベルの名前を引く `buildDraftLabelExists` か、下書きの名前の作成区分) で決めた必須の `isDraft` を持ち、ビューは名前を比べずにこの値で作成者の下書きを編集の形で開く。楽観的に作る日報は `isDraft: true` でラベルを持たず (下書きの札はサーバーの日報で出る)、公開は答えの前に `isDraft` を外し (ラベルの札は答えまで残る)、失敗で戻す。
|
|
194
|
+
- **開発者向け**:
|
|
195
|
+
- バンドルの大きさの増加は理由とともに記録する: `--accept` には `--reason '<文>'` が要り、`--write --accept --reason` は増えたファイルごとにコミットする台帳 `bundle-growth-ledger.json` (`version`・`from`・`to`・`deltaPercent`・`reason`) へ項目を足してから基準を書く。基準の項目が台帳の最後の大きさを超えるか台帳に項目が無ければ、どの検査も落ちる。`--write` は esbuild のメタファイル (`tsup --metafile`、tarball には入れない) から入口ごとのモジュールのバイト数を出す。
|
|
196
|
+
- spec の種から決まる乱数は 1 つの生成器 (`createSeededRandom`、`src/shared/test-helpers/seeded-random.ts`。mulberry32) で、`src/client/test-helpers/seeded-random-copies.spec.ts` がクライアントと共有の木にほかの生成器の写しが無いことを確かめる。
|
|
197
|
+
- ids のストリームのクライアントは中断を名前 (`AbortError`) で見分ける (別の realm の `DOMException` は `instanceof Error` でなく、jsdom ではその中断が読み直しに数えられた。ブラウザーと Node の振る舞いは同じ)。接続の層 (ヘッダーの待ち・`online` の起こし・中断の分類・読み手の取り消し・待ちの終わり) は spec がどの分岐も通る。
|
|
198
|
+
- `useLazyRef` (`src/client/hooks/use-lazy-ref.ts`) は初めの値を 1 回だけ作る ref (キーボードのフックと DetailList のスクロールの復元が使う)。行の要素の数 (`row-element-count.spec.tsx`)・行の状態の行列でのクラスの重ね合わせ (`row-class-merge.spec.tsx`)・常駐の行のヒープ (`resident-rows-heap.spec.ts`)・載る行の印の手間 (`row-marks-first-mount.spec.tsx`) を spec が固定する。
|
|
199
|
+
- `src/docs-examples.spec.ts` のスキーマの走査は、公開する README と CHANGELOG に加えて `docs/plans` と `docs/specs` の markdown も読む (角括弧のスキーマの名前は文字か `_` で始まるものだけを数え、配列の展開やファイル名の省ける部分を数えない)。
|
|
200
|
+
|
|
201
|
+
### Fixed
|
|
202
|
+
|
|
203
|
+
- **混み合いの 429 で検索の答えの行が誤りにならない**: ID でまとめた詳細の 429 は、`Retry-After` が 5 秒 (`ID_BATCH_MAX_RETRY_AFTER_MS`。受け入れのゲートが満杯のときの 1 秒を含み、レートのバケットの窓の 60 秒より短い) の内なら、行を誤りも null も持たない読み込み中のまま待たせ、待ちの後にまだ待つ行の ID を 1 回だけ頼み直す。
|
|
204
|
+
2 度目の 429 と、`Retry-After` の無い・読めない・上限を超える 429 は今までどおり `[daily-report] reports answered HTTP 429` の誤り。以前は、枠がすぐ空く 1 秒の 429 でも、まとまりのどの行もすぐに誤りを持った。
|
|
205
|
+
- **キャッシュの全消去の前に送った詳細の答えを、消した後のキャッシュへ書かない**: キャッシュの全消去 (ユーザーの切り替えと開発用の全消去) は、送っている ID でまとめた詳細の要求を止め、やり直しの待ちを消し、まだ待つ行の ID を新しい要求で頼み直す (やり直しも新しく 1 回持つ)。
|
|
206
|
+
以前は、全消去の前に送った要求が最後まで読まれ、その答えが消した後のキャッシュへ入った。
|
|
207
|
+
- **混み合いのゲートで、利用者の検索と新しい答えの詳細をすぐに断らない**: 受け入れのゲートが満杯か閲覧者が取り分を持ちきっているとき、検索は `SEARCH_WAIT_MS` (3,200 ms。ステージングで測った最も遅い LIKE の検索
|
|
208
|
+
1.6 秒のおよそ 2 倍) まで、ID でまとめた詳細は検索の印と同じ `BATCHED_READ_WAIT_MS` (2,000 ms) まで、閲覧者ごとに `DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT` 個・全体で受け入れの同時数まで枠を待ち、
|
|
209
|
+
待ち行列か待ちの上限を超えれば今までどおり 1 秒の `Retry-After` の 429。すぐに断るのは絞り込みの候補だけ (クライアントは 1 度に 1 つしか送らない)。
|
|
210
|
+
以前は、解除した前の検索やほかのビューの検索の SQL が閲覧者の枠を持つ間 (SQL は終わるまで枠を持つ) に確定した検索が「混み合っている」で終わり、前の答えの捨てられたまとまりが取り分を持つ間に届いた
|
|
211
|
+
新しい答えの最初の詳細のまとまりもすぐに 429 で断られた。
|
|
212
|
+
- **絞り込みの行を開いた帯の下の隙間はちょうど G**: 帯の高さを格子のブロックの刻み q へ切り上げた余り (0 から q − 1 px) は、検索の行と絞り込みの行の間に来る。帯 (`VIEW_TOOLBAR_BAND_CLASS_NAME`) は縦の flex の列になり、
|
|
213
|
+
検索窓のランドマーク (`SEARCH_BOX_CLASS_NAME`) が列の空きを受け取って 2 つの行の間に置く。行の最後の中身 (箱、伝えることがあれば状態の行) は帯の下端で終わり、閉じた行の操作部品と同じくビューの最初の面のちょうど G 上にある。
|
|
214
|
+
行を開いても検索の行は動かず、検索の操作部品から見出しまでは G 以上 G + q 未満で、行の中の隙間 (見出しと部品の間 4・`sm` 未満の箱の間 G・状態の行の上 G) は変わらない (余りを見出しと部品の間に置くと、q = 8 でその間が 11 まで広がり、上の箱の部品のほうに近く読める)。
|
|
215
|
+
以前は余りが帯の下端に残り、高さ 38px のホストの部品で、帯の最後の中身からビューの最初の面までが q = 4 で G + 2、q = 8 で G + 6 になった。
|
|
216
|
+
- **絞り込みの切り替えは開いた状態を見せ、AND/OR の切り替えと同じ見た目の文法に従う**: 2 つの切り替えは 1 つの面の組 (`SEARCH_TOGGLE_FACE_CLASS_NAMES`) から作る。12px の semibold の文字の 24px の丸い札で、
|
|
217
|
+
絞り込みの切り替えは閉じていれば欄の面 (OR と同じ)、開いていれば塗りの面 (AND と同じ blue-700 に白の文字、暗色は blue-600。強制配色では `HighlightText` を載せた `Highlight`) で描く (`SEARCH_FILTERS_TOGGLE_CLASS_NAMES`)。
|
|
218
|
+
面の間で違うのは色だけなので開け閉めで切り替えは動かず、名前・`aria-expanded`・`aria-controls` は今までどおり。AND の面も強制配色では `Highlight` で描く。
|
|
219
|
+
以前は絞り込みの切り替えが角の丸い四角の欄の面 1 つで、開いても閉じても同じに見え (違うのは `aria-expanded` だけ)、AND/OR の切り替えと形も文字の太さも違った。
|
|
220
|
+
- **絞り込みの行は検索のランドマークの中にある**: 検索窓の `<search>` (`data-testid="daily-report-search"`) は検索の行と、開いている間は絞り込みの行を持つので、絞り込みの箱は検索の一部として支援技術に届く。
|
|
221
|
+
Tab の順 (入力欄・絞り込みの切り替え・絞り込みの箱) と帯の見た目は変わらず、ランドマークは名前を持たないまま。以前は絞り込みの行がランドマークの外 (その後ろの兄弟) にあった。
|
|
222
|
+
- **壊れた ids の行で一覧を欠いたまま完走しない**: JSON として読めない行と厳格な読み手が拒む行は、警告 1 行 (`rejected a malformed line`) で接続を止めて試行を終え、最後に受けた行のカーソルとトークンから読み直す (それまでに受けた行は残り、その前進は失敗の数を戻す)。拒み続ける行は、失敗の上限で最初の走査を `failed` にし、再検証は誤りを持って諦める (`complete` と完走の時刻のまま)。
|
|
223
|
+
以前は壊れた行を警告して読み飛ばし、後の行の `isLast` で走査が `complete` になり (最初の走査は欠けた一覧で完走し)、再検証では完走の剪定がその行の日報 (最大 4,000) を一覧から消した。
|
|
224
|
+
- **再開の値はサーバーが読み返す形のときだけ採る**: 行の `nextCursor` は ID が正の安全な整数で営業日が正準のキーか null のとき (`isResumableIdsStreamCursor`。サーバーの再開の解釈と同じ判定)、`snapshot` はトークンの形のとき (`isIdsStreamSnapshotToken`)、`total` は 0 以上の安全な整数のときだけ採る (行の日報は受ける)。以前は数ならどのカーソルの ID (1.5・0・1e21) も採り、サーバーが再開をどれも 400 で拒んで走査が失敗するまで繰り返した。
|
|
225
|
+
- **ホストが同じ選択を続けて断っても、側面ペインは List の選択を見せる**: キー・行の起動・クリックの経路が選択の置き場へ書くたびに、合わせる効果が同じ確定で走る (頼みの数 `selectionRequest`)。以前は、断った矢印キーの後の Enter・2 度の矢印キー・2 度のクリックで置き場がホストの選択 (601) と違う日報 (602) のまま残り、側面ペインが List の選択していない日報を見せた。
|
|
226
|
+
- **添付のファイル名は利用者の文字の間隔の上書きでもはみ出さない (WCAG 1.4.12)**: 名前は計算済みの字間 (コードポイントごと) と語間 (語の区切りの文字ごと) まで数えて測る (canvas の `letterSpacing` は Safari 17.4 に無いので算術。負の値は数えない)。数えられない形 (パーセント・それを含む数式) の列は収めずに CSS の省略で描く。
|
|
227
|
+
以前は書体だけで測ったので、字間 0.12em・語間 0.16em の上書きで、収めた名前が 240 px の列から 29〜70 px はみ出して隣のタイルの名前や行の操作に重なった (Chromium 148 の探りで 7 つのうち 2 つ、最大 64.2 px)。収めた後に掛かった上書きは次の収め直しまで残る (README の **File name** の Limit)。
|
|
228
|
+
- **文書の誤り**: README の添付の格子の例は細い古典的なスクロールバーの溝を 10 px とした (Chromium 148 の Linux では 11 px で、側面ペインの格子は W = 725、列 231 px で余り 0、電話の列は 165 px)。端の溝の実測の例は窓の高さを書かず、別の窓の高さの値を混ぜていた。0.37.0 の Migration の行は公開の後に足した前方への参照を外し、公開した文に戻した。
|
|
229
|
+
|
|
230
|
+
- **配布する CSS にサーバーのコードの語から作った規則を載せない**: standalone の CSS の走査は、spec とテストの補助に加えてサーバーのコード (`src/server/**`・`src/server.ts`) を外す。要素を描かないサーバーのコードの語 (列名の `ordinal`・文書の `lowercase` や `paused`) から作った `.ordinal`・`.lowercase`・`.paused` は、パッケージのどの要素にも付かないのに、同じクラス名を持つホストの要素の見た目を変えていた (`.paused` はアニメーションを止める)。README の Tailwind のホストの例も、公開物では `dist/client.mjs` だけを、モノレポではサーバーのコードを除いて走査する。
|
|
231
|
+
|
|
232
|
+
### Breaking
|
|
233
|
+
|
|
234
|
+
- **`DailyReportDetail.isDraft` は必須で、SSE のストリームのキーは `:v3`**: どの詳細もサーバーが決めた `isDraft` を持ち、SSE のスキーマ (`dailyReportDetailSchema`) は既定値で補わずに必須で読む。`isDraft` を持たない前の版のエントリを読まないよう、ストリームのキーの版を上げた (`<streamKey>:v3`)。
|
|
235
|
+
Migration: データベースへ書くどのワーカーもこの版で一緒に配備する (前の版のワーカーは `:v2` へ書き続け、新しい版の接続には届かない)。前の版のストリーム (`<streamKey>:v2`) は書かれなくなるので、手で消せる。詳細を自分で組むホスト (代役のサービス・試験の固定値) は `isDraft` を足す。
|
|
236
|
+
- **クライアントの `config.draftLabelName` は削除**: ビューは名前を比べずに `isDraft` で下書きを決めるので、設定は下書きの名前を持たない (`draftLabelName` を渡すと閉じたキー集合の外の未知キーとして `RangeError`)。サーバーの `draftLabelNames` は今までどおり必須。
|
|
237
|
+
楽観的に作った日報はサーバーの日報が届くまでラベルを持たないので、下書きの札はそのときに出る。公開は答えの前に下書きの状態を外し、下書きのラベルの札は答えまで残る。
|
|
238
|
+
Migration: `config` から `draftLabelName` を消す。
|
|
239
|
+
- **`search-marks` の答えは `deferred` を持ち、欄の印は密でもありうる**: 答えは `{ wordsDigest, reports, deferred }` で (`DailyReportSearchMarksAnswer.deferred` は必須。予算を超えて印を付けなかった読める日報の ID)、欄の印 (`DailyReportSearchFieldMarks`) は `{ fingerprint, ranges }` か `{ fingerprint, dense: true }` (範囲が 10,000 を超える欄。範囲を持たない)。
|
|
240
|
+
Migration: サーバーとクライアントを同じ版で配備する (前の版のクライアントは後回しの ID を頼み直さず、密の欄を持つ答えを丸ごと拒む。この版のクライアントは `deferred` の無い前の版の答えを拒む。どちらもその行は印なしになる)。答えを自分で読むホストは `deferred` の ID をもう一度頼み、`"ranges" in field` で密の欄を見分ける。`markDailyReportSearchHits` を直接呼ぶホストも同じ形を受ける。
|
|
241
|
+
- **`SqlResultCache` の構築は `maxBytes` を求める**: `new SqlResultCache({ defaultTtlMs, maxBuckets, maxBytes }, epochStore)` の `maxBytes` (見積もったバイト数の上限、1 以上の整数) は省けない。`createDailyReportServer` の `cacheMaxBytes` は任意 (既定 512 MiB)。
|
|
242
|
+
Migration: キャッシュを自分で作るホストは `maxBytes` を渡す (`createDailyReportServer` を使うホストは何も変えない)。
|
|
243
|
+
- **ids のストリームの変化の行は列のまとまり**: `DailyReportIdsStreamItemsChange` の `appended` と `removed` は `DailyReportRowBatch`、`replaced` は値の変わった行の前と後の値の組 `{ previous, next }` (`DailyReportIdsStreamItemReplacements`。2 つのまとまりの行 `i` は同じ日報) で、`DailyReportIdsStreamItemReplacement` (行ごとの値の変化) は無い。
|
|
244
|
+
Migration: 変化を読むホストは、まとまりの行を `itemAt(i)` (`length` まで) か `items()` で読み、値の変化は `replaced.previous.itemAt(i)` と `replaced.next.itemAt(i)` の組で読む。一覧全体は `DailyReportIdsStreamClient.readRows()` (列) か `readItems()` (行のオブジェクト) で読む。
|
|
245
|
+
|
|
5
246
|
## 0.39.0 (2026-10-09)
|
|
6
247
|
|
|
7
248
|
### Added
|
|
@@ -160,7 +401,7 @@ All notable changes to `@aiquants/daily-report` are documented here.
|
|
|
160
401
|
### Breaking
|
|
161
402
|
|
|
162
403
|
- **`DailyReportActionState.items` は配列でない**: アクションの状態の行 (`useDailyReportActionContext().items`) は、長さ (`length`)・位置の日報 (`at`)・走査だけで読む永続の並び (型は `Pick<ReadonlyArray<DailyReportItem>, "length" | "at"> & Iterable<DailyReportItem>`) で、行が変わるたびに新しいオブジェクトになる。ビューの `dailyReportItems` も同じ形を受ける (配列も渡せる)。
|
|
163
|
-
Migration: `items` を配列として読むホスト (`items[i]`・`map`・`filter`・`find`) は、`items.at(i)`・`for…of`・`Array.from(items)` で読む (`Array.from` は一覧全体を写す)
|
|
404
|
+
Migration: `items` を配列として読むホスト (`items[i]`・`map`・`filter`・`find`) は、`items.at(i)`・`for…of`・`Array.from(items)` で読む (`Array.from` は一覧全体を写す)。
|
|
164
405
|
- **`markDailyReportSearchHits` は要求のシグナルを求める**: サービスの `markDailyReportSearchHits(scope, userId, plan, ids, { signal })` は 5 つ目の引数に要求のシグナルを取り、確かめた時点で中断されていればその `reason` を投げる。
|
|
165
406
|
Migration: サービスを自分で呼ぶホストは、要求の `request.signal` (止めないなら `new AbortController().signal`) を渡し、投げた `reason` を要求の中断として扱う。
|
|
166
407
|
|