@aiquants/daily-report 0.34.0 → 0.36.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,152 @@
2
2
 
3
3
  All notable changes to `@aiquants/daily-report` are documented here.
4
4
 
5
+ ## 0.36.0 (2026-10-08)
6
+
7
+ ### Added
8
+
9
+ - **検索の答えのハイライト**: 答えを見せている間、List のカード (本文の先頭の行と作成者)・側面ペイン・DetailList の行は、サーバーが当たりと判定した所に `<mark data-daily-report-search-mark>` を付ける。打った語の形で当たった所 (正規化でだけ当たった所を含む: `デンプン` は でんぷん に付く) は `word` で実線の下線、ローマ字の読みでだけ当たった所は `reading` で点線の下線。
10
+ 側面ペインと DetailList は検索する欄の描いている文字すべて (件名・本文・作成者と更新者と社員の名前・区分と作成区分・顧客・面談者の名前と所属・添付のファイル名・コメントの作者と本文) に付ける。2 つ以上のキーワードの OR では、日報が当たったキーワードの語だけに付ける。
11
+ 印を付けないもの: ラベル名と外部の文字 (kintone の明細の表 (`dbo_kintone.TD日報明細`) だけで当たった日報には印が出ない)・一覧と側面ペインが除くタグ・一覧の 4 行目以降と切った後ろ・省いたファイル名の部分・編集のフォーム・作者不明の代わりの名・値でない文字。そのまま値を差し込まない `interviewerWithAffiliation` のひな形は札を素で描く。
12
+ 位置はサーバーが索引と同じ処理から求めるので、印は検索が当たらない所に付かず、ブラウザーは正規化しない (`@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
+ - **一致の印**: 側面ペインの作成者の行と DetailList の行の見出しに、contact map の点数と同じ書体 (11 px の等幅、slate-600 / 暗色 slate-300) の控えめな印を出す。2 つ以上のキーワードの OR では当たったキーワードの数 (`一致 2/3`)、日報が当たったのにそのビューが描く印が 1 つも無いとき (ラベルや明細だけで当たったなど) は `表示外で一致` (en `Hidden match`)。答えの最も長い文字の幅を前もって取っておくので、印が届いても見出しは流れ直さない。印は `aria-hidden` で `title` を持ち、画面に出さない文を添える。
14
+ - **ローマ字の読み**: 英字を打った検索語は、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` も読みを持たない。
16
+ 漢字には届かず (`nouki` は 納期 に当たらない)、長音は畳まず、同音の綴りは片側だけ (`numazu` は ぬまづ に当たらないので `numadu` か仮名で打つ)。仮名の語でローマ字の本文は探さない。英字を打っていない語 (NFKC で英字になるローマ数字や № の `Ⅱです`・`№12` を含む) の計画は変わらず、正規化の版も変わらないので、索引の作り直しは要らない。
17
+ - **検索の答えの `words`**: `search` の答えは `{ ids, businessDates, sourceTypes, words }` で、`words` は計画の語 (キーワード → 語 → 候補 `{ text, tier }`)。`q` と `combine` だけで決まり、閲覧者のことは明かさない。パッケージのクライアントは語を厳しく読み、崩れていれば答えを丸ごと拒む。
18
+ - **検索の印の要求**: `GET {apiBasePath}/search-marks?q=&combine=&ids=` (ID は 100 件まで) が、頼んだ日報のうち閲覧者が読めるものの欄ごとの印の範囲 (生の値の UTF-16 の位置と tier、値の指紋) と、2 つ以上のキーワードの OR では当たったキーワードの組を返す (`{ wordsDigest, reports }`)。設定が無ければ 404。
19
+ 受け入れは検索と同じ工場から作る自分のインスタンス (同時数は検索の `concurrency` とクライアントが同時に送る数 `DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT` = 2 の大きい方の `SEARCH_MARKS_CONCURRENCY_PER_SEARCH_CONCURRENCY` = 2 倍、回数は `rateLimitPerMinute` の `SEARCH_MARKS_RATE_PER_SEARCH_RATE` = 10 倍、拒否の行は `route=search-marks`)。サービスの `markDailyReportSearchHits`、エンジンの口の `matchedSets`。
20
+ パッケージのクライアントは描く行の ID をまとめて (1 回 100 件・同時に 2 本まで) 頼み、答えは送った順に当て、行は自分の印が届いたときだけ描き直す。
21
+ - **添付のファイル名の印**: 格子の外の CSS の省略の形では語幹と拡張子の 2 つの要素がそれぞれ自分の部分に、収めた形では収めた文字に印を付ける (省いた中ほどと `…` には付けない)。分解された名前の印は合成した文字の全体を覆う。`title`・データ属性・読み上げ用の名前は素のまま。
22
+ - **`DailyReportUIComment.isAuthorUnknown`**: `mergeComments` は作者名の無いコメント (代わりの名を見せるもの) に `true` を付ける。検索はその代わりの名に印を付けない。
23
+ - **ラベル 4 個** (固有キーは 98 から 101。下の Breaking の `deleteFailed` を除いた数): `searchMatchOutsideView`・`searchMatchOutsideViewDescription` と、整形関数の `searchMatchedKeywords(matched, total)`・`searchMatchedKeywordsDescription(matched, total)`。
24
+ - **SQL の結果のキャッシュの上限** `cacheMaxBuckets` (`createDailyReportServer`、既定 4,096): 書き込みは書いた順の最も新しいものとして入れ直し、上限を超えた分は最も古く書いたバケットから捨てる。期限切れの掃除は単調な時計で既定の TTL ごとに多くとも 1 回なので、書き込みの費用は表の大きさによらない。
25
+ - **公開の名前**: 共有エントリの `DailyReportSearchFieldMarks`・`DailyReportSearchReportMarks`・`DailyReportSearchMarksAnswer` (`search-marks` の答え)、server の `DailyReportSearchAlternative`・`DailyReportSearchAlternativeTier`・`DailyReportSqlResultCacheMissAdmission`、client の `DailyReportLedgerView`。
26
+
27
+ ### Changed
28
+
29
+ - **検索の答えを見せている間の一覧の組み立てを軽くした**: ストリームの一覧が変わるたびに全件の表を作る代わりに、答えの日報の ID の集合 (答えごとに 1 回作る) でストリームを 1 回なめ、答えが持つ日報だけを拾う。
30
+ 一覧の中身と順は変わらない。合成のデータでの目安 (22.8 万件の一覧、一覧の変化 1 回): 答え 10 件で 35 → 3.4 ms、1,000 件で 27 → 5.8 ms、5 万件で 33 → 10 ms、18.9 万件で 43 → 24 ms。
31
+ - **`DailyReportSearchWord.alternatives` は `{ text, tier }` の並び** (公開の型の変更): tier 0 は打った語の形、tier 1 はローマ字の読みの形。SQL と照合の基準と答えの `words` と印がこの形を読む。計画の型 (`DailyReportSearchPlan`) を読むホストは、候補の文字を `alternative.text` で読む。
32
+ - **検索の書き方の説明** (`labels.searchSyntaxHelp`) は、英字の語がローマ字の読みでも仮名を探すことと、読みで当たった所を点線の下線で示すことを 1 文で足した (en と ja)。
33
+ - **検索窓の行は格子に乗る**: 行は操作列と同じ 32 px の格子のブロック 1 つで、24 px の操作部品を下端に置く (操作列から G、ビューの最初の面の G 上)。始まりと終わりの寄せは帯が持ち、操作列と検索の行が共有するので、入力欄の終わりの縁はカードの終わりの縁に揃う。帯はビューより上に重なり、検索の操作部品のフォーカスの輪がビューの余白の上に描かれる。
34
+ 消去のボタンは入力欄の箱の中に置き、その場所を入力欄がいつも空けておくので、文字を打っても入力欄の幅は変わらない。状態の行は文言を省かずに折り返す (幅は行の半分まで、空なら場所を取らない)。以前は 32 px の箱の中央に 24 px の操作部品を置いたので見える隙間が 4・12・8 px になり、入力欄の終わりの縁はどこにも揃わず、最初の打鍵で消去のボタンが入力欄を 32 px 縮め、8rem の状態の列は日本語の 5 つの文言のうち 3 つを切った。
35
+ - **ログのチャンネルは閉じた一覧**: どのロガーも `createChannelLogger(hostLogger, defaultLevel, channel)` で作り、`channel` は閉じた一覧 `DAILY_REPORT_LOG_CHANNELS` の名前でしか名指せない (接頭辞 `[DailyReport<チャンネル>]` を綴るのはロガーのモジュールだけ)。書き間違いは型の誤りになり、どの一覧も知らないチャンネルを黙って作らない。
36
+ クライアントの 3 つのロガーも同じ接頭辞を持つ (`[DailyReportActionProvider]`・`[DailyReportDetailLoader]`・`[DailyReportSseConnection]`。以前は角括弧の無いモジュールの名前)。キャッシュの取りこぼしの受け入れの拒否は新しいチャンネル `[DailyReportReads]` (warn) に書く。
37
+ - **`cacheDefaultTtlMs` は 1 以上の整数**: `createDailyReportServer` は `cacheDefaultTtlMs` と `cacheMaxBuckets` を `requireIntegerSetting` で読み、ほかの値は設定の名前を名乗る `RangeError` で止まる。
38
+ - **開発者向け**:
39
+ - 網羅率のつめ車 (`pnpm run check:coverage`) は、測った値の切り捨てより低い下限 (余り) も、どちらの指標でも落とす (`<file>: <metric> floor <f> below measured <m>; run pnpm run coverage:ratchet`)。spec が足した網羅率は下限としてすぐに固定される。
40
+ - モノレポの根の解決 (`scripts/lib/repository-root.mjs`) は物理パスで読み、ファイルシステムの根の手前で止まり、起点にファイル・ディレクトリ・`file:` の URL を受け、空白の起点を拒む。リポジトリの契約の表 (`.config/scripts/lib/repository-root-contract.mjs`) を `src/repository-root.spec.ts` が走らせる。
41
+ - プロファイル用の source map: `DAILY_REPORT_PROFILE_SOURCE_MAPS=1` のビルドは、ソースの本文を持たない source map をローカルのプロファイルのために書く (公開のビルドは map を書かず、ほかの値はビルドを止める)。
42
+ - `src/docs-examples.spec.ts` のテストの名前は名指す例と文書の名前を切らずに持つ (`it.each` の書式は値を 40 字で切り、2 つのテストが同じ名前になっていた)。
43
+ - 文字の欄、検索の印、一致の印の色のコントラストと、行の導きの写しの数、検索の 3 つの設定の下の描画の回数、公開のプロバイダーのコンテキストの読み方を、それぞれの spec が確かめる。
44
+
45
+ ### Fixed
46
+
47
+ - **キャッシュの取りこぼしも閲覧者ごとに縛る**: `business-date`・`report`・ids のストリームの読み取りがキャッシュを取りこぼして取得を始めるとき、閲覧者の取りこぼしの枠を 1 つ取る (`createCacheMissGate`: 閲覧者ごとに 8、ワーカーごとに 64。待ち行列は閲覧者ごとに 256・ワーカーごとに 1,024 で、待つのは 60 秒まで)。命中と同じキーの取得への相乗りは枠を取らない。待ち行列か待ちの上限を超えた読み取りは問い合わせを始めず、`{"error":{"message":"Too many reads"}}` と `Retry-After: 5` の 503 で答える (ids のストリームは、再開の GET がその 503、新規の GET は本体を定型のエラー行で閉じる)。既に中断した要求は枠を取らず取得も始めない。拒否は `[DailyReportReads]` に閲覧者の窓ごとに 2 行まで。
48
+ 以前はキャッシュ (`cache`) が同じ読み取りしか分け合わず、0001 年から 9999 年のどの営業日も、どの ID も別のキーなので、キーを変えるだけで 1 人の閲覧者が際限なく問い合わせを走らせられ、結果は上限なしに TTL の間残った (32,000 個のキーで 32,000 回の問い合わせと 11.7 秒のメインスレッド)。
49
+ - **ビューの Tab の止まり先は 1 つ**: ビューを入れるタブの中身は Tab の止まり先ではなくなった (`TabsContent` の必須の `tabStop`。中身が自分の止まり先を持たないときだけ止まり先)。ビューの前の最後の操作部品からの Tab 1 回で行のカーソルへ届き、行からの `Ctrl+Home` はその操作部品 (検索窓があれば検索の行の最後の操作部品) へ出る。以前はどのビューのタブの中身も 2 つ目の止まり先で、`Ctrl+Home` はそこ (キーが何もしない所) へ出た。
50
+ - **コメントの投稿と削除の失敗は画面でも巻き戻る**: 楽観的な状態の台帳を変えるのはプロバイダーのただ 1 つの書き手で、変更と同じ呼び出しで台帳の版を進める。投稿に失敗したコメントは一覧から消え、削除に失敗したコメントは戻る。以前は失敗の処理が台帳だけを書いて版を進めなかったので、知らせは失敗を伝えるのに、画面は投稿したまま・消したままに見えた。
51
+ - **側面ペインの削除の失敗は 1 回だけ知らせる**: 削除のボタンは拒否を受けて戻るだけで、誤りの知らせが失敗を伝える。以前は知らせに加えて別の文言のネイティブの `alert()` を出し、閉じるまでストリーム・SSE・時計のスクリプトを止めた。
52
+ - **文字の欄の境界と置き文字は 3:1 と 4.5:1 を満たす**: コメントの入力欄・編集フォームの `Input` と `Textarea`・検索の入力欄と AND/OR の切り替えの OR の面は、1 つのトークン (`INPUT_FIELD_CLASS_NAME`) から slate-500 の枠線と slate-500 (暗色 slate-400) の置き文字を取る (明: 白に 4.76、slate-50 に 4.55。暗: slate-900 に 3.74、slate-800 に 3.07)。ホストの `--input` などの色のトークンは使わない。以前の枠線は 1.2〜2.7:1 で WCAG 1.4.11 に届かなかった。
53
+ - **AND/OR の切り替えはフォーカスを保つ**: 押してもフォーカスは切り替えに残り、名前が新しい状態を伝える (WCAG 3.2.2)。以前はフォーカスを入力欄へ移したので、2 度目の Space は空白を打ち、読み上げは新しい状態を伝えなかった。
54
+ - **自動既読のスイッチは説明を持つ**: スイッチは見える文言を名前に、自動既読が表示に依ること (`labels.autoReadTitle`) を説明 (`aria-describedby`) とツールチップに持つ。以前の説明は包みの `title` だけで、名前にも説明にもならず、キーボードのフォーカスでは出なかった。
55
+ - **API・action・SSE の経路のどの応答も同じセキュリティヘッダーを持つ**: `X-Content-Type-Options: nosniff`・`X-Frame-Options: DENY`・`Content-Security-Policy: default-src 'none'` を、ステータスによらず (JSON の答えと拒否・action の答え・要求の隔離の拒否・SSE のストリームと誤り・ids の NDJSON) 1 か所の組 (`RESOURCE_RESPONSE_SECURITY_HEADERS`) から付ける。以前は action の答え、SSE の経路の 404・503・500、隔離の拒否がそれぞれ組の一部を欠いた。
56
+ - **Chrome 121〜124 でもビューの列は中央に残る**: 列の原点の `round()` の宣言は `@supports` の下に置く。値は `var()` を含むので、`round()` を持たない描画エンジン (下限の内の Chrome 121〜124) は宣言を計算値の段階で無効にして `margin-left` を 0 にし、列を左端に寄せていた。
57
+ - **行の導きは描画のやり直しで写しを重ねない**: 前の行と読むもの (元の版と変化の鎖・落ち着き・削除の印) の組 1 つの導きは、React が描画を何回やり直しても 1 回だけ (`deriveReportRowsOnce`)。README の「publish 1 回に参照の写し 1 回」は、押し続けたキーが publish を運ぶトランジションの描画を捨てて根からやり直させる間も成り立つ。以前は 1 回の publish の導きと写しがやり直しの数だけ走った (本物の並行の描画で 21〜25 回、80,000 行で写し 1 回が約 320 KB)。
58
+ - **取り下げた削除の印が隠していた行は戻る**: 削除の印がある間のストリームの値は隠す行として覚え、削除が断られて印を取り下げると、その行をストリームの値で正準の場所へすぐに戻す (`withdrawRemovalMark`)。以前は印の間に同じ値の中継が届くと行が戻らず、落ち着いた次の導きで持つ行として手放された (乱数の操作列の 128 件のうち 11 件で、一覧全体から導いた行と食い違った)。
59
+ - **DetailList の行は自分のはみ出しを切る中身を持たない**: 行の中の画面に出さない文 (見出しと状態・種別のバッジの展開した名前) は `sr-only` (`overflow: hidden` と `clip`) でなく、切らない透明な 1 px の文 (`VISUALLY_HIDDEN_CLASS_NAME`) で描く。Chromium は窓をずらすスクロールの 1 段で、はみ出しを切る中身を持つ行をすべて描き直す (描く範囲の 11 行から 2 行へ)。
60
+ - **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
+ - **検索の印とローマ字の読み (上の Added) の直し**:
62
+ - 読みの下限は文字の塊で数える (`readingLetterClusterCountOf`): 数字・記号・先頭のダッシュは数えないので、仮名 1〜2 字の読み (`ha100` → ハ100・`no12` → ノ12・`-site` → ーシテ) を足さず、「単価は100円」「10月の12日」「コピーして」のような言い回しに当たらない。
63
+ - 読むかは打った語で決める: 英字の文字を打った語だけが読みを持ち、英字の文字を打たずに NFKC で英字になるローマ数字や № を含む語 (`Ⅱです`・`№12`・`第Ⅲ期`) は 0.35.0 と同じ計画になる。
64
+ - 詳細の読み取りが同じ日報を 2 行で返しても (自分の日報が 2 つの下書きのラベルを持つとき)、`search-marks` の答えは日報ごとに 1 つで、クライアントの読み取りが束ごと拒んで窓の印を落とすことは無い。
65
+ - 閲覧者 1 人は、クライアントが同時に送る数の `search-marks` の要求を持てる (`concurrency` が 1 でも 2 本目が 429 にならない)。
66
+ - 見せる答えの検索が替わると、載ったままの行は新しい答えの印が届くまで素の文字を描き、前の答えの印と当たったキーワードの数を描かない。
67
+ - 印の答えは送った順に当てる: 前の epoch のまとまりや編集の前に送ったまとまりの答えが遅れて届いても、後に送った要求が置いた新しい印を上書きしない。
68
+
69
+ ### Breaking
70
+
71
+ - **`DailyReportConfigProvider` は必須**: 設定のコンテキストは既定値を持たず、プロバイダーの外の `useDailyReportConfig` (と、設定を読むパッケージのどの部品) は描画で `[daily-report] useDailyReportConfig must be used within a DailyReportConfigProvider` を投げる。以前は en の既定の設定と検索なしで黙って描いた。
72
+ Migration: `DailyReportPage` を使うホストは変更が要らない。ビュー・`DailyReportResolvedContent`・アクションのプロバイダーを自分で組むホストは `DailyReportConfigProvider` で包む。
73
+ - **ラベル `deleteFailed` を外した**: 側面ペインの削除の失敗は誤りの知らせだけが伝える (上の Fixed)。
74
+ Migration: `labels.deleteFailed` を上書きしていたホストは、その鍵を外す (残すと知らないキーとして `RangeError`)。文言は `operationFailed('delete')` が持つ。
75
+ - **server の `generateETag` と `jsonResponseWithETag` を外した**: どちらもパッケージの内部の部品で、入口からは出さない。
76
+ Migration: 使っていたホストは自分の実装を持つ (ETag は本文の字面のハッシュ、`If-None-Match` は弱い比較で読む)。
77
+ - **`SqlResultCache` は `maxBuckets` を求める**: コンストラクターの設定は `{ defaultTtlMs, maxBuckets }` で、どちらも 1 以上の整数。
78
+ Migration: `SqlResultCache` を自分で作るホストは `maxBuckets` を渡す (`createDailyReportServer` を使うホストは `cacheMaxBuckets` で変えられ、省けば 4,096)。
79
+ - **楽観的な状態の台帳は読むだけ** (`DailyReportActionLedgers`): どの台帳も `DailyReportLedgerView` (凍結した `{ current }` と読むだけの Map) で、中身を変えるのはプロバイダーの書き手だけ。以前は書き換えられる `MutableRefObject` だった。
80
+ Migration: 台帳を読むだけのホストは変更が要らない。書き込んでいたホストはアクションのコンテキストの関数 (`toggleStar`・`addComment` など) を使う。
81
+
82
+ ## 0.35.0 (2026-10-08)
83
+
84
+ 任意の文字の検索。サーバーの設定のブロック `search` とクライアントの `config.search` を渡したホストだけで働き、渡さないホストの振る舞いは変わらない (公開・削除・コメントの経路がトランザクションに入ることと、`DailyReportDb` の `execute` を除く)。仕様は `docs/specs/2026.10.07 [AI] 03-search-specification.md`、README の **Text search** が今の振る舞いを述べる。
85
+
86
+ あわせて 0.34.0 の続き: フォーカスを見える要素へだけ渡すことと状態の面のフォーカスの引き継ぎ、誤りの知らせの読み上げ (常設の警告の領域と繰り返しの数)、ストリームの読み上げの領域、コメントの削除の 404、`DailyReportErrorProvider` を必須にすること、デスクトップのビューの上端と内容の列の寄せの格子、側面ペインの読む面の取っておく高さ、DetailList の日時の見え方、行の導きの写し、サーバーのログのチャンネル、JSON の答えの弱い比較の 304 と 1 回の直列化、エンドポイントの表が宣言する上限、要求の中断で返るストリームの枠、公開面の閉包。どれも README の該当の節が今の振る舞いを述べる。
87
+
88
+ ### Fixed
89
+
90
+ - **フォーカスは見える要素へだけ渡す**: 自分を外すか空になる操作部品 (2 つの再試行のボタン・誤りの知らせを閉じる操作・見出しの札の入れ物・本体へ入れ替わる状態の面) は、1 つの規則 (`focusVisibleSuccessor`) でフォーカスを渡し、受け取るのは文書の中にあってフォーカスでき、面積のある箱を持ち、`checkVisibility` (`visibility` と不透明度を含む) を通る要素だけで、渡った先を確かめる。
91
+ 状態の面と札の入れ物は、フォーカスを持つ間は操作部品のフォーカスの輪を描く。状態の面がフォーカスを持ったまま本体へ入れ替わると、ページの引き継ぎ (`FocusHandOverContext`) を、ビューが行をマウントした最初の確定で受け取って Tab の止まり先の行 (空の一覧なら選ばれたビューのタブ) へ渡し、利用者の切り替えで作り直した状態の画面は新しい面へ渡す。札の入れ物はフォーカスを持ったまま空になると、ページの後継 (ビューの行、無ければ選ばれたビューのタブ) へ渡す。
92
+ 以前は状態の面が輪を消したまま (`outline-none`) フォーカスを持ち、読み込みの失敗からキーで再試行するとフォーカスは毎回画面に出さない読み上げの領域 (1 × 1 の箱) へ渡り (行カーソルへ渡す枝は、ビューがその確定でまだ行を描かないので走らなかった)、札の入れ物はストリームが終わると 0 × 0 の箱でフォーカスを持った (WCAG 2.4.7)。
93
+ - **誤りの知らせはフォーカスを body へ落とさない**: 知らせを閉じるときの返し先は、来た元・ビューの行カーソル・知らせを置いたダイアログ・選ばれたビューのタブ (行の無い一覧)・状態の面 (ビューがまだ無い) の順の、見える最初の要素。以前は来た元も行カーソルもダイアログも無いと (来た元の消えた空の一覧など) body へ落ち、状態の面がフォーカスを持つ間の利用者の切り替えもフォーカスを body へ落とした。
94
+ - **コメントの削除の 404 も済んだものとみなす**: サーバーにもう無いコメントの削除 (404) は、巻き戻しも誤りの知らせもせず、コメントを外したままにする (日報の削除と同じ道)。404 を済んだと数えるのは対象を消す操作 (`delete`・`deleteComment`) だけで、その処理は 404 の意味 (`onGone`) を持たなければ型検査を通らず、ほかの操作の処理は持てない (`ActionCallbacks`)。以前はコメントを戻して誤りを知らせ、やり直しても毎回 404 になった。
95
+ - **同じ失敗の繰り返しも読み上げ、知らせの移動では読み上げない**: 読み上げは、失敗より前から空のまま置いた常設の `role="alert"` の領域 (`[data-daily-report-notice-alert]`。プロバイダー自身のものと、登録された置き場ごとのもの) が受け持ち、失敗はそのとき前面にある置き場の領域へ、回の番号を鍵にした新しい文字として 1 回だけ書く。見える帯は文言を名前に持つ `role="group"` で、同じ文言の失敗が続くと回数を見せる (`labels.noticeRepeated`)。
96
+ 以前は同じ文言の失敗が DOM を何も変えず (見えも読み上げもしない)、ポータルの置き場と流れの中の置き場が React の別の位置だったので、オーバーレイを開閉するたびに古い知らせを新しい `role="alert"` として差し込み直した (WCAG 4.1.3、2.2.4)。
97
+ - **ストリームの読み上げの領域は移り変わりだけを知らせ、フォーカスを受けない**: ページの常設の状態の領域は空のままマウントし、マウントの後の変化 (再試行の後の読み込み中・再接続中・失敗) だけを知らせる。以前はマウントの時点で読み込み中の文字を持ったので最初の読み込みは読み上げられず、読む画面では見える状態の面と同じ文字を 2 度読ませた。
98
+ - **ストリームの枠は要求の中断で返る**: 閲覧者のストリームの枠は、受け入れ (`createStreamSlots().admit`) が本体のシグナルに結び付け、受け入れた後のどの中断でもその時に 1 回だけ返る。以前の ids のストリームは中断の購読を応答を作るときに足したので、再開の GET が全量のクエリを待つ間に中断されると、枠はクエリの決着まで (中断の後に本体を引かないアダプターでは返らずに) 残り、そうした要求が 32 本重なると、その閲覧者の次のストリームは 503 になった。
99
+ - **JSON の答えの 304 は弱い比較**: `jsonResponseWithETag` は `If-None-Match` の並びの各タグを `W/` を外して比べ (`*` は一致にしない)、添付の縮小画像と同じ 1 つの読み手 (`ifNoneMatchIncludes`) を使う。以前は字面の一致で比べたので、圧縮する中継 (gzip の nginx は転送する強い ETag を弱める) を通る 1 KiB 以上の答えは、再検証で 1 度も 304 にならなかった。
100
+ - **デスクトップのビューの上端と面の縁も格子に乗る**: 内容の列の上と左右の寄せと原点は格子のブロックの刻み q (1/4 の倍数で 4 px、1/8 の奇数倍で 8 px) で、ビューの上の操作列 (設定したときは検索の行も) は高さを q へ切り上げる帯 (`VIEW_TOOLBAR_BAND_CLASS_NAME`) に入る。
101
+ 以前は列の上の寄せが 4 px で、ホストの 32 px のヘッダーの下ではビューの上端が 68 px (8 で割って 4 余る) だったので、1/8 の奇数倍の画素密度 (1.125・1.375・2.625 など) ではビューの上端が装置の画素の半分ずれ、どの行の縁・選択の輪・アウトラインもそれを受け継いだ (1280 × 800・1.125 で操作列・ビュー・最初の面の上端が 40.5・76.5・85.5 装置画素。今は 45・81・90)。412 px 幅・2.625 の面の左の縁も 31.5 から 42 装置画素になった。
102
+ - **側面ペインの読む面は読み込みの前後で高さを変えない**: 読み込み中の読む面は読む行 3 本分の高さを取っておき (`min-h-24`: 寄せ 12 × 2 + 24 px の行 × 3 = 96 px)、骨組みは 24 px の行の箱 3 本で、16 px の棒をそれが代わる文字の行の上に置く。以前の取っておく高さは 88 px、骨組みは 64 px (読む行 2.67 本分) で、3 行の日報は読み込むと 88 から 96 px へ伸び、骨組みの棒は文字の行より 4 px 上にあった。
103
+ - **行の導きの写しはページのライブラリーに左右されない**: ids のストリームの publish から行を導くとき、変化の場所は前の行の値の読み取り (末尾に続く Δ 行なら Δ + 1 行、ほかの変化は 1 行ごとに二分探索) で決め、新しい行の配列は前もって確保した配列への 1 回の写しで作る (`concat`・`filter`・`slice` を使わない)。利用者の操作 (持つ・手放す・外す) も持つ行の表と二分探索で行の場所を決める。
104
+ 以前の追加は `rows.concat(additions)` で、`concat` の速い経路はページ全体の保護 (`Symbol.isConcatSpreadable`) に依るので、ページのどこかのライブラリーが 1 度書くと (apache-arrow はプロトタイプに書く) publish ごとに全行を 1 件ずつたどった。Chromium 148 の 228,222 行で、保護が壊れた `concat` は中央値 51.6 ms、今の写しは 0.65 ms。値の置き換え・外す・手放すも行を写して絞り込み、日報の作成 1 回の SSE で約 13 ms、削除 1 回で 9〜10 ms を使った。
105
+
106
+ ### Changed
107
+
108
+ - **`DailyReportDb` は `execute` を持つ** (公開の型の変更): 索引と検索の SQL を流すため。drizzle の `db` とトランザクションは持っているので、`db` をそのまま渡すホストは変更が要らない。手で書いた代役を渡すホストは `execute(query)` を足す。
109
+ - **公開・削除・コメントの追加と削除はトランザクションの中で行う**: 索引を同じトランザクションで書くため。検索の設定が無くても同じで、変わるのはデッドロックの犠牲になったときに丸ごとやり直されることだけ。
110
+ - **DetailList の日時は `YYYY-MM-DD HH:mm` で見せる** (既定の見た目): カードの作成日時のような ISO の日時は、日付・空白 1 つ・時と分で見せ (秒は丸めずに切る: `2026-12-31T23:59:59` は `2026-12-31 23:59`)、`<time datetime>` には ISO の値をそのまま残す。日付だけの値と日付の無い `-` はそのまま。規則は `DateText` の 1 か所で、見出しの札とメタデータの列 (と、それを使うどの呼び出し元) も同じ形で見せる。以前は `2025-10-17T14:20:00` のように `T` と、いつも `:00` の秒を見せた。
111
+ - **サーバーのログの行はチャンネルを名乗る**: どの行も、書いた部分 (チャンネル) の接頭辞を持つ (`[DailyReportAPI]`・`[DailyReportSSE]`・`[DailyReportStreams]` など。ホストのロガーへ渡す行も同じ。`createChannelLogger`)。ホストのロガーはどのチャンネルのどの水準の行も受け取り、水準を自分で選ぶ。ホストのロガーが無ければ、チャンネルごとの既定の水準から console へ書く。
112
+ 迂回のバケットの 429 は自分のチャンネル `[DailyReportRefresh]` (以前は `[DailyReportAPI]`)、`createDailyReportServer` の redis の警告は `[DailyReportServer]` (以前は `[DailyReport]`)、共有の SSE リーダーは `[DailyReportSseReader]` (以前は `[SSE Reader]`)、`transformJsonArray` は `[DailyReportExternalSource]` で書く。500 は文面と例外のオブジェクトを 1 回の呼び出しで書く 1 行 (以前は `Stack:` の 2 行目を別に書き、同時の要求の行と混ざった)。
113
+ - **JSON の答えは 1 回だけ直列化する**: `jsonResponseWithETag` は本文の字面から ETag を作る (`etagOfText`)。以前は ETag のためと本文のために 2 回直列化した (622,349 バイトの答えで組み立て 5.41 ms のうち 0.78 ms)。
114
+ - **エンドポイントの表は上限を宣言する**: API の経路の表 (`API_ENDPOINT_TABLE`。以前の `API_ENDPOINT_BODY_KINDS`) の行は、本体の種類と、閲覧者ごとのデータベースの仕事を縛る上限 (`admission`: `cache`・`refresh-bucket`・`stream-slots`・`search-limiter`。型で 1 つ以上) を持つ。上限を名乗らないエンドポイントは表に足せず、表を回す 1 つの spec が宣言した上限をハンドラーごとに確かめる。
115
+ - **開発者向け**: 詳細の表示の欄の解決 (`resolveHubDisplayFields`) と投稿コメントの見え方 (`isSearchCommentVisible`) を、詳細と索引と照合の基準が共有する。営業日の一覧の取得は、ID でまとめた詳細と同じ 1 つの読み取り (`fetchVisibleHubDetails`) を使う。エンドポイントの名前は `shared/api-endpoints.ts` の表に `search` と `reports` を足した。
116
+ - 整数の設定と内部の上限はどれも 1 つの検証 (`requireIntegerSetting`) で読む: クライアントの読み込みの枠 (`createLoadSlotLimiter`)・進捗の記憶 (`createThumbnailProgressStore`)・検索の設定・件名の列の長さも。2^53 以上の値は拒む (以前の手書きの `Number.isInteger` は通した)。`src/integer-bounds.spec.ts` が手書きの整数の検証を例外の一覧なしに落とす。
117
+ - docstring の検査は、関数に加えて契約を述べる宣言の JSDoc (プロパティとメソッドのシグネチャ・列挙と項目・型の別名・インターフェース・クラス・関数でない公開の定数) にも英語と日本語の両方を求める (1 行の形 `English. 日本語。` も受ける)。本番のコードは違反 0。
118
+ - 検査のスクリプトと spec は、モノレポの根を 1 つの解決 (`scripts/lib/repository-root.mjs`: 自分のファイルから上の `pnpm-workspace.yaml` か `.git`、検証した `PROJECT_ROOT`) で見つけ、見つからなければパスを名乗って落ちる。決まった段数の `../../../.config` は使わない。
119
+ - 条件付き要求の読み手 `ifNoneMatchIncludes` は `src/server/attachment-delivery/http.ts` から `src/server/response.ts` へ移った (JSON の答えと添付の縮小画像が共有する)。
120
+ - `src/docs-examples.spec.ts` は、仕様書が引用する名前を探すとき、分割代入で受ける名前 (動的な読み込みの答えを含む) も数える。
121
+
122
+ ### Added
123
+
124
+ - **文字の検索**: 検索窓 (操作列の下の 2 段目。AND/OR の切り替え・入力欄・消去のボタン・状態の行) で、空白で区切ったキーワード (二重引用符の組はそのすべての語を含む 1 つのキーワード) を探す。語は、閲覧者が読める 1 つの欄か 1 つのコメントの、fuzzy-search の既定の正規化をかけた文字の部分文字列として当たる。認可は詳細と同じ (削除した日報・ほかの利用者の下書き・見えない区分を出さず、コメントは区分の範囲か自分の投稿だけ)。
125
+ - **索引**: 専用のスキーマの 2 つの派生の表 (`DailyReportSearchDocument`・`DailyReportSearchComment`。`defineDailyReportSearchSchema`)。作成・更新・公開・削除・コメントの追加と削除の 6 つの経路は、元の表の書き込みと同じトランザクションの中で日報の行に鍵を取ってから索引を書き直し、書けなければ保存も失敗する。パッケージの外の書き込みの後は `service.reconcileSearchIndex` (区切りごとのトランザクション) で突き合わせる。
126
+ - **要求**: `GET {apiBasePath}/search?q=&combine=intersection|union` (列ごとの答え・ETag・400 の理由・閲覧者ごとの回数とプロセスの同時数の 429) と `GET {apiBasePath}/reports?ids=` (ID でまとめた詳細、100 件まで)。設定が無ければどちらも 404。
127
+ - **画面**: 答えを見せている間は答えの順で一覧を置き換え、見出しは当たりの件数、行は詳細を ID でまとめて読む。保存・SSE の日報とコメントの知らせ・落ち着いた ID の列の変化で、約 1 秒後に静かに送り直す (`If-None-Match`)。選択は答えに残れば保ち、消えたら一番近い日報へ移り、解除で検索の前へ戻る。検索中の作成は検索を解除してから作る。
128
+ - **公開の名前**: server エントリの `defineDailyReportSearchSchema` と型 `DailyReportSearchConfig` / `DailyReportSearchSourceTextReader` / `DailyReportSearchTables` / `DailyReportSearchReconcileOptions` / `DailyReportSearchReconcileProgress` / `DailyReportSearchReconcileSummary`。サービスの `searchLimits` / `searchDailyReports` / `getDailyReportDetailsByIdsByExternalId` / `reconcileSearchIndex`。プロバイダーの状態の `searchInvalidation` / `isReportRemoved` / `removedVersion`。
129
+ - **ラベル 12 個** (固有キーは 85 から 97): `searchInputLabel`・`searchPlaceholder`・`searchCombineAnd`・`searchCombineOr`・`searchCombineAndTitle`・`searchCombineOrTitle`・`searchClear`・`searching`・`searchNoHits`・`searchSyntaxHelp` と、整形関数の `searchHitCount(count)`・`searchFailed(failure)`。
130
+ - **公開面の閉包**: 公開した宣言が参照するパッケージ自身の型は、どれもいずれかの入口から公開する (API Extractor の `ae-forgotten-export` と同じ規則。`src/public-surface.spec.ts` が 3 つの入口の宣言の出力をたどって確かめる)。ホストは自分が渡すか受け取る型 (ポート・props・フックの値) を形を書き写さずに名前で読める。
131
+ 共有エントリ: `DailyReportActionResultReading`・`DailyReportAttachmentState`・`DailyReportBusinessDateInput`・`DailyReportUIComment`・`DailyReportIdsStreamCursor`・`DailyReportSseTerminalEvent`・`DailyReportLogger`・`DAILY_REPORT_SEARCH_COMBINES`・`DailyReportSearchCombine`・`DailyReportSearchHit`。
132
+ client: 部品の props (`DailyReportPageProps`・`DailyReportResolvedContentProps`・`DailyReportListProps`・`DailyReportDetailListProps`・`DailyReportDetailListScrollRestore`・`DailyReportAttachmentIndicatorProps`)、設定 (`DailyReportClientConfig`・`DailyReportHeaderProps`・`DailyReportSearchSetting`・`DailyReportSearchSettingInput`)、アクションのコンテキスト (`DailyReportActionContextType`・`DailyReportActions`・`DailyReportActionFunctions`・`DailyReportActionLedgers`・`DailyReportActionState`・`DailyReportActionBusinessDate`)、
133
+ `DailyReportDetailResource`・`UseDailyReportSseConnectionOptions`・`DailyReportListNavigationHandle`・ids のストリーム (`DailyReportIdsStreamState`・`DailyReportIdsStreamPhase`・`DailyReportIdsStreamClientOptions`・`DailyReportIdsStreamItemsChange`・`DailyReportIdsStreamItemReplacement`)・`DailyReportSearchFailure`。
134
+ server: `DailyReportService`・`DailyReportEpochStore`・`DailyReportSqlResultCacheQueryOptions`・`DailyReportExternalSource`・`DailyReportSseReaderConfig`・`DailyReportVisibleSourceTypeSet`・`DailyReportResourceRouteArgs`、ポートの型 (`DailyReportResolveUserId`・`DailyReportEncodeUserId`・`DailyReportResolveVisibleSourceTypes`・`DailyReportVisibleSourceTypes`・`DailyReportVisibilityDimensions`・`DailyReportVisibilityResolveContext`・`DailyReportVisibilityResolveReason`・`DailyReportRedisClient`・`DailyReportRedisBlockingClient`・`DailyReportRedisStreamMessage`・`DailyReportSseEntryMessage`)、
135
+ 表と行 (`DailyReportHubTable`・`DailyReportInternalTable`・`DailyReportCommentTable`・`DailyReportLabelTable`・`DailyReportHubLabelTable`・`DailyReportUserStatusTable`・`DailyReportUserTable`・`DailyReportAttachmentTable`・`DailyReportHubRow`・`DailyReportInternalRow`・`DailyReportUserStatusRow`・`DailyReportIdsSource`)、`DailyReportDb` の部品 (`DailyReportDbRows`・`DailyReportDbSelectChain`・`DailyReportDbInsertChain`・`DailyReportDbUpdateChain`・`DailyReportDbDeleteChain`)、
136
+ 添付 (`DailyReportAttachmentThumbnailDelivery`・`DailyReportAttachmentBytes`・`DailyReportAttachmentThumbnailError`・`DailyReportAttachmentThumbnailFailure`・`DailyReportSharpPipelineLike`・`DailyReportSharpHeaderLike`) と検索 (`DailyReportSearchDocumentTable`・`DailyReportSearchCommentTable`・`DailyReportSearchSqlExecutor`・`DailyReportSearchPlan`・`DailyReportSearchSet`・`DailyReportSearchWord`)。
137
+ - client の `DAILY_REPORT_LATTICE_BLOCK_QUANTUM_CLASS_NAME`: 装置の画素の格子のブロックの刻み q をカスタムプロパティ `--aqdr-lattice-block` で運ぶクラス (パッケージ自身の刻みのクラスそのもの)。ホストは文書の根に置き、自分のバー (ヘッダーとフッター) の高さを `round(up, <大きさ>, var(--aqdr-lattice-block))` で切り上げる。1/8 の奇数倍の画素密度では 4 px (`DAILY_REPORT_LAYOUT_LATTICE_PX`) が装置の画素の半分なので、単位だけでは格子を外れる。
138
+ - **ラベル `noticeRepeated(count)`** (固有キーは 98): 同じ文言の失敗が続いたときに知らせの文言の横に見せる回数 (en `(2 times)`、ja `(2 回目)`)。
139
+ - **ID でまとめた詳細 (`reports`) の受け入れ**: 検索と同じ工場から作る自分の受け入れを持つ。同時数は検索の `concurrency` の 2 倍 (`REPORTS_CONCURRENCY_PER_SEARCH_CONCURRENCY`。閲覧者 1 人の取り分は検索の `concurrency`) で、回数は閲覧者ごと 1 分に検索の `rateLimitPerMinute` の 10 倍 (`REPORTS_RATE_PER_SEARCH_RATE`。検索のバケットと分け合わない)。超えた要求は 429 (`Retry-After`) で、拒否の行は `route=reports` を名乗る。内部ユーザーを持たない閲覧者は受け入れもクエリも使わずに空の答え。
140
+
141
+ ### Breaking
142
+
143
+ - **`DailyReportErrorProvider` は必須**: 通知の面はプロバイダーの外で黙って何もしない既定値を持たず、`DailyReportErrorProvider` の外で描いた `DailyReportActionProvider` と List のモバイルのオーバーレイは描画で `[daily-report] useDailyReportErrorSurface must be used within a DailyReportErrorProvider` を投げる (アクションのコンテキストのフックがプロバイダーの外で投げるのと同じ)。以前はプロバイダーの外の失敗を知らせずに捨てた。
144
+ Migration: `DailyReportPage` を使うホストは変更が要らない。`DailyReportActionProvider` を自分で組むホストは `DailyReportErrorProvider` で包む (README が以前から求めていた組み方)。
145
+ - **ホストのロガーへ渡す行は接頭辞を持ち、どの水準も届く** (上の Changed): ホストのロガーは、以前は接頭辞の無い行を、チャンネルの既定の水準を通ったものだけ受けた。今はどの行も `[DailyReport<チャンネル>]` で始まり (文字列でない文面は接頭辞を別の最初の引数として)、debug を含むどの水準も届く。500 は 1 行で、例外のオブジェクトを 2 番目の引数で受ける。
146
+ Migration: 行の字面を照合するホストの記録や警報は、接頭辞の付いた行に合わせる (迂回のバケットの 429 は `[DailyReportRefresh]`)。水準を選ぶのはホストのロガー。
147
+ - **`deleteReport` は失敗で拒否する**: 削除の失敗は、日報を一覧へ戻した後に失敗を返す (誤りの知らせも出す)。以前は `@throws` を書きながら解決したので、呼び出し側は削除が済んだものとしてフォーカスを渡した。利用者の切り替えの後に解決した継続は巻き戻さずに解決する。
148
+ 側面ペインの削除のボタンは拒否を受けて `labels.deleteFailed` の `alert()` を出し (誤りの知らせと並ぶ)、日報が消えたものとしてフォーカスを渡さない。DetailList の削除のボタンは受けて何もしない。
149
+ Migration: アクションのコンテキストの `deleteReport` を自分で呼ぶホストは拒否を受ける (`catch` する)。
150
+
5
151
  ## 0.34.0 (2026-10-07)
6
152
 
7
153
  0.33.0 の続き: コメントの作者を ID で決めること、誤りの知らせを時間で消さず何も覆わない流れの中に置くことと、モバイルのオーバーレイの中に描くこと、自分を外す再試行ボタンのフォーカスとストリームの状態の読み上げ、端の行の `Home` / `End`、照合用 ID の衝突と消えた日報の削除、期限のある台帳と操作ごとの錠、1/8 の倍数の画素密度の装置の画素の格子、action と API のワイヤの契約の公開と ID の 1 つの定義域、キャッシュを迂回する読み取りの合わせ方と上限、閲覧者ごとのストリームの枠、JSON の応答の記録、対を持たないサロゲートのファイル名。どれも README の該当の節が今の振る舞いを述べる。