@aiquants/daily-report 0.36.0 → 0.38.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,17 +2,124 @@
2
2
 
3
3
  All notable changes to `@aiquants/daily-report` are documented here.
4
4
 
5
+ ## 0.38.0 (2026-10-09)
6
+
7
+ ### Added
8
+
9
+ - **検索の絞り込み**: 検索を、作成者 (一覧に出る作成者 `employeeName`。詳細と同じ伏せ方)・区分 (`Internal` と `externalSources` の各アダプターの区分)・顧客 (詳細に出る顧客名) で絞り込める。どれも複数の値を選べ、1 つの絞り込みの値どうしは OR、絞り込みどうしと文字とは AND。
10
+ 作成者と顧客は索引の値と完全に等しい値だけが通り (値の無い日報は通らない)、区分は大小文字によらず比べる。絞り込みの値には印を付けない。
11
+ 文字が空白だけ (`String.prototype.trim` が読む空白。クライアントとサーバーが共有する `isBlankSearchText`) で絞り込みが 1 つ以上あれば、絞り込みだけの検索として、閲覧者が読める日報のうち絞り込みを通るものをすべて ID の列の順に並べる (答えの `words` は `[]` で、ハイライトも一致の印も無い)。
12
+ 空白でないのに語が残らない文字 (幅の無い文字だけなど) は、絞り込みがあっても今までどおり 400。
13
+ - **`search` の絞り込みの引数**: `author`・`sourceType`・`customer` (`DAILY_REPORT_API_QUERY_PARAMS` の `searchAuthor`・`searchSourceType`・`searchCustomer`) を値ごとに繰り返して送る (値はコンマを含みうるので、コンマでつながない)。上限 (`DAILY_REPORT_SEARCH_FILTER_LIMITS`) は、1 つの絞り込みの値 16 個・値 1 つ 200 単位 (UTF-16)・作成者と顧客の値の長さの合計 300 単位。
14
+ 値は送られたままに読み (切り詰めも捨てもしない)、空白だけの値・正規の形でない値 (前後に `trim` が読む空白がある・対を持たないサロゲートを含む)・長すぎる値・多すぎる値・同じ値の重複 (区分は大小文字によらず比べる)・長さの合計の超過・`Internal` でも外部ソースの区分でもない区分は、`{ error: { message: "Invalid search filters", reason } }` の 400。
15
+ 理由は `filter-blank-value`・`filter-value-not-canonical`・`filter-value-too-long`・`filter-too-many-values`・`filter-duplicate-value`・`filters-too-long`・`filter-unknown-source-type`。400 の順は `q` と `combine`・絞り込み・文字の計画で、どれもサービスの呼び出しより前。絞り込みの引数の無い要求は、SQL に条件を足さない。
16
+ - **絞り込みの候補 `GET {apiBasePath}/search-facets`** (JSON。`DailyReportSearchFacets` の `{ sourceTypes, authors, customers }`): 区分は、閲覧者が読める区分を設定の順 (`Internal`、続いて `externalSources` の順。大小文字だけが前の区分と違う後の区分は除く) に、問い合わせずに返す。
17
+ 作成者と顧客は、閲覧者が開ける日報の索引の文書の NULL でない値を、検索と同じ範囲の条件の SQL の `GROUP BY` で重複なく、列の順に、件数を絞らずに返す (ほかの人の下書き・消した日報・見えない区分の日報の値は出ない)。サーバーではキャッシュしない。
18
+ 候補と絞り込みは索引の値を読む (パッケージの保存と突き合わせが書き直す) ので、パッケージの外で元の行を変えるホストは、その日報の突き合わせを流す。
19
+ 受け入れは同じ工場から作る 4 つ目のインスタンスで、検索と同じ `concurrency` と `rateLimitPerMinute` に自分のバケットを持つ (拒否の行は `route=search-facets`)。検索の設定が無ければ 404、内部 ID の分からない利用者には受け入れも問い合わせも無しに空の候補を返し、答えは ETag 付きで、同じ候補への `If-None-Match` には 304。
20
+ サービスの `listDailyReportSearchFacets(scope, userId)` と、絞り込みが名指せる区分 `searchFilterSourceTypes`。
21
+ - **ホストが描く絞り込みの部品 `config.search.renderFilterSelect`**: 関数 (`DailyReportSearchFilterSelectRenderer`) を渡すと、検索窓の入力欄の後に絞り込みの切り替え (`labels.searchFiltersToggle(n)`。n は選んだ値の数) が出て、開くと検索の行の下に、作成者・ソース区分・顧客の 3 つの箱と状態の行を持つ絞り込みの行が出る。
22
+ 候補は行を開くたびに読み直し (要求は 1 本ずつ。読み直しの間と、読み直しに失敗したあとは前の候補のまま)、作成者と顧客はロケールの照合の順、区分はサーバーの順で、見出しはバッジの見出し。渡さなければ、切り替えも行も候補の要求も無い。
23
+ 箱 (`<fieldset>`) は見出し (`<legend>`) を部品の上に見せ、見出しが箱の名前になる。読み直しで候補から消えた選んだ値は、その箱の候補の後ろに足すので、部品はそれを見せて外せる。
24
+ 箱ごとの部品は `{ facet, label, options, value, onChange, locale }` (`DailyReportSearchFilterSelectProps`) を受ける制御された部品で、`onChange` へは `options` の値の重複の無い部分集合を渡す (それ以外は `RangeError`)。上限を超える選択は受けずに状態の行で知らせる (知らせは、外からの解除や新しい選択で選択が変われば消える)。
25
+ 箱ごとに誤りの境界を置くので、描画で投げた部品はその箱だけを `labels.searchFilterUnavailable` にし、ほかの箱と検索窓は動き続ける。境界の中に Suspense の境界も置くので、描画を待たせる部品 (`React.lazy`・`use()`) はその箱だけに `labels.searchFiltersLoading` を見せ、画面のほかの所を隠さない。絞り込みの変更は入力欄の今の文字と AND/OR で確定し直し、Enter・デバウンス・AND/OR の切り替えも今の絞り込みを載せる。
26
+ パッケージはブラウザーのコードに `@aiquants/select-box` も `@aiquants/fuzzy-search` も取り込まない (`src/client-bundle.structure.spec.ts` がソースと作り直した束で確かめる) ので、パッケージのブラウザーの下限は変わらない。部品は自分の下限を持つ (`@aiquants/select-box` で作る部品は、`Intl.Segmenter` のために Firefox 125)。
27
+ - **ラベル 6 個** (固有キーは 102 から 108。文字列のキー 89・整形関数 19。キーの表は全 119 キー): `searchFilterSourceType` (en `Source`・ja `ソース区分`)・`searchFiltersLoading`・`searchFiltersTooMany`・`searchFilterUnavailable` と、整形関数の `searchFiltersToggle(count)` (en `Filters` / `Filters (3)`・ja `絞り込み` / `絞り込み (3)`。0 なら数を出さない)・`searchFiltersFailed(failure)` (候補を読めなかったときの状態の行。`busy`・`unavailable`・`failed` ごとに、検索の失敗の文言と別の、候補のことを述べる文言。`busy` と `failed` は開き直せば読み直すことも述べ、`unavailable` (サーバーが絞り込みを持たない) は述べない。ja `絞り込みの候補を読み込めませんでした。絞り込みを閉じて開き直すと読み直します`)。作成者と顧客の箱の見出しは `author` と `customer`。
28
+ - **公開の名前**: server の `DailyReportSearchCriteria`・`DailyReportSearchFilters`・`DailyReportSearchNameFilter`・`DailyReportSearchFacets`、client の `DAILY_REPORT_SEARCH_FILTER_FACETS`・`DailyReportSearchFilterFacet`・`DailyReportSearchFilterOption`・`DailyReportSearchFilterSelectProps`・`DailyReportSearchFilterSelectRenderer`。
29
+ 共有エントリの `DAILY_REPORT_API_ENDPOINTS` は `searchFacets` を、`DAILY_REPORT_API_QUERY_PARAMS` は `searchAuthor`・`searchSourceType`・`searchCustomer` を持つ。
30
+
31
+ ### Changed
32
+
33
+ - **索引の文書は絞り込みの値を持つ**: `DailyReportSearchDocument` の `author_name` と `customer_name` (NULL を許す `nvarchar(200)`、`Latin1_General_100_BIN2`) に、作成者と顧客を、一覧が見せる正規の形 (`String.prototype.trim` で前後の空白を除き、対を持たないサロゲートを U+FFFD にし、何も残らなければ NULL) で書く。ホストは作成者と顧客の表示の値を、前後の空白を除いて 200 単位 (UTF-16) までに保つ。超えた値は日報の ID とこの取り決めを名乗る例外 (その日報の保存は失敗し、突き合わせはその日報の区切りで止まる)。
34
+ `defineDailyReportSearchSchema` と `DailyReportSearchDocumentTable` は 2 列 (`authorName`・`customerName`) を持つ。文書の要約は絞り込みの値の節 (`P`) を持つので、値だけが変わった日報も突き合わせが書き直す。要約は文字を UTF-16 の単位のまま入れる (対を持たないサロゲートと U+FFFD を見分ける)。正規化の版は変わらない。
35
+ - **検索のセッションは絞り込みと一緒に確定する**: 世代は、正規の形の絞り込み (値ごとに重複を除き、UTF-16 の単位の順に並べたもの) が変わったときにも進み (並びだけの違いでは進まない)、静かな送り直しも同じ絞り込みを載せ、解除は絞り込みも消す。
36
+ 消去のボタンと Esc は文字だけを消し、絞り込みが残れば絞り込みだけの検索として続け、残らなければ解除する。消去のボタンは消す文字があるときだけ出る (絞り込みだけの検索で入力欄が空なら出ず、Esc も何もしない)。絞り込みの部品を渡さないホストでは、どれも今までどおり。
37
+ - **開発者向け**: `src/client-bundle.structure.spec.ts` は、`@aiquants/select-box` も `src/client` と `src/shared` のソースと作り直した `client.mjs`・`index.mjs` に入らないことを確かめる。
38
+
39
+ ### Breaking
40
+
41
+ - **検索の設定を持つホストは、配備の前に文書の表へ 2 列と索引を足す**: この版は保存と突き合わせのたびに `author_name` と `customer_name` を書くので、列の無い表ではどの保存も失敗する。`search.tables.document` に自前のモデルを渡すホストは、モデルにも `authorName` と `customerName` が要る (無ければ `search` の検査が `RangeError`)。
42
+ Migration: 配備の前に `DailyReportSearchDocument` へ `author_name nvarchar(200) collate Latin1_General_100_BIN2 NULL` と `customer_name nvarchar(200) collate Latin1_General_100_BIN2 NULL` を足し、それぞれに索引を作る (候補の `GROUP BY` と絞り込みの `IN` が読む)。列は NULL を許すので、0.36.0 のプロセスは足した後もそのまま動く。
43
+ データベースへ書くすべてのサーバーがこの版で動いてから `service.reconcileSearchIndex` を 1 回流す (要約が変わるので、すべての文書を書き直して 2 列を埋める)。流し終えるまで、書き直していない文書は作成者と顧客の絞り込みを通らず、その値は候補に出ない。
44
+ 前の版のサーバーが後から書いた文書は、次の突き合わせまで 2 列が空か古いままなので、前の版が無くなってからもう 1 回流し、書き直しが 0 件であることで確かめる。
45
+ - **`service.searchDailyReports(scope, userId, criteria)`**: 3 つ目の引数は計画 (`DailyReportSearchPlan`) でなく条件 (`DailyReportSearchCriteria`: `{ plan, filters }`。絞り込みだけの検索では `plan` が null)。
46
+ Migration: サービスを直接呼ぶホストは、計画を `{ plan, filters: { sourceTypes: null, authors: null, customers: null } }` で包む。
47
+
48
+ ## 0.37.0 (2026-10-09)
49
+
50
+ ### Added
51
+
52
+ - **読みの印の説明**: ローマ字の読みでだけ当たった印 (`reading`) は、支援技術へも説明 (`aria-description`) で弱い当たりであることを伝える。説明は新しいラベル `searchReadingMarkDescription` (en `Matched by its romaji reading`、ja `ローマ字の読みで一致`) で、打った語の印 (`word`) は説明を持たない (よくある印の読み上げを増やさない)。印の属性の値と説明は tier ごとの 1 つの表 (`SEARCH_MARK_LOOKS`) から取る。固有キーは 101 から 102 (文字列のキー 85・整形関数 17。キーの表は全 113 キー)。
53
+ 以前は読みの当たりと語の当たりの違いが下線の形 (点線か実線か) にしか無く、読み上げはどちらも同じ `mark` として伝えた (WCAG 1.3.1)。
54
+ - **DetailList の行の説明に一致の印の文**: 画面が印の store を持つ間、DetailList の行の説明 (`aria-describedby`) は、状態・添付の目印・種別のバッジの後に、見出しの一致の印の隠した文 (`キーワード 2 個のうち 1 個に一致`・`この画面に出していない項目 (ラベル・明細など) で一致しました`) を続ける。隠した 2 つの文は 1 つの要素にまとまり、見える印と置き物はその外にある。
55
+ 以前は行の説明が一致の印を含まず、矢印キーで行を移る読み上げには、目で見る利用者が行ごとに見る `一致 k/n` と `表示外で一致` が届かなかった。
56
+
57
+ ### Changed
58
+
59
+ - **検索の印の要求は送る時に決める**: 印の store は、購読された日報を 1 つの待ち行列に置き、要求の枠が空くたびに、まだ行が購読している日報を新しい順に 100 件まで取って送る (`createIdBatchScheduler`。同時に 2 本まで)。待つ間に行が外れた日報は送らないので、スクロールや押し続けたキーが通り過ぎた行の印は頼まず、止まった窓の印は空いた最初の枠で頼む。
60
+ どの行も購読しなくなった要求は止め (サーバーはその印の計算をやめる。下の Fixed)、送る前に外れた行と、止めた要求の行は、戻ってきたときに頼み直す。日報ごとの記録 (印・送った順・頼んだ epoch・429 のやり直し・再検証) は 1 つにまとまり、どの行も購読しない日報の記録は `SEARCH_MARKS_RECORD_BOUND` (400) を超えた分を古いものから忘れるので、持つ量はスクロールした距離でなく描いている窓に従う。
61
+ 以前はマイクロタスクごとに購読された ID をそのまま束にし、束を古い順に 2 本ずつ、行がまだ載っているかを見ずに送った。2 秒の PageDown の押し続けでは要求 61 本の ID の 93.5 %、1 秒の List のスクロールバーの引きでは 95.2 % が送る時に既に外れた行のもので、止まった窓の印は要求 1 本が 196 / 500 / 927 ms のとき押し終わりから 4.4 / 13.9 / 26.8 秒後に届いた (store の模擬。今は外れた行の ID は 0 で、止まった窓の印は押し終わりの後の最初の要求で届く)。
62
+ - **印が届いても描き直すのは葉と一致の印だけ**: 行 (List のカード・DetailList の本文カード・側面ペインの中身) は自分の部分木を印の囲み (`DailyReportSearchMarksScope`) で包み、日報の印を購読するのは囲みで、行の本体は印を読まない。印が届いて描き直すのは囲みと印を読む葉 (`SearchMarkedText`) と一致の印だけで、行の本体・見出し・添付の一覧・コメントの節は急ぎの描画でも後追いの描画でも描き直さない。
63
+ 以前は行の本体が購読のフックを呼んだので、印が届くたびに本体の部分木を急ぎの描画 (出力は同じ) と後追いの描画で 2 度描いた (載っている 8 行で子孫の描画 48 回・5.9 ms)。
64
+ - **`search-marks` の答えは ETag を持たない**: 経路が組むどの答え (印・400・404・429・離れた要求の 503) も、1 回だけ直列化した `Cache-Control: no-store` の JSON で、ETag を持たず、`If-None-Match` を読まない (共有のセキュリティヘッダーは同じ)。まとまりは送るたびに描いている行から組むので同じ URL はほとんど繰り返されず、パッケージのクライアントは `If-None-Match` を送らない。以前は本文全体の SHA-256 で使われない ETag を作った。
65
+ - **行の並びは塊を共有する永続の並び**: プロバイダーの行は 1,024 行の書き換えない塊を背骨が束ねる並び (`ReportRowList`) で、導きの編集は触らない塊をどれも前の版と共有した新しい版を 1 回で作る。末尾へ足す publish が書くのは背骨と最後の塊の行と足した行だけで、80,000 行に 4,000 行を足す publish の参照の書き込みは 84,000 から 4,128 になった。途中の削除と差し込みは、最初に変わる塊から後ろを 1 回だけ詰め直す。
66
+ ビュー・キーボード・アクションの状態・検索の一覧の導きは、行を長さ・位置 (`at`)・走査だけで読む (下の Breaking)。一覧に無い id の走査 (削除の直後の選択) は塊を位置で読み、行ごとの関数の呼び出しを持たない (22.8 万行で 4.1〜4.6 ms から 1.9〜2.6 ms)。
67
+ - **答えを見せている間の一覧の導き**: 検索の答えの一覧 (`deriveSearchListItems`、`src/client/search/search-list-items.ts`) は、publish が書いた塊だけを確かめ (前の版と共有する塊は確かめ直さない)、答えの日報に関わらない publish では前の一覧そのものを返すので、ビューも行も描き直さない。以前は publish のたびにストリームの一覧全体をなめ、答えを見せている間の押し続けたキーの 1 回の保持ごとに 10〜22 ms の描画を足した。
68
+ - **ids のストリームの無音の見張りは接続ごとにタイマー 1 本**: 本文の read の無音は、接続ごとに 1 本のタイマーが上限に足りない分だけ張り直して見張り (`watchReadSilence`)、無音が `readStallTimeoutMs` に届いたときだけ読み手を取り消してカーソルから再開する。以前は read ごとにタイマーを張って外し、配信中の 1 回の保持ごとに 26〜46 ms を使った。
69
+ - **数の整形は作り置きの書式**: 組み込みの整形関数は、ロケールごとに 1 度だけ作った `Intl.NumberFormat` で桁を区切る (出力は同じ)。以前は呼ぶたびに書式を作る `toLocaleString` を、ストリームの publish ごとに描き直す行の位置と件数のたびに呼んだ。
70
+ - **本文の位置つきの置き換えは 1 回の走査**: 位置つきの表示の文字の置き換え (`replaceDisplayText`) は、区間の位置を前へだけ進める 1 回の走査で、側面ペインの本文 (`paneContentOf`) はタグの数に比例する (段落 2,000 で 122 ms から 1.3 ms。出力は同じ)。添付のファイル名の NFC の表示 (`nfcDisplayOf`) も名前の長さに比例する (244 単位で 1,184 µs から 89 µs)。以前はどちらも長さの 2 乗だった。
71
+ - **読み上げ用のファイル名は自分のはみ出しを切らない**: 添付のファイル名の読み上げ用の写し (名前の中とダウンロードのリンクの中) は、`sr-only` (`overflow: hidden` と `clip`) でなく、切らない透明な 1 px の文 (`VISUALLY_HIDDEN_CLASS_NAME`) に layout の閉じ込めを足して隠す (`HiddenFileName`)。閉じ込めは、仮想化した行の外 (側面ペイン・オーバーレイ) で 1 行の長い名前がスクロールの箱の横幅を広げないため。添付の節はほかに切る中身 (見える名前の省略・収めた名前・大きさの注記・サムネイルの枠の角) を持つので、添付のある DetailList の行は窓をずらすスクロールの 1 段でなお描き直される。
72
+ - **ログ: クライアントの ids のストリームのチャンネル**: ids のストリームのクライアントの警告 (再検証の諦め・読めない行と種類の分からない行の読み飛ばし・形の誤ったアンカー) は、チャンネル `[DailyReportIdsStream]` (`DAILY_REPORT_LOG_CHANNELS` の `IdsStream`) から console へ書く。どの行も接頭辞をちょうど 1 つ持ち、古い詳細の更新の行は `[DailyReportDetailLoader] Stale update skipped id=<id>`、アンカーの行は `skipped a malformed streamAnchor: <値>`。`DailyReportIdsStreamClientOptions.warn` へ注入した書き先はチャンネルの代わりに、文面と引数を接頭辞無しで受ける。
73
+ 以前はクライアントの ids のストリームが文面に `[DailyReportIdsStream]` を手で綴り (一覧に無い 4 つ目のクライアントのチャンネル)、古い更新の行は `[DailyReportDetailLoader] [DailyReportCache] …` と接頭辞を 2 つ持った。
74
+ - **公開の docstring は既定値を値で書く**: `createDailyReportServer` の `cacheDefaultTtlMs` と `cacheMaxBuckets` の docstring は、ホストが読み込めないモジュールの内部の定数の名前でなく、既定値 (60,000 ms と 4,096) を書く。振る舞いは同じ。
75
+ - **開発者向け**:
76
+ - `src/docs-examples.spec.ts` は、公開する markdown (`package.json` の `files` の `.md`) がパッケージの例のスキーマ (`dbo_app`・`dbo_daily_report_search`) のほかのデータベースのスキーマを名指さないことを確かめる (修飾した `dbo_<名前>.<表>`・角括弧で囲んだスキーマで修飾した名前・修飾した `dbo`、修飾の無い `dbo_<名前>`)。0.36.0 の README と CHANGELOG はホストの表の名前を 1 つずつ持った (下の Fixed)。
77
+ - `src/shared/logger.call-sites.spec.ts` は、ロガーのモジュールの外のどの文字列の字面も `[DailyReport` の接頭辞の形を綴らないことを確かめる。
78
+ - 12 px の文字の下限の検査は、`src/client` の下のどのモジュールも 1 回の走査で読み (例外の一覧は無い)、検索の葉と一致の印も対象にする。
79
+ - 印が届いたときの描画の回数 (List・DetailList・画面)、`表示外で一致` の場合ごとのビューのテスト、印の store の模擬の場合 (60 回の確定・2 つの聞き手・429 と dispose)、塊の共有、下書きの規則の SQL (本物の SQL の処理系で流す) を、それぞれの spec が確かめる。
80
+ - 印の tier は名前 (`DAILY_REPORT_SEARCH_TIER`: `word` が 0、`reading` が 1。入口からは出さない) で書き、通信の読み取りと計画と印の見え方の表が同じ判定 (`isDailyReportSearchAlternativeTier`) を使う。表に無い tier の印はパッケージの名を名乗る誤りで描画を止める (通信の読み取りが通さないので、来るのは崩れた不変条件だけ)。
81
+ - `display-text.ts` の改行のタグの規則は 1 つの元 (`LINE_BREAK_TAG_SOURCE`) から 3 つのパターンを作る (振る舞いは同じ)。
82
+ - 文書の直し: `TabsContent` の `tabStop` (最初の意味のある中身がフォーカスを受けないか、中身が Tab の届くべきスクロールの容器のとき)、内部ユーザーを持たない閲覧者の読み取り (データの問い合わせは無いが、ID の解決は受け入れの前と読み取りの中で 2 回走り、見つからない間はキャッシュしない)、`mergeComments` と `resolveCommentColorClass` の docstring。
83
+
84
+ ### Fixed
85
+
86
+ - **`表示外で一致` はビューの葉が描いた印から決める**: 行の葉 (`SearchMarkedText`) は描いている間だけ、欄の印が描く値について今のものかと塗った印の数を行の集計 (`drawn-search-marks-tally.ts`) へ登録し、一致の印は集計だけを読む。登録がどれも今のもので合計が 0 のときだけ `表示外で一致` を出す。
87
+ 以前の一致の印はビューが描く欄の写しを手で持ち (`countVisibleSearchMarks`)、4 つの場合でビューと食い違って、印も `表示外で一致` も出さなかった: 側面ペインの関連のタブ (記事を描かないのに件名・顧客・本文・添付・コメントを描いたと数えた)、利用者が削除を送っているコメント、ホストのひな形が素で描く面談者の札、収めたファイル名の省いた中ほどだけの当たり。CSS の省略が隠す当たりは、印を塗るので今も描いたものと数える (何を隠すかはブラウザーが決める)。
88
+ - **一致の印の書体は 12 px**: 一致の印 (`SEARCH_INDICATOR_CLASS_NAME`) は、ページの文字の書体でパッケージの下限の 12 px (`text-xs`) を 16 px の行に置き、数字は等幅の数字 (`tabular-nums`) で `一致 k/n` の幅を一定にする。色と比は同じ (slate-600 / 暗色 slate-300)。以前は 11 px の等幅で、パッケージの 12 px の下限を割り、`一致`・`表示外で一致` は各 OS の等幅の和文の代わりの書体で描かれた (下限の検査は、検索の部品のディレクトリを読んでいなかった)。
89
+ - **側面ペインの作成者の行は折り返す**: 作成者の行 (作成者・種別のバッジ・一致の印の枠) は 4 px の間で折り返し、見出し語の `作成者` は縮めずに 1 行のまま、値だけが縮んで折り返す。狭いペインで収まらない一致の印の枠は、自分の 16 px の行へ落ちる。以前は行が折り返さず、200 px の最小の幅から約 400 px までの間は枠が作成者を押し潰し (`作成者` が 1 字ずつ縦に積まれ、長い名前で見出しの行が 24 px から 160 px に伸びた)、最小の幅では枠が見出しの箱からはみ出して切られた。
90
+ - **検索の印の要求は離れたら枠を返し、新しい答えのまとまりは待つ**: `search-marks` の経路は要求のシグナルをサービスへ渡し (`markDailyReportSearchHits(…, ids, { signal })`)、サービスは読み取りの前と日報ごとの印の前に確かめて、離れた要求の仕事をそこで止める (走っている SQL の読み取りは止められないので、枠はその終わりまで持つ)。離れた要求には本文の無い 503 (`Retry-After` 付き) で答え、誤りの行を書かない。
91
+ 受け入れは、ゲートが満杯か閲覧者が取り分を持ちきっているとき、検索の印だけを待たせる (閲覧者ごとに `DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT` 個・全体で受け入れの同時数まで、`SEARCH_MARKS_WAIT_MS` = 2,000 ms まで。検索と ID でまとめた詳細は今までどおりすぐに断る)。中断された要求の拒否は、3 つの経路のどれでも満杯として記録しない。
92
+ 以前は離れた要求のサービスが 2 つの SQL の読み取りと印の計算を最後まで走らせて閲覧者の取り分を持ち、新しい検索の最初のまとまりが待たずに 429 (`Retry-After: 1`) で断られた。負荷の下では 1 回きりのやり直しも断られ、その行はその epoch の間、印なしのままだった。
93
+ - **取りこぼしのゲートは枠の少ない閲覧者へ先に渡す**: キャッシュの取りこぼしのゲート (`createCacheMissGate`) とサムネイルの生成の待ち行列は、空いた枠を、上限に達していない利用者のうち保持数の最も少ない利用者の最も古い待機者へ渡す (`createConcurrencyGate`。利用者の中と保持数の等しい利用者の間は到着順)。以前は上限に達していない最も古い待機者へ渡したので、全部の枠が埋まっている間は、上限まで持つ閲覧者の先に並んだ読み取りが枠を取り直し続け、後から来た閲覧者の最初の読み取りが待った (模擬では、忙しい閲覧者が 8 人いるときの新しい閲覧者の最初の読み取りの中央値が 1,293 ms から 334 ms になった。残りはドライバーの接続の待ち)。
94
+ - **429 を待つ間の dispose も頼み直しを残さない**: 印の store を dispose すると、429 のやり直しの時計が持つ日報も、待っている・送っている日報と一緒に頼んでいないものに戻す。以前は時計だけを消したので、React が同じ store をもう一度マウントすると (`<Activity>` を隠して見せ直す)、その日報はその epoch の間二度と頼まれず、行は印なしのままだった。429 で断られた行が `Retry-After` の前に外れて後で戻った場合も同じだった。
95
+ - **長い行の印は書記素の塊を割らない**: 4,000 単位より長く片に分けた行では、片の境目が書記素の塊を割りうるので、印の範囲を生の値の塊の境目まで広げる (`Intl.Segmenter`)。以前は 👍🏽 の 4,000 単位を超える連なりを 👍 で探すと、印が絵文字を割った。上限以内の行の範囲は同じ。
96
+ - **印の範囲のまとめは出現の数と欄の長さに比例する**: 欄の印は tier ごとの覆いの差分からまとめる (`markSearchText`)。範囲は重ならない最大の連なりなので、欄の範囲は単位の数を超えず、答えは描く値の長さに比例する (1 字の語で 21,200 単位の日報 100 件を探す答えは 1.6〜2.9 MB で、同じ日報の詳細の答えは 2.2 MB)。以前は出現ごとに覆う単位を塗ったので、費用は出現の数 × 覆う長さで、長い書記素の塊の中の出現では数の 2 乗に伸びた (塊の境目まで広げた肌の色の修飾 10 万個の塊 1 つで 51 秒)。
97
+ - **読みの計画は語を打つ順によらない**: 語とキーワードの重複を、正規化した文字でなく候補の集まり全体 (文字と tier。読みを含む) で除く。正規化して同じでも読みを持つかが違う語 (`Ⅱです` は読まず、`iiです` は イイデス と読む) は両方残り、語を打つ順を入れ替えた検索語は同じ計画になる (キーワードと語の並びのほか)。上限は今までどおり打った語で数える。以前は先に打った語だけが残り、読みを探すかが打つ順で決まり、OR の検索語に語を足すと答えが狭まりえた。
98
+ - **長音を重ねた 1 字の読みは足さない**: 読みの文字の塊 (`readingLetterClusterCountOf`) は、長音の連なりを 1 つと数える。`ha--` → ハーー と `ha---` → ハーーー は文字の塊が 2 つなので読みを持たず、`site-` → シテー と `ko-hi-` → コーヒー は読む。以前は連なりの長音を 1 つずつ数えたので、ハーー は 3 つと数えて足し、「それは――」(索引では ソレハーー) のような言い回しに当たった。
99
+ - **NFKC で英字でなくなる修飾文字の語は読まない**: 読みを持つには、打った語に加えて正規化した語も英字の文字を含む必要がある。英字の修飾文字 U+10781 (→ ː) と U+10782 (→ ˑ) を足した語 (`納期ヲ確認` と修飾文字) は読みを持たず、0.35.0 と同じ計画になる。以前は打った語の判定だけを通り、正規化した語の読みが `納期オ確認` のような同音の畳み込みを足した。
100
+ - **下書きの規則は 1 つの定義で、日報は 1 行**: 閲覧者が何を見てよいかを決めるサービスの読み取り (ID の一覧と最初のページ・添付・ID でまとめた詳細と営業日の詳細と検索の印が使う `fetchVisibleHubDetails`・詳細・ミューテーションの対象の確かめ) と検索の SQL は、下書きの規則を 1 つの定義 (`buildDraftVisibilityCondition`: 作成者か、日報のどのラベルも下書きの名前を持たないことを、ラベルの関係への `NOT EXISTS` で書く) から作る。見える日報の集合は同じ。
101
+ 以前の読み取りは下書きのラベルの関係を `LEFT JOIN` で結合して絞ったので、閲覧者自身の日報が下書きのラベルを 2 つ持つと (`draftLabelNames` に複数の名前) その日報を 2 行で返した (`search-marks` だけは答えの中で重複を除いていた)。ステージング (22.8 万件、下書きの関係は 0 件) の ID の一覧の全量の読み取りは、CPU が 500〜516 ms から 248〜263 ms に減り、経過時間は計画が並列のハッシュ結合から直列の入れ子のループになって 128〜162 ms から 248〜263 ms に延びた (最初のページは 7 ms)。
102
+ - **自動既読のスイッチの文言は自分のスイッチに結ぶ**: 自動既読のスイッチと文言は `useId` の id で結び、開発用の SSE のスイッチは固定の id を持たない (自分の `aria-label` が名前)。以前は文書全体で固定の id (`auto-read-mode`・`sse-mode`) を使ったので、画面が 2 つ描かれると (`<Activity>` が隠した画面を残すとき) 2 つ目の文言が 1 つ目のスイッチを切り替え、2 つ目のスイッチは名前を失った。
103
+ - **文書の誤り**: 0.36.0 の README と CHANGELOG は、外部の文字だけで当たった日報の例にホストの表の名前を書いていた (README の **Highlights** と、下の 0.36.0 の Added。汎用の口 `externalSourceTexts` の言葉で書き直した)。README は、一覧の 1 行に切った後ろとファイル名の省いた部分を「印は付くが見えない」と書いたが、Chromium は印を切る省略記号を印の中に描く (印の地は `…` の下まで続き、下線は最後に見える文字で終わる)。
104
+
105
+ ### Breaking
106
+
107
+ - **`DailyReportActionState.items` は配列でない**: アクションの状態の行 (`useDailyReportActionContext().items`) は、長さ (`length`)・位置の日報 (`at`)・走査だけで読む永続の並び (型は `Pick<ReadonlyArray<DailyReportItem>, "length" | "at"> & Iterable<DailyReportItem>`) で、行が変わるたびに新しいオブジェクトになる。ビューの `dailyReportItems` も同じ形を受ける (配列も渡せる)。
108
+ Migration: `items` を配列として読むホスト (`items[i]`・`map`・`filter`・`find`) は、`items.at(i)`・`for…of`・`Array.from(items)` で読む (`Array.from` は一覧全体を写す)。
109
+ - **`markDailyReportSearchHits` は要求のシグナルを求める**: サービスの `markDailyReportSearchHits(scope, userId, plan, ids, { signal })` は 5 つ目の引数に要求のシグナルを取り、確かめた時点で中断されていればその `reason` を投げる。
110
+ Migration: サービスを自分で呼ぶホストは、要求の `request.signal` (止めないなら `new AbortController().signal`) を渡し、投げた `reason` を要求の中断として扱う。
111
+
5
112
  ## 0.36.0 (2026-10-08)
6
113
 
7
114
  ### Added
8
115
 
9
116
  - **検索の答えのハイライト**: 答えを見せている間、List のカード (本文の先頭の行と作成者)・側面ペイン・DetailList の行は、サーバーが当たりと判定した所に `<mark data-daily-report-search-mark>` を付ける。打った語の形で当たった所 (正規化でだけ当たった所を含む: `デンプン` は でんぷん に付く) は `word` で実線の下線、ローマ字の読みでだけ当たった所は `reading` で点線の下線。
10
117
  側面ペインと DetailList は検索する欄の描いている文字すべて (件名・本文・作成者と更新者と社員の名前・区分と作成区分・顧客・面談者の名前と所属・添付のファイル名・コメントの作者と本文) に付ける。2 つ以上のキーワードの OR では、日報が当たったキーワードの語だけに付ける。
11
- 印を付けないもの: ラベル名と外部の文字 (kintone の明細の表 (`dbo_kintone.TD日報明細`) だけで当たった日報には印が出ない)・一覧と側面ペインが除くタグ・一覧の 4 行目以降と切った後ろ・省いたファイル名の部分・編集のフォーム・作者不明の代わりの名・値でない文字。そのまま値を差し込まない `interviewerWithAffiliation` のひな形は札を素で描く。
118
+ 印を付けないもの: ラベル名と外部の文字 (ホストの読み取りの口が外部ソースの区分ごとに渡す文字 (`externalSourceTexts`) はビューが描かないので、それだけで当たった日報には印が出ず、一致の印が `表示外で一致` を出す)・一覧と側面ペインが除くタグ・一覧の 4 行目以降と切った後ろ・省いたファイル名の部分・編集のフォーム・作者不明の代わりの名・値でない文字。そのまま値を差し込まない `interviewerWithAffiliation` のひな形は札を素で描く。
12
119
  位置はサーバーが索引と同じ処理から求めるので、印は検索が当たらない所に付かず、ブラウザーは正規化しない (`@aiquants/fuzzy-search` はクライアントの束に入らない。`src/client-bundle.structure.spec.ts` が確かめる)。印は箱を 0 にして書体を受け継ぐので、行の幅も高さも変えない。yellow-200 に slate-900 (暗色 yellow-800 に yellow-50、15.33:1 と 6.61:1)、強制配色では `Mark` と `MarkText`。
13
120
  - **一致の印**: 側面ペインの作成者の行と DetailList の行の見出しに、contact map の点数と同じ書体 (11 px の等幅、slate-600 / 暗色 slate-300) の控えめな印を出す。2 つ以上のキーワードの OR では当たったキーワードの数 (`一致 2/3`)、日報が当たったのにそのビューが描く印が 1 つも無いとき (ラベルや明細だけで当たったなど) は `表示外で一致` (en `Hidden match`)。答えの最も長い文字の幅を前もって取っておくので、印が届いても見出しは流れ直さない。印は `aria-hidden` で `title` を持ち、画面に出さない文を添える。
14
121
  - **ローマ字の読み**: 英字を打った検索語は、fuzzy-search の公開関数 (`queryNeedles` と `convertRomaji`) で読んだ完全な仮名の読みでも探す (`denpun` → でんぷん・デンプン・デンプン)。ヘボン式と IME の綴りの両方 (`shinbun` と `sinbun`)。読みは文字の塊 3 つ以上 (`SEARCH_READING_MIN_CLUSTERS`。数字・記号・先頭のダッシュは数えない) を語ごとに 2 つまで (`DAILY_REPORT_SEARCH_READINGS_PER_WORD`) で、英字の綴りそのものも候補に残る。
15
- 文字の塊が 1 つか 2 つの読みは足さない: 索引は平仮名を片仮名へ畳むので、短い読みはありふれた言い回しに当たる (日報 228,794 件で `site` → シテ が「して」に当たって 43.55 %、1,244 ms)。`memo` や `kome` のような 2 文字の語は仮名で打ち、`ha100` も読みを持たない。
122
+ 文字の塊が 1 つか 2 つの読みは足さない (0.36.0 は長音の連なりの長音を 1 つずつ数えた。下の Fixed を参照): 索引は平仮名を片仮名へ畳むので、短い読みはありふれた言い回しに当たる (日報 228,794 件で `site` → シテ が「して」に当たって 43.55 %、1,244 ms)。`memo` や `kome` のような 2 文字の語は仮名で打ち、`ha100` も読みを持たない。
16
123
  漢字には届かず (`nouki` は 納期 に当たらない)、長音は畳まず、同音の綴りは片側だけ (`numazu` は ぬまづ に当たらないので `numadu` か仮名で打つ)。仮名の語でローマ字の本文は探さない。英字を打っていない語 (NFKC で英字になるローマ数字や № の `Ⅱです`・`№12` を含む) の計画は変わらず、正規化の版も変わらないので、索引の作り直しは要らない。
17
124
  - **検索の答えの `words`**: `search` の答えは `{ ids, businessDates, sourceTypes, words }` で、`words` は計画の語 (キーワード → 語 → 候補 `{ text, tier }`)。`q` と `combine` だけで決まり、閲覧者のことは明かさない。パッケージのクライアントは語を厳しく読み、崩れていれば答えを丸ごと拒む。
18
125
  - **検索の印の要求**: `GET {apiBasePath}/search-marks?q=&combine=&ids=` (ID は 100 件まで) が、頼んだ日報のうち閲覧者が読めるものの欄ごとの印の範囲 (生の値の UTF-16 の位置と tier、値の指紋) と、2 つ以上のキーワードの OR では当たったキーワードの組を返す (`{ wordsDigest, reports }`)。設定が無ければ 404。
@@ -59,7 +166,7 @@ All notable changes to `@aiquants/daily-report` are documented here.
59
166
  - **DetailList の行は自分のはみ出しを切る中身を持たない**: 行の中の画面に出さない文 (見出しと状態・種別のバッジの展開した名前) は `sr-only` (`overflow: hidden` と `clip`) でなく、切らない透明な 1 px の文 (`VISUALLY_HIDDEN_CLASS_NAME`) で描く。Chromium は窓をずらすスクロールの 1 段で、はみ出しを切る中身を持つ行をすべて描き直す (描く範囲の 11 行から 2 行へ)。
60
167
  - **ID でまとめた詳細 (`reports`) は、閲覧者 1 人にクライアントが同時に送る数の読み取りを通す**: 受け入れの同時数は、検索の `concurrency` と `DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT` (2) の大きい方の `REPORTS_CONCURRENCY_PER_SEARCH_CONCURRENCY` (2) 倍で、閲覧者の取り分は 2 を下回らない (`concurrency` が 1 ならプロセスで 4)。以前は `concurrency` が 1 のとき取り分が 1 で、クライアントの 2 本目の読み取りが 429 で断られた。
61
168
  - **検索の印とローマ字の読み (上の Added) の直し**:
62
- - 読みの下限は文字の塊で数える (`readingLetterClusterCountOf`): 数字・記号・先頭のダッシュは数えないので、仮名 1〜2 字の読み (`ha100` → ハ100・`no12` → ノ12・`-site` → ーシテ) を足さず、「単価は100円」「10月の12日」「コピーして」のような言い回しに当たらない。
169
+ - 読みの下限は文字の塊で数える (`readingLetterClusterCountOf`): 数字・記号・先頭のダッシュは数えないので、仮名 1〜2 字の読み (`ha100` → ハ100・`no12` → ノ12・`-site` → ーシテ) を足さず、「単価は100円」「10月の12日」「コピーして」のような言い回しに当たらない。ただし 0.36.0 は長音の連なりの長音を 1 つずつ数えたので、仮名 1 字に長音を 2 つ以上続けた読み (`ha--` → ハーー) は足し、「それは――」(索引では ソレハーー) のような言い回しに当たった (連なりを 1 つと数えるのは次の版から)。
63
170
  - 読むかは打った語で決める: 英字の文字を打った語だけが読みを持ち、英字の文字を打たずに NFKC で英字になるローマ数字や № を含む語 (`Ⅱです`・`№12`・`第Ⅲ期`) は 0.35.0 と同じ計画になる。
64
171
  - 詳細の読み取りが同じ日報を 2 行で返しても (自分の日報が 2 つの下書きのラベルを持つとき)、`search-marks` の答えは日報ごとに 1 つで、クライアントの読み取りが束ごと拒んで窓の印を落とすことは無い。
65
172
  - 閲覧者 1 人は、クライアントが同時に送る数の `search-marks` の要求を持てる (`concurrency` が 1 でも 2 本目が 429 にならない)。