@aiquants/daily-report 0.37.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 +43 -0
- package/README.md +83 -26
- package/dist/client.d.mts +33 -4
- package/dist/client.d.ts +33 -4
- package/dist/client.js +6 -6
- package/dist/client.mjs +6 -6
- package/dist/{comment-adapter-_h3W1duG.d.mts → comment-adapter-BsbOiHmX.d.mts} +1 -1
- package/dist/{comment-adapter-DOUwSlKf.d.ts → comment-adapter-CldlCy1v.d.ts} +1 -1
- package/dist/index.d.mts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/search-filters-wire-5MWi6GWu.d.mts +12 -0
- package/dist/search-filters-wire-5MWi6GWu.d.ts +12 -0
- package/dist/server.d.mts +78 -22
- package/dist/server.d.ts +78 -22
- package/dist/server.js +9 -9
- package/dist/server.mjs +9 -9
- package/dist/styles/daily-report.standalone.css +1 -1
- package/package.json +1 -1
- package/dist/{sse-schema-getLS62T.d.mts → sse-schema-byewDmZK.d.mts} +52 -52
- package/dist/{sse-schema-getLS62T.d.ts → sse-schema-byewDmZK.d.ts} +52 -52
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
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
|
+
|
|
5
48
|
## 0.37.0 (2026-10-09)
|
|
6
49
|
|
|
7
50
|
### Added
|
package/README.md
CHANGED
|
@@ -83,6 +83,9 @@ Every TypeScript or JavaScript code block of this README names its source in its
|
|
|
83
83
|
|
|
84
84
|
File names are cut at code-point boundaries rather than grapheme boundaries, because `Intl.Segmenter` (Firefox 125) is above the floor (see [Attachment display](#attachment-display)).
|
|
85
85
|
|
|
86
|
+
The controls of the search's filter row are the host's, not the package's (`config.search.renderFilterSelect`; see **Filter row** in [Text search](#text-search)): the package's browser code imports no control library — neither `@aiquants/select-box` nor `@aiquants/fuzzy-search`, which `src/client-bundle.structure.spec.ts` checks in the sources and in a fresh build —, so the floor above stays the package's.
|
|
87
|
+
A host control carries its own floor (a control built on `@aiquants/select-box`, for example, needs Firefox 125 for `Intl.Segmenter`), and a control that throws while it renders turns only its own filter cell into `labels.searchFilterUnavailable`.
|
|
88
|
+
|
|
86
89
|
### Tailwind CSS
|
|
87
90
|
|
|
88
91
|
This package has **no hand-written component CSS**, so it ships **no components-only artifact** — only a standalone build for non-Tailwind hosts. Components carry Tailwind utility classes in JSX. Pick one consumption mode:
|
|
@@ -189,13 +192,14 @@ export const loader = (args) => dailyReportServer.attachment.loader(args)
|
|
|
189
192
|
React Router runs a resource route's loader for `HEAD` and drops the body without reading or cancelling it, and an adapter need not abort the request's signal once the response is sent, so body work started for a `HEAD` would run until the process ends. A `GET` whose signal is already aborted starts no body work either.
|
|
190
193
|
- **Single fetch**: React Router answers a single-fetch data request (`<path>.data`) by running the route's loader and reading its whole body before it answers, so a `.data` request to either stream would hold every event, or the whole NDJSON, in server memory until the client leaves.
|
|
191
194
|
The package's request isolation refuses them before authentication ([Request isolation](#request-isolation)): on the SSE route every one, and on the API route those of every endpoint whose body streams — the ids stream's (`ids-stream.data`, with or without a cursor, and its trailing-slash form `ids-stream/_.data`) —, `GET` and `HEAD` alike, with a 404 JSON (`{"error":{"message":"Not Found"}}`, `Cache-Control: no-store` and the routes' security headers, **Security headers** below) thrown the way a loader answers early.
|
|
192
|
-
A refused request authenticates nothing, subscribes to nothing, starts no timer and runs no query. The JSON endpoints (`report`, `business-date`, and `search`, `reports` and `search-
|
|
195
|
+
A refused request authenticates nothing, subscribes to nothing, starts no timer and runs no query. The JSON endpoints (`report`, `business-date`, and `search`, `reports`, `search-marks` and `search-facets` with the `search` block) and the action keep serving single fetch, which React Router's own fetchers use.
|
|
193
196
|
- **The API route's methods**: authentication comes first (401), then the endpoint — an unknown name is 404, an inherited name such as `toString` included — and then the method: one the endpoint does not answer is 405 with `Allow`.
|
|
194
|
-
The endpoint table (`API_ENDPOINT_TABLE` in `src/server/api-endpoint.ts`) states once, per endpoint, what kind of body it answers and what bounds its database work per viewer, and the methods follow from the body (`API_ENDPOINT_METHODS`): `GET` for a JSON body (`report` and `business-date`, and `search`, `reports` and `search-
|
|
197
|
+
The endpoint table (`API_ENDPOINT_TABLE` in `src/server/api-endpoint.ts`) states once, per endpoint, what kind of body it answers and what bounds its database work per viewer, and the methods follow from the body (`API_ENDPOINT_METHODS`): `GET` for a JSON body (`report` and `business-date`, and `search`, `reports`, `search-marks` and `search-facets` with the `search` block, whose ETag is computed from the body, so a `HEAD` would build the body only to discard it) and `GET, HEAD` for a streaming body (the ids stream).
|
|
198
|
+
The API route's refusal of data requests above follows from the same table.
|
|
195
199
|
Each row's `admission` lists at least one bound (the row's type requires it, so an endpoint cannot enter the table without one): `cache` (reads through the SQL result cache, which shares one fetch among identical concurrent reads and keeps the result for its TTL, in at most `cacheMaxBuckets` buckets), `miss-gate` (a read that misses the cache takes one of the viewer's cache-miss slots before its query starts, **Cache misses per viewer** below),
|
|
196
200
|
`refresh-bucket` (a read that bypasses the cache spends the viewer's refresh bucket, **`forceRefresh`** under **Request values** below), `stream-slots` (the body holds one of the viewer's stream slots, **Streams per viewer** below) and `search-limiter` (the endpoint's own search admission, **Requests** in [Text search](#text-search)).
|
|
197
201
|
`cache` alone shares only identical reads: every distinct key — any business date from 0001 to 9999, any id — misses it, so the distinct reads are bounded by `miss-gate`.
|
|
198
|
-
`business-date` and `report` declare `refresh-bucket`, `cache` and `miss-gate`, the ids stream `refresh-bucket`, `stream-slots`, `cache` and `miss-gate`, and `search`, `reports` and `search-
|
|
202
|
+
`business-date` and `report` declare `refresh-bucket`, `cache` and `miss-gate`, the ids stream `refresh-bucket`, `stream-slots`, `cache` and `miss-gate`, and `search`, `reports`, `search-marks` and `search-facets` `search-limiter` each. Every handler admits its bounds after its 400s, and one spec walks the table and proves each declared bound on its handler (`src/server/handlers.json-endpoints.spec.ts`), so a bound that is declared and not enforced fails.
|
|
199
203
|
- **Streams per viewer**: one viewer holds at most `DAILY_REPORT_STREAMS_PER_VIEWER` = 32 streaming responses at once in one worker process, SSE streams and ids streams together (`createStreamSlots` in `src/server/stream-body.ts`: one set per handler factory, keyed by the internal user id, no wait queue). The bound is the package client's own need with headroom: a view holds 2 streams (its SSE connection for its whole life, and its ids stream while it loads or rescans), and a viewer may keep 16 views open across tabs, windows and devices.
|
|
200
204
|
It is not derived from HTTP/1.1's 6 connections per origin, since one HTTP/2 connection carries many streams. A held stream costs about 10 KB of heap and one visibility re-resolution every 60 s, so 32 streams cost about 310 KB and 0.53 re-resolutions a second.
|
|
201
205
|
A stream takes its slot after the checks that decide its status — on the SSE route after the resume cursor and before the visibility is resolved or anything subscribed, on the ids stream after its 400s and the refresh bucket — and gives it back exactly once, at whichever comes first: the request's abort, at any time after the slot was taken, or the settle path that ends its body (the SSE connection's close; the ids stream's end, cancel or error, and a resumed `GET` that fails before its body starts).
|
|
@@ -295,7 +299,7 @@ export const loader = (args) => dailyReportServer.attachment.loader(args)
|
|
|
295
299
|
| `[DailyReportRefresh]` | `warn` | the refresh bucket's 429 (**`forceRefresh`** there) |
|
|
296
300
|
| `[DailyReportStreams]` | `warn` | the 503 of **Streams per viewer** there |
|
|
297
301
|
| `[DailyReportReads]` | `warn` | the 503 of **Cache misses per viewer** there |
|
|
298
|
-
| `[DailyReportSearch]` | `warn` | the 429 of the `search`, `reports` and `search-
|
|
302
|
+
| `[DailyReportSearch]` | `warn` | the 429 of the `search`, `reports`, `search-marks` and `search-facets` admissions (**Requests** in [Text search](#text-search)) |
|
|
299
303
|
| `[DailyReportServer]` | `warn` | `createDailyReportServer` without a `redis` provider |
|
|
300
304
|
| `[DailyReportService]` | `info` | the service: SSE publishing (an event over the size bound, a slow or failed publish), a failed read of the stream anchor, the development cache clear |
|
|
301
305
|
| `[DailyReportSseReader]` | `info` | the shared SSE reader's Redis errors |
|
|
@@ -857,7 +861,7 @@ A viewer therefore writes at most two lines per bucket and window, whatever its
|
|
|
857
861
|
|
|
858
862
|
### Text search
|
|
859
863
|
|
|
860
|
-
The screen can search the reports by text. The server side is one optional block of the service configuration, `search`, and the client side is `config.search`; pass the client part only when the server has the block (without it, the `search`, `reports` and `search-
|
|
864
|
+
The screen can search the reports by text and filter the search by author, source type and customer. The server side is one optional block of the service configuration, `search`, and the client side is `config.search`; pass the client part only when the server has the block (without it, the `search`, `reports`, `search-marks` and `search-facets` endpoints answer 404 `Unknown endpoint`).
|
|
861
865
|
|
|
862
866
|
```ts examples/search-wiring.ts
|
|
863
867
|
/**
|
|
@@ -867,11 +871,11 @@ The screen can search the reports by text. The server side is one optional block
|
|
|
867
871
|
* The search is one optional block, `search`: the two index tables (here from `defineDailyReportSearchSchema`; the host's own models of
|
|
868
872
|
* the same shape also work), the readers of the searchable text the detail view does not show (per external source type), and the
|
|
869
873
|
* request limits. With the block, the six save paths rewrite the index inside their transaction (a failed index write fails the save)
|
|
870
|
-
* and the `search`, `reports` and `search-
|
|
874
|
+
* and the `search`, `reports`, `search-marks` and `search-facets` endpoints answer; without it they answer 404. Create the tables and run the initial
|
|
871
875
|
* reconciliation before passing the block in production: while the tables are missing, every save fails.
|
|
872
876
|
* 検索は任意の 1 つのブロック `search` にまとまる。索引の 2 表 (ここでは `defineDailyReportSearchSchema` から。同じ形のホストのモデルでもよい)、
|
|
873
877
|
* 詳細画面に出ない検索できる文字を読む口 (外部ソースの区分ごと)、要求の上限。ブロックを渡すと、保存の 6 つの経路がそのトランザクションの中で索引を書き直し
|
|
874
|
-
* (書けなければ保存も失敗する)、`search`・`reports`・`search-marks` のエンドポイントが答える。渡さなければどれも 404。本番では、表を作って最初の
|
|
878
|
+
* (書けなければ保存も失敗する)、`search`・`reports`・`search-marks`・`search-facets` のエンドポイントが答える。渡さなければどれも 404。本番では、表を作って最初の
|
|
875
879
|
* 突き合わせを流してからブロックを渡す (表が無い間は、どの保存も失敗する)。
|
|
876
880
|
*/
|
|
877
881
|
import {
|
|
@@ -904,7 +908,7 @@ export type SearchWiring = {
|
|
|
904
908
|
* 文字の検索を有効にした日報サーバーを作る処理。
|
|
905
909
|
*
|
|
906
910
|
* @param wiring Base configuration, the hidden-text reader and the limits. 基本設定・隠れた文字を読む口・上限。
|
|
907
|
-
* @returns The server (`DailyReportServer`); its API route answers `search`, `reports` and `search-
|
|
911
|
+
* @returns The server (`DailyReportServer`); its API route answers `search`, `reports`, `search-marks` and `search-facets`. サーバー (`DailyReportServer`。API の経路が `search`・`reports`・`search-marks`・`search-facets` に答える)。
|
|
908
912
|
* @throws {RangeError} When the block is invalid (an unknown key, a missing column, a limit that is not an integer of at least 1, or a reader for a source type `externalSources` does not have). ブロックが不正なとき (知らないキー・列の欠け・1 以上の整数でない上限・`externalSources` に無い区分の読み取りの口)。
|
|
909
913
|
*/
|
|
910
914
|
export const createDailyReportServerWithSearch = ({ base, readNiDetailTexts, limits }: SearchWiring): DailyReportServer =>
|
|
@@ -947,18 +951,39 @@ export const reconcileEveryReport = (server: DailyReportServer, signal: AbortSig
|
|
|
947
951
|
Words and keywords are de-duplicated by their whole set of alternatives, readings included, so two words that normalize alike but differ in their readings (`Ⅱです` reads nothing, `iiです` reads イイデス) are both kept: the plan does not depend on the order the words are typed in, and adding a word to an OR query never takes an alternative away.
|
|
948
952
|
- **What is searched.** The fields the detail view shows, resolved the same way (the title, the body, the people masked like the detail, the categories, the customer, the interviewers and the attachment file names), the label names (searched although the views do not draw them), the text the host's readers return for an external source (`externalSourceTexts`: lines the detail view does not show, never the audit actors or raw ids), and the comments.
|
|
949
953
|
A posted comment is searched for a viewer the detail view would show it to (the comment scope allows the report's source type, or the viewer wrote it); an external comment follows the comment scope alone.
|
|
950
|
-
- **
|
|
951
|
-
|
|
954
|
+
- **Filters.** The search can be filtered by author — the author name the list shows (`employeeName`), masked like the detail —, by source type (`Internal` and the `sourceType` of each `externalSources` adapter) and by customer, the customer name the detail shows.
|
|
955
|
+
Each filter takes several values: the values of one filter are ORed, and the filters are ANDed with each other and with the text. An author or customer value passes only the exactly equal value of the index (a report without the value never passes that filter); a source type compares case-insensitively, as the visibility does.
|
|
956
|
+
A blank text — whitespace only, as `String.prototype.trim` reads it (`isBlankSearchText`, the one definition the client and the server share) — with at least one filter is a filters-only search: it lists every report the viewer may read that the filters pass, in the ID list's order, and its answer carries no words, so nothing is highlighted.
|
|
957
|
+
A text that is not blank but leaves no word (zero-width characters only, for example) is refused with the plan's 400, filters or not. Filter values are never highlighted, and a request without filters is the plain text search described above.
|
|
958
|
+
- **Authorization.** The search SQL applies the detail's conditions itself: no deleted report, no other user's draft (by the draft label names), only the viewer's visible source types. The filters' candidates (`search-facets`, under **Requests**) are read under the same conditions.
|
|
959
|
+
- **The index.** Two derived tables per database, `DailyReportSearchDocument` (one row per report: the normalized text, the digest of the raw source, the normalizer version, `row_version` and the two filter values) and `DailyReportSearchComment` (one row per comment, with its author id), their normalized text `nvarchar(max)` in `Latin1_General_100_BIN2` so that `LIKE` compares it as it is, and both deleted with their report (`ON DELETE CASCADE`).
|
|
960
|
+
The filter values are two nullable columns of the document, `author_name` and `customer_name`: `nvarchar(200)` in `Latin1_General_100_BIN2`, so that the filters and their candidates compare values exactly, each with an index of its own (the candidates' `GROUP BY` and the filters' `IN` read them).
|
|
961
|
+
They hold the author name the list shows (`employeeName`) and the customer name the detail shows, both masked like the detail, in their canonical form (`canonicalSearchFilterText`), the text the list shows: the value with `String.prototype.trim` applied (every leading and trailing whitespace, U+3000 and line breaks included), each lone surrogate replaced by U+FFFD (a URL cannot carry one, so the filter could never send it back), and `NULL` when nothing remains.
|
|
962
|
+
Values the list shows alike are therefore one candidate, and every candidate is a value a filter can send unchanged.
|
|
963
|
+
**Host contract**: the author and customer display values (`employeeName` and `customerName` of each `externalSources` adapter's `mapRow`, and the `userTable`'s `displayName` of a report's creator, the author when no `employeeName` is given) must be at most 200 UTF-16 units after trimming.
|
|
964
|
+
A longer one throws `[daily-report] search index: report <id>: the author|customer display value has <n> UTF-16 units after trimming; the host must keep the author and customer display values at most 200 UTF-16 units`, which fails that report's save, and stops the reconciliation at that report's batch (rolled back; the batches before it stay written) on every run until the host shortens the value; the report id in the message names the row to fix.
|
|
965
|
+
The package writes both columns in every save and every reconciliation, so a document table without them fails every save: create them with the table, and add them (with their indexes) to an existing table before deploying a build that writes them. They are nullable, so adding them disturbs no process that does not write them yet.
|
|
952
966
|
The six save paths (create, update, publish, delete, add and delete a comment) rewrite the report's rows inside their own transaction, after taking an update lock on the report row; a failed index write fails the save. Publishing, deleting and the comment paths therefore run in a transaction, which a deadlock retries as a whole.
|
|
953
967
|
- **Reconciliation.** Writes outside the package (a sync, an import) leave the index stale until `service.reconcileSearchIndex({ batchSize, hubIds?, signal?, onProgress? })` runs.
|
|
954
968
|
It walks every report (or the given ones) in batches of 1 to 500, one transaction per batch at the low deadlock priority, takes the same locks as a save, and rewrites only the reports whose digest or normalizer version differ.
|
|
955
969
|
Run it from a command-line process after such writes, for the initial build, and after deploying a build whose normalizer version changed (the version fingerprints the normalization of the whole Basic Multilingual Plane, the Unicode and the ICU versions; the first save or reconciliation of a process computes it, about a second).
|
|
970
|
+
The digest covers the fields, then the comments, then the two filter values in the form the document stores (its `P` section), all as UTF-16 code units (a lone surrogate and U+FFFD differ), so a change of a filter value alone rewrites the document too, while a source value that differs only in leading or trailing whitespace does not. A document written by a build whose digest has no `P` section differs from every digest this build computes:
|
|
971
|
+
after deploying this build onto an existing index (its columns added first, under **The index**), run one reconciliation once every server that writes the database runs this build, which rewrites every document and fills the filter columns. Until then a document that no save has rewritten keeps both filter values `NULL`, so the author and customer filters never pass it and its values are no candidates (the source-type filter reads the report row and passes it).
|
|
972
|
+
A server still on a previous build keeps writing documents without the filter values (a new document gets `NULL`, a rewritten one keeps its old values), so a document it writes after the reconciliation keeps empty or stale filter values until the next reconciliation; run it again after the last such server is gone, and a second run that reports 0 rewrites confirms that every document carries this build's values.
|
|
973
|
+
The author and customer candidates and the author and customer filters read these stored values, not the source rows: the package's saves and the reconciliation refresh them, so a host that changes source rows outside the package (an external source's sync, an import) should run the reconciliation for those reports (`hubIds`), or the candidates and matches keep the old value until then.
|
|
956
974
|
- **Requests.** `GET {apiBasePath}/search?q=<text>&combine=intersection|union` answers column-wise, `{ ids, businessDates, sourceTypes, words }` in the order of the ID list, with an ETag; a missing `q` or `combine` and a query the plan rejects are 400 (the body's `reason` is `empty`, `too-long`, `too-many-sets`, `too-many-words-in-set`, `too-many-words`, `word-too-long` or `blank-word`), and a viewer over `rateLimitPerMinute` or a process at `concurrency` gets 429 with `Retry-After`.
|
|
957
975
|
`words` is the plan's words, keyword by keyword: each word's alternatives `{ text, tier }` (`DailyReportSearchAlternative`), tier 0 the typed word's forms and tier 1 its romaji readings. It depends on `q` and `combine` alone, never on the viewer, and the client reads it strictly with the rest of the answer (at most 16 keywords, 16 words a keyword, 32 words, 6 alternatives a word of 1 to 100 units, the first alternative of tier 0, tiers that never fall and no text twice in a word): a malformed answer is refused whole. It decides which words the highlights mark.
|
|
976
|
+
A filters-only search answers `words: []`, and the client accepts an answer without keywords only for a request whose text is blank (and an answer with keywords only for one whose text is not).
|
|
977
|
+
The filters travel as `author`, `sourceType` and `customer` (`DAILY_REPORT_API_QUERY_PARAMS.searchAuthor`, `searchSourceType` and `searchCustomer`), each parameter repeated once per value and never comma-joined, since a value may hold a comma; the client sends them in canonical order (each filter's values deduplicated and sorted by UTF-16 code units).
|
|
978
|
+
The limits (`DAILY_REPORT_SEARCH_FILTER_LIMITS`) are 16 values per filter, 200 UTF-16 units per value and 300 units for the author and customer values together (a source type is a configured name and does not count). A value is read as sent, never trimmed or dropped, so a request whose filters break a rule is refused whole:
|
|
979
|
+
a blank value, a value not in canonical form (leading or trailing whitespace as `String.prototype.trim` reads it, or a lone surrogate; the form the index stores, under **The index**), a value over 200 units, more than 16 values, the same value twice (source types compared case-insensitively), more than 300 units and a source type that is neither `Internal` nor an `externalSources` adapter's are each a 400 `{ error: { message: "Invalid search filters", reason } }`,
|
|
980
|
+
the `reason` being `filter-blank-value`, `filter-value-not-canonical`, `filter-value-too-long`, `filter-too-many-values`, `filter-duplicate-value`, `filters-too-long` or `filter-unknown-source-type`.
|
|
981
|
+
The 400s come in order, all before any service call: `q` and `combine` (`Invalid search request`), then the filters, then the text (`Invalid search query` with the plan's `reason`). `q` stays required: a blank `q` (`q=` included) with a filter is the filters-only search, and without one it is still the 400 `empty`.
|
|
982
|
+
A request without filter parameters adds no condition to the SQL: its statement and parameters are those of the text search alone.
|
|
958
983
|
`GET {apiBasePath}/reports?ids=<id,id,...>` (distinct canonical ids, at most 100) answers the visible details among them, `{ reports }`. Neither is cached on the server.
|
|
959
984
|
`reports` has an admission of its own from the same factory, never the search's: `REPORTS_CONCURRENCY_PER_SEARCH_CONCURRENCY` (2) × the larger of `concurrency` and `DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT` (2, the batch reads the client keeps in flight) reads at once per process, of which one viewer holds at most half, so a viewer always passes the client's two concurrent reads (a `concurrency` of 1 admits 4 per process),
|
|
960
985
|
and `REPORTS_RATE_PER_SEARCH_RATE` (10) × `rateLimitPerMinute` reads per viewer and minute, in a bucket apart from the search's, so paging through a large answer neither runs out of reads nor spends the searches.
|
|
961
|
-
Its order is the search's: the ids (400), then a viewer without an internal user answered `{ reports: [] }` with no admission and no query, then the admission with the request's signal (an aborted request takes no slot and returns its token; a full gate is 429 with `Retry-After: 1`, an empty bucket 429 with one window's `Retry-After`), the query, and the slot's release whatever the outcome. Its refusal lines name `route=reports`.
|
|
986
|
+
Its order is the search's: the ids (400), then a viewer without an internal user answered `{ reports: [] }` with no admission and no dataset query, then the admission with the request's signal (an aborted request takes no slot and returns its token; a full gate is 429 with `Retry-After: 1`, an empty bucket 429 with one window's `Retry-After`), the query, and the slot's release whatever the outcome. Its refusal lines name `route=reports`.
|
|
962
987
|
`GET {apiBasePath}/search-marks?q=<text>&combine=intersection|union&ids=<id,id,...>` (the shown answer's query and at most 100 distinct canonical ids, the rows a view draws) answers `{ wordsDigest, reports }` (`DailyReportSearchMarksAnswer`): for each requested report the viewer may read, once, its id, the mask of the keywords it matches (`matchedSets`: bit i for keyword i, for an OR of two or more keywords;
|
|
963
988
|
`null` otherwise) and the marks of every non-empty field value the views draw (`fields`, by key: `subject`, `content`, `author`, `employeeName`, `updatedBy`, `category`, `creationCategory`, `customerName`, `interviewer:<i>:name|affiliation`, `attachment:<i>` and `comment:<id>:author|content`, the comment id as `mergeComments` gives it).
|
|
964
989
|
A field's marks are the fingerprint of the raw value they were computed on (FNV-1a 32 over its UTF-16 units) and flat `[start, end, tier]` triples in UTF-16 offsets of that value; `wordsDigest` is the digest of the answer's `words`.
|
|
@@ -971,28 +996,52 @@ export const reconcileEveryReport = (server: DailyReportServer, signal: AbortSig
|
|
|
971
996
|
A new answer's first batches therefore wait for the abandoned batches of the previous answer, which hold their slots until their reads end, instead of spending their one retry; a request past the queue or the wait bound gets 429 with `Retry-After: 1`.
|
|
972
997
|
Its order is the search's and the details': 404 without the `search` block, 400 for a missing `q` or a bad `combine`, 400 with the plan's `reason`, 400 for the ids, then a viewer without an internal user answered `{ wordsDigest, reports: [] }` with no admission and no dataset query, then the admission with the request's signal, the marks (one report at a time, yielding to the event loop between reports), the slot's release whatever the outcome, and the JSON. It is not cached on the server either.
|
|
973
998
|
The service takes the request's signal too (`markDailyReportSearchHits(scope, userId, plan, ids, { signal })`) and checks it before each read and before each report's marks, stopping there once the client has left; drizzle cannot cancel a running query, so the slot is held until the read in progress ends. A request whose client left — while it waited, at one of the service's checks or after the service answered — is answered 503 without a body, with `Retry-After` and `Cache-Control: no-store`, and writes no error line;
|
|
974
|
-
the refusal of an aborted request is never recorded as busy, on any of the
|
|
999
|
+
the refusal of an aborted request is never recorded as busy, on any of the four search routes (`search-facets` included).
|
|
975
1000
|
Every answer the route builds — the marks, its 400, 404 and 429, and that 503 — is JSON without an ETag and with `Cache-Control: no-store` (`noStoreJsonResponse` in `src/server/response.ts`, serialized once, with the shared security headers): the batches are cut from the rows on screen, so a URL rarely repeats, and the package's client sends no `If-None-Match`; an `If-None-Match` is not read.
|
|
976
|
-
|
|
1001
|
+
`GET {apiBasePath}/search-facets` (it reads no parameter) answers the filters' candidates for the viewer, `{ sourceTypes, authors, customers }` (`DailyReportSearchFacets`).
|
|
1002
|
+
`sourceTypes` lists the source types a filter may name (`service.searchFilterSourceTypes`: `Internal`, then each `externalSources` adapter's, in the configured order, distinct case-insensitively: a later one that differs from an earlier one only in case is dropped, since the filter compares source types case-insensitively and would refuse both as a duplicate) that the viewer's scope allows, without a query.
|
|
1003
|
+
`authors` and `customers` are the distinct non-`NULL` filter values of the documents of the reports the viewer may open, read with the search's own scope conditions (another user's draft, a deleted report and a hidden source type lend no value): one `GROUP BY` query per column, the two run one after the other, in the column's order (BIN2: UTF-16 code units) and with no row limit.
|
|
1004
|
+
Its order is the search's: 404 without the `search` block, then a viewer without an internal user answered `{ sourceTypes: [], authors: [], customers: [] }` with no admission and no dataset query, then the admission with the request's signal, the candidates (none, and no query, for a scope that denies every source type), the slot's release whatever the outcome, and the ETag'd JSON (304 for a matching `If-None-Match`).
|
|
1005
|
+
It is not cached on the server, since a cache would keep offering the value of a report that has since become a draft or been deleted.
|
|
1006
|
+
Its admission is a fourth instance from the same factory, with the search's own `concurrency` and `rateLimitPerMinute` and a bucket of its own, so the candidates still load while the viewer's searches hold their slots (a viewer holds at most half of a gate) or after they spent their bucket; its refusal lines name `route=search-facets`.
|
|
1007
|
+
- **The search box** (`config.search: {}` searches on Enter only; `{ debounceMs }`, an integer from 300 to 5000, also after typing stops; `{ renderFilterSelect }` adds the filters, **Filter row** below) is a second row in the toolbar's band, under the view-mode toolbar: the AND / OR toggle, the field, the filter toggle (with `renderFilterSelect` only) and a status line. Enter, Esc and the debounce wait while an IME composes.
|
|
977
1008
|
The row is one 32 px lattice block whose 24 px controls stand at its bottom (`SEARCH_ROW_CLASS_NAME`), so they sit G = 8 under the toolbar's bar and their bottom edge is G above the view's first surface; the band carries the toolbar's start inset G and the active view's end inset, so the field ends where the cards end in both views, and it stacks above the view, so a control's focus outline paints over the view's G strip (**Origin on the lattice** in [Selection and focus appearance](#selection-and-focus-appearance)).
|
|
978
|
-
The toggle names the current mode (`AND` / `OR`) and keeps focus when pressed, so a second Space flips the mode again and the new name is read where focus stays. The clear button sits inside the field's box at its end, in room the field always keeps free, so typing never changes the field's width; it shows while the field holds text or
|
|
1009
|
+
The toggle names the current mode (`AND` / `OR`) and keeps focus when pressed, so a second Space flips the mode again and the new name is read where focus stays. The clear button sits inside the field's box at its end, in room the field always keeps free, so typing never changes the field's width; it shows while there is text to clear (the field holds text, or the committed search's text is not blank) and returns focus to the field.
|
|
1010
|
+
The clear button and Esc clear the text: without a selected filter they release the search, and with filters the search goes on as a filters-only search (with an empty field it then shows no clear button, and Esc does nothing).
|
|
979
1011
|
The status line (searching, the hit count, no hits, a failure) wraps instead of truncating, at most half the row wide, and takes no room while it is empty. The field's and the OR face's boundary and placeholder come from the text-field token (**Text fields** in [Selection and focus appearance](#selection-and-focus-appearance)).
|
|
980
1012
|
The field's description (`labels.searchSyntaxHelp`, also its tooltip) gives the syntax, says that a word in Latin letters also finds its kana reading and that a match found through a reading is underlined with dots.
|
|
981
1013
|
While a search request runs or after it failed, the views keep the list they showed; an answer replaces the list in its order (a report the provider marked removed is left out), and the header shows the hit count. While an answer shows, the rows read their details by id in batches (`reports?ids=`) instead of by business date.
|
|
982
1014
|
A new search scrolls the views to the top, releasing the search returns them to the report that was at the top before it, and the selection stays on a report the answer keeps, else moves to the nearest remaining one (on release, back to the selection before the search). A creation during a search releases it first and selects the new report.
|
|
983
1015
|
Saves, SSE report and comment events and changes of the settled ID list re-query the search quietly after about a second (`If-None-Match`).
|
|
1016
|
+
- **Filter row** (`config.search.renderFilterSelect`, a function the host provides, `DailyReportSearchFilterSelectRenderer`; a value that is not a function is a `RangeError` at render, `[daily-report] config.search.renderFilterSelect must be a function (it renders the control of each filter cell); got <value>`). Without it there is no filter toggle, no filter row and no `search-facets` request.
|
|
1017
|
+
The filter toggle (`data-testid="daily-report-search-filters-toggle"`) is a 24 px button G after the field, sharing its top and bottom edges, named `labels.searchFiltersToggle(n)` with the number of selected values (`Filters`, `Filters (3)`), with `aria-expanded` and, while the row shows, `aria-controls`.
|
|
1018
|
+
It opens the filter row under the search row: a `<fieldset>` the toggle names (`data-testid="daily-report-search-filters"`) with one cell per filter in the order of `DAILY_REPORT_SEARCH_FILTER_FACETS` — author (`labels.author`), source type (`labels.searchFilterSourceType`) and customer (`labels.customer`); `data-testid` `daily-report-search-filter-author`, `-source-type` and `-customer` —,
|
|
1019
|
+
one cell per line below `sm` and the three in one line at widths 2 : 1 : 3 from `sm`, and a status line (`daily-report-search-filters-status`, `aria-live="polite"`) that shows `labels.searchFiltersLoading` while the candidates load the first time, `labels.searchFiltersFailed(failure)` when loading them failed (also after a failed reload, under the previous candidates) and `labels.searchFiltersTooMany` after a refused selection, for as long as the row shows the selection that was refused (a release from outside, such as a creation during a search, or any new selection clears it).
|
|
1020
|
+
Each cell shows its label as a visible caption above the host's control (`data-testid` `daily-report-search-filter-<facet>-caption`, a 16 px line that wraps rather than being cut, 4 px above the control) and is a `<fieldset>` whose `<legend>` is that caption, so the caption names the cell; the control receives the same label as its accessible name.
|
|
1021
|
+
The open row adds to the toolbar's band G, the cells (caption, 4 px and the host's control; below `sm` one per line, G apart) and, while it has something to say, the status line (G + 24 px or more), and the band rounds its whole height up to the block quantum as it does closed: the host's controls decide the open band's height, and the closed band is unchanged.
|
|
1022
|
+
- **Candidates**: every opening loads `search-facets` again (one request at a time: an opening while one is in flight sends none; stopped on unmount and when `apiBasePath` changes), so authors and customers that appear while the screen stays mounted become selectable and values that left the viewer's reports drop out. The previous candidates stay while the new ones load, and a reload that answers the same values in the same order keeps the very same option arrays.
|
|
1023
|
+
A failed first load shows only the status line and is retried at the next opening; a failed reload keeps the previous candidates and shows the failure in the status line. 401 and 403 call `onSessionExpired`; 404 shows `unavailable`, 429 `busy`, and anything else, a malformed answer included, `failed`.
|
|
1024
|
+
The cells render once the candidates are loaded. The author and customer options are sorted with `Intl.Collator(config.locale)` (values the collation ties are ordered by UTF-16 code units) and labelled with their value; the source-type options keep the server's order and are labelled with their badge label from `sourceTypeConfigs` (the source type itself without one).
|
|
1025
|
+
- **The host's control** receives `DailyReportSearchFilterSelectProps` — `facet`, `label`, `options` (`DailyReportSearchFilterOption`, `{ value, label }`: one array per answer, rebuilt only when the answer, `locale` or `sourceTypeConfigs` change, for controls that validate once per array), `value` (the selected values), `onChange(values)` and `locale` — and is controlled: it shows `value` and reports a new selection through `onChange`.
|
|
1026
|
+
A selected value that the latest candidates no longer hold (a reload dropped it) is appended to that cell's `options`, labelled as the candidates label it, so the control can still show and release it; the appended array stays the same while the selection and the answer do.
|
|
1027
|
+
The reported values must be distinct values of `options`: anything else is a host-control bug and throws a `RangeError` (`[daily-report] config.search.renderFilterSelect (<facet>) onChange values must be distinct values of the options the control received; got …`). A selection over the limits is refused, so the control keeps showing the previous `value`, and the status line says why.
|
|
1028
|
+
Each cell renders its control inside an error boundary of its own: a control that throws while it renders turns only its cell into `labels.searchFilterUnavailable` until the row closes, React still reports the error, and the other cells and the search box keep working.
|
|
1029
|
+
Inside that boundary each cell also has a Suspense boundary of its own: a control that suspends (`React.lazy`, `use()`) shows `labels.searchFiltersLoading` in its cell until it is ready, and never reaches a Suspense boundary of the host, so the rest of the screen stays shown.
|
|
1030
|
+
The package's browser code imports no control library, so the control carries its own browser floor (**Browser floor** under [Install](#install)).
|
|
1031
|
+
- **Commits**: a change of the filters commits again with the field's current text and AND / OR mode, cancelling a pending debounce: a text that is not blank searches with the filters, a blank text with filters is a filters-only search, and with neither the search is released. Enter, the debounce and the AND / OR toggle carry the current filters too.
|
|
1032
|
+
The filters belong to the committed search: a change of their canonical form is a new search (the views scroll to the top), the same values in another order are not, the quiet re-query sends them again, and every release clears them — a creation during a search included.
|
|
984
1033
|
- **Highlights.** While an answer shows, the List cards, the side pane and the DetailList rows mark what the server matched with `<mark data-daily-report-search-mark>`: `word` where a typed word's form matched, the normalization included (`デンプン` marks でんぷん), and `reading` where only a romaji reading matched.
|
|
985
1034
|
- **Where**: a List card marks its preview lines and its creator; the side pane its author, subject, customer, body, attachment file names and comments (author and body, each on its own); a DetailList row its subject, customer and body, the header's author and categories, the metadata's author, updater and categories, the interviewers' names and affiliations, its attachment file names and its comments.
|
|
986
1035
|
A file name is marked in both of its forms: in the stem and extension spans of the CSS layout, each with its own part of the name, and in the fitted text, where the synthetic `…` and the elided middle carry no mark (**File name** in [Attachment display](#attachment-display)).
|
|
987
1036
|
- **Which words**: every word of an AND answer and of a one-keyword answer; for an OR of two or more keywords, only the words of the keywords the report matched (`matchedSets`; a word that several keywords share is marked when any of them matched), so a quoted keyword whose words appear only in part marks nothing.
|
|
988
|
-
- **Never marked**: label names (searched, never drawn: a report matched only through its labels shows no mark, and the indicator below shows `Hidden match`); the texts a host's readers supply for an external source (`externalSourceTexts`), which the views do not display: a report matched only through them shows no mark either, and the indicator shows `Hidden match`; the markup the List and the side pane strip (a word inside a tag), while the DetailList draws its tags as text and marks them;
|
|
1037
|
+
- **Never marked**: label names (searched, never drawn: a report matched only through its labels shows no mark, and the indicator below shows `Hidden match`); the texts a host's readers supply for an external source (`externalSourceTexts`), which the views do not display: a report matched only through them shows no mark either, and the indicator shows `Hidden match`; the filter values (a report is marked for its words alone); the markup the List and the side pane strip (a word inside a tag), while the DetailList draws its tags as text and marks them;
|
|
989
1038
|
the List preview's lines after the third (not drawn) and the elided middle of a fitted file name, whose synthetic `…` stands outside every mark; the edit form and the viewer's own draft open in it; placeholders (`-`), dates, visit times, badges, the visually hidden heading and the unknown-author substitute; and a comment still being posted.
|
|
990
1039
|
Text that the browser hides keeps its marks: a List preview line past its one-line clamp, the creator name of a List card and a file-name part of the CSS layout past their ellipsis. In Chromium a clamp or ellipsis that cuts a mark, or falls at its start, is drawn inside the mark, so the mark's fill continues under the `…` while its underline ends at the last character shown (a cut at a mark's start shows a filled `…` alone).
|
|
991
1040
|
An `interviewerWithAffiliation` template that does not insert the name and the affiliation verbatim (it rewrites them, uses one twice, or words itself by their values) draws the pill plain. `LIKE` counts an occurrence inside a grapheme cluster, so such a match marks the whole cluster.
|
|
992
1041
|
- **Look**: one token (`SEARCH_MARK_CLASS_NAME`): yellow-200 under slate-900 (dark yellow-800 under yellow-50; 15.33:1 and 6.61:1 on any surface, since the mark sets its own text colour), a solid underline for a word and a dotted one for a reading (a cue that does not depend on colour), and the system `Mark` / `MarkText` in forced colours.
|
|
993
1042
|
It writes `margin`, `border` and `padding` 0 and inherits the font, so a host's `mark` rule cannot change it and a mark changes no width, no line box and no row height. Accessible names and descriptions flatten the marks, and find-in-page and copying read the same text as without them.
|
|
994
1043
|
A reading mark also carries an accessible description (`aria-description`: `labels.searchReadingMarkDescription`, en `Matched by its romaji reading`, ja `ローマ字の読みで一致`), so the weaker match does not rest on the underline's style alone; a word mark carries none, so the common case adds no speech. The attribute's value and the description come from one table keyed by the tier (`SEARCH_MARK_LOOKS` in `src/client/search/search-marked-text.tsx`); a tier outside it stops the render with an error naming the package.
|
|
995
|
-
- **Arrival**: the screen holds one marks store per shown answer (`search-marks-store.ts`, inside the client bundle), only while a view draws an answer: without `config.search`, before the first answer is drawn, after the release and for a view rendered on its own there is none, and the DOM is the plain one.
|
|
1044
|
+
- **Arrival**: the screen holds one marks store per shown answer (`search-marks-store.ts`, inside the client bundle), only while a view draws an answer that has words: without `config.search`, before the first answer is drawn, for a filters-only answer (it has no words, so nothing is marked and no match indicator shows), after the release and for a view rendered on its own there is none, and the DOM is the plain one.
|
|
996
1045
|
A row wraps its subtree in the marks scope (`DailyReportSearchMarksScope`), which subscribes to the report's marks while the row is mounted and hands them to its leaves. The row body reads no marks, so an arrival re-renders only the scope, the marked leaves (`SearchMarkedText`) and the match indicator — never the row's body, header, attachment list or comment section, and never another row; a newly mounted row draws its text plain first and its marks in a deferred render.
|
|
997
1046
|
The requests are decided at send time (`createIdBatchScheduler` in `src/client/search/id-batch-scheduler.ts`): a subscribed report waits in one queue, and each free request slot — two in flight at most — takes the newest waiting reports that a row still subscribes to, at most 100 a request, never cached. A report whose rows left while it waited is dropped, so a scroll or a held key sends nothing for the rows it passed, and the window it stops on goes out in the first free slot.
|
|
998
1047
|
A request whose rows have all left is aborted (the server then stops computing its marks; **Requests** above), and a row that leaves before its marks were sent, or whose request was aborted, asks again when it returns.
|
|
@@ -1595,28 +1644,28 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
|
|
|
1595
1644
|
```
|
|
1596
1645
|
|
|
1597
1646
|
- **One surface for all wording.** The catalog covers the 11 engine chrome keys of
|
|
1598
|
-
`@aiquants/virtualscroll` plus
|
|
1647
|
+
`@aiquants/virtualscroll` plus 108 own keys: field headings, the page title (`title`, passed to
|
|
1599
1648
|
`renderHeader` and used as the accessible name of both lists), the lists' keyboard help, the row state and the List card's position read to
|
|
1600
|
-
screen readers, tabs, buttons, placeholders, the `confirm()` text, stream badges, the search box and the search's match indicator, the status screen and the page's status region
|
|
1649
|
+
screen readers, tabs, buttons, placeholders, the `confirm()` text, stream badges, the search box, its filter row and the search's match indicator, the status screen and the page's status region
|
|
1601
1650
|
and the mutation-failure message with its repeat count. The engine part of each catalog is `VIRTUAL_SCROLL_LABEL_CATALOGS[locale]`, reused by
|
|
1602
1651
|
value, so the scroll arrows, the names of the scroll bar and its thumb, and the "No items" text always speak the same language as the rest.
|
|
1603
1652
|
- **Forwarded to every embedded `VirtualScroll`.** Both the List and the DetailList pass `locale`
|
|
1604
1653
|
and the 11 engine keys of the resolved labels to their `VirtualScroll`. The engine label object keeps
|
|
1605
1654
|
its identity while its values are unchanged, so rebuilding `config.labels` on every render does not
|
|
1606
1655
|
re-render the memoized list subtree.
|
|
1607
|
-
- **Formatter keys.**
|
|
1656
|
+
- **Formatter keys.** Nineteen keys take arguments and are functions: `totalCount(count)`,
|
|
1608
1657
|
`debounce(milliseconds)`, `streamProgressCount(loaded)`, `streamProgressRatio(loaded, total, percent)`,
|
|
1609
1658
|
`streamRetrying(loaded)`, `reportLoadFailed(reportHubId)`, `reportSummaryLoadFailed(reportHubId)`,
|
|
1610
1659
|
`interviewerWithAffiliation(name, affiliation)`, `operationFailed(operation)` (`operation` is a
|
|
1611
1660
|
`DailyReportOperation`: `create` / `update` / `publish` / `delete` / `addComment` / `deleteComment` /
|
|
1612
1661
|
`toggleStar` / `toggleRead`), `noticeRepeated(count)`, `rowState({ isRead, isStarred })`, `listRowPosition(position, total)`, `listRowPositionInUnknownTotal(position)`,
|
|
1613
|
-
`searchHitCount(count)`, `searchFailed(failure)` (`failure` is `invalid` / `busy` / `unavailable` / `failed`), `searchMatchedKeywords(matched, total)
|
|
1662
|
+
`searchHitCount(count)`, `searchFailed(failure)` (`failure` is `invalid` / `busy` / `unavailable` / `failed`), `searchMatchedKeywords(matched, total)`, `searchMatchedKeywordsDescription(matched, total)`, `searchFiltersToggle(count)` and `searchFiltersFailed(failure)` (`failure` is `busy` / `unavailable` / `failed`). An override of such a key must be a function too.
|
|
1614
1663
|
- **Digit grouping follows the UI locale, not the runtime.** The built-in formatters group numbers with
|
|
1615
1664
|
the BCP 47 tag bound to their locale (`en` → `"en-US"`, `ja` → `"ja-JP"`), through one `Intl.NumberFormat` per
|
|
1616
1665
|
locale built once (the row positions and counts re-render on every stream publish); a host override receives
|
|
1617
1666
|
the raw number and formats it itself.
|
|
1618
1667
|
- **Fail-fast**: an unsupported `locale` (`"EN"`, `"ja-JP"`, `"fr"`, `""`, `null`, ...) throws a
|
|
1619
|
-
`RangeError` at render. So do an unknown `labels` key (a key error that lists the
|
|
1668
|
+
`RangeError` at render. So do an unknown `labels` key (a key error that lists the 119 keys), a string
|
|
1620
1669
|
key whose value is not a string with non-whitespace content, and a formatter key whose value is not a
|
|
1621
1670
|
function. An `undefined` value keeps the catalog value.
|
|
1622
1671
|
- **`config` has a closed key set**: `apiBasePath`, `ssePath`, `draftLabelName`, `locale`, `labels`,
|
|
@@ -1654,7 +1703,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
|
|
|
1654
1703
|
grouping follows `locale` (`en` → `"en-US"`, `ja` → `"ja-JP"`) and dates stay in ISO form. Packages
|
|
1655
1704
|
that do format dates (for example `@aiquants/period-slider`) name that separate axis `formatLocale`.
|
|
1656
1705
|
|
|
1657
|
-
Exported API (`@aiquants/daily-report/client`): `DAILY_REPORT_LABEL_KEYS` (all
|
|
1706
|
+
Exported API (`@aiquants/daily-report/client`): `DAILY_REPORT_LABEL_KEYS` (all 119 keys, the 11 engine keys
|
|
1658
1707
|
first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReportLabels(locale, labels)`
|
|
1659
1708
|
(the effective frozen labels — the catalog object itself when `labels` is `undefined`), the resolved
|
|
1660
1709
|
`locale` / `labels` on `useDailyReportConfig()`, and the types `DailyReportLocale` (= `VirtualScrollLocale`),
|
|
@@ -1675,14 +1724,14 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
|
|
|
1675
1724
|
| `noItems` | engine: the one message of an empty List or DetailList, over the top of the list | No items | 項目がありません |
|
|
1676
1725
|
| `id` | DetailList card: id chip | ID | ID |
|
|
1677
1726
|
| `businessDate` | business date heading | Business date | 営業日 |
|
|
1678
|
-
| `author` | author heading | Author | 作成者 |
|
|
1727
|
+
| `author` | author heading; the label of the search filter row's author cell | Author | 作成者 |
|
|
1679
1728
|
| `visitTime` | visit time heading | Visit time | 訪問時間 |
|
|
1680
1729
|
| `createdAt` | created-at heading | Created at | 作成日時 |
|
|
1681
1730
|
| `updatedAt` | updated-at heading | Updated at | 更新日時 |
|
|
1682
1731
|
| `updatedBy` | updated-by heading | Updated by | 更新者 |
|
|
1683
1732
|
| `category` | DetailList: label of the category chip and of the metadata column's category pair | Category | 区分 |
|
|
1684
1733
|
| `creationCategory` | DetailList: label of the creation-category chip and of the metadata column's creation-category pair | Creation category | 作成区分 |
|
|
1685
|
-
| `customer` | customer heading | Customer | 顧客 |
|
|
1734
|
+
| `customer` | customer heading; the label of the search filter row's customer cell | Customer | 顧客 |
|
|
1686
1735
|
| `subject` | subject heading | Subject | 件名 |
|
|
1687
1736
|
| `content` | content heading | Content | 内容 |
|
|
1688
1737
|
| `interviewers` | interviewer list heading | Attendees | 面談者 |
|
|
@@ -1745,6 +1794,10 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
|
|
|
1745
1794
|
| `commentDeleteConfirm` | comment delete confirmation | Delete | 削除する |
|
|
1746
1795
|
| `unknownUser` | author name when none is known (legacy / relational comment without a name, optimistic data of a user without a name) | Unknown | 不明なユーザー |
|
|
1747
1796
|
| `sourceInternal` | built-in `Internal` badge (only when `sourceTypeConfigs` is omitted) | Original | オリジナル |
|
|
1797
|
+
| `searchFilterSourceType` | search filter row: the label of the source-type cell (the author and customer cells use `author` and `customer`) | Source | ソース区分 |
|
|
1798
|
+
| `searchFiltersLoading` | search filter row: the status line while the candidates load | Loading filters... | 絞り込みの候補を読み込み中... |
|
|
1799
|
+
| `searchFiltersTooMany` | search filter row: the status line after a selection over the filter limits was refused | No more filter values can be selected | これ以上は絞り込みの値を選べません |
|
|
1800
|
+
| `searchFilterUnavailable` | search filter row: a cell whose host control threw while it rendered | This filter cannot be shown | この絞り込みを表示できません |
|
|
1748
1801
|
| `totalCount` | header annotation: the size of the views' set (the declared total while the ids stream delivers, the rows once it completes) | `(1234)` → Total: 1,234 | `(1234)` → 総件数: 1,234 件 |
|
|
1749
1802
|
| `debounce` | header annotation | `(300)` → Debounce: 300 ms | `(300)` → デバウンス: 300 ms |
|
|
1750
1803
|
| `streamProgressCount` | stream badge before the total is known | `(1234)` → Loading 1,234 | `(1234)` → 読み込み中 1,234 件 |
|
|
@@ -1758,6 +1811,7 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
|
|
|
1758
1811
|
| `rowState` | visually hidden row state, the first description of a DetailList row and the second of the List card's primary button | `({ isRead: false, isStarred: true })` → Unread, starred | `({ isRead: false, isStarred: true })` → 未読、スター付き |
|
|
1759
1812
|
| `listRowPosition` | visually hidden position of a List card in the list, the first description of its primary button (`position` is 1-based; `total` is the size of the views' set) | `(3, 1234)` → 3 of 1,234 | `(3, 1234)` → 全 1,234 件中 3 件目 |
|
|
1760
1813
|
| `listRowPositionInUnknownTotal` | the same position while the size of the views' set is unknown (the ids stream has not declared its total yet), in place of `listRowPosition` | `(3)` → Item 3 | `(3)` → 3 件目 |
|
|
1814
|
+
| `searchFiltersToggle` | search box: the filter toggle, with the number of selected filter values (no number at 0; digit grouping) | `(0)` → Filters; `(3)` → Filters (3) | `(0)` → 絞り込み; `(3)` → 絞り込み (3) |
|
|
1761
1815
|
|
|
1762
1816
|
### External Source Badge Configuration (`sourceTypeConfigs`)
|
|
1763
1817
|
|
|
@@ -1915,12 +1969,14 @@ A type the closure brings in is published under a name that carries `DailyReport
|
|
|
1915
1969
|
`UseDailyReportSseConnectionOptions`, the ids stream's `DailyReportIdsStreamState` / `DailyReportIdsStreamPhase` / `DailyReportIdsStreamClientOptions` / `DailyReportIdsStreamItemsChange` (one net change) / `DailyReportIdsStreamItemReplacement` (one value change inside it), and `DailyReportSearchFailure` (why a search failed, as the search box tells it).
|
|
1916
1970
|
- The ids stream: `DailyReportIdsStreamClient` (`resync()`, `has(reportHubId)`, `readItems()` — the list as of the last publish, built when it is read; its `now` option is the monotonic clock of the publish window) / `DailyReportIdsStreamStatus` / `primeDailyReportIdsStreamSession` / `bootstrapDailyReportIdsStreamSession` / `ensureDailyReportIdsStreamSession` / `resyncDailyReportIdsStream`; the route helpers `createDailyReportClientLoader` / `dailyReportShouldRevalidate`.
|
|
1917
1971
|
- Configuration and wording: `defaultDailyReportClientConfig` / `resolveDailyReportLabels` / `DAILY_REPORT_LABEL_CATALOGS` / `DAILY_REPORT_LABEL_KEYS` / `resolveSourceTypeConfig`.
|
|
1972
|
+
- The search's filter controls: `DAILY_REPORT_SEARCH_FILTER_FACETS` with its type `DailyReportSearchFilterFacet` (the filters in the filter row's order, `author`, `sourceType` and `customer`), and the contract of the control a host renders in each filter cell: `DailyReportSearchFilterSelectRenderer` (what `config.search.renderFilterSelect` is) /
|
|
1973
|
+
`DailyReportSearchFilterSelectProps` (what it receives) / `DailyReportSearchFilterOption` (one candidate, `{ value, label }`).
|
|
1918
1974
|
- Layout: `DAILY_REPORT_LATTICE_BLOCK_QUANTUM_CLASS_NAME`, the hook that carries the block quantum q (`--aqdr-lattice-block`, 4 px or 8 px by ratio) on the document root, which a host's bars round up to, and `DAILY_REPORT_LAYOUT_LATTICE_PX`, the 4 px unit u that q is built from (see **G-symmetric frame**); they are the only public layout values. The row geometry is not public: it follows the host's root font size (see **List slot P**), so rows are located by `[data-daily-report-row]` or the test handle.
|
|
1919
1975
|
- Types: `DailyReportClientConfigInput` / `DailyReportClientConfigDefaults` / `DailyReportClientConfig` (the resolved configuration `useDailyReportConfig` returns) / `DailyReportHeaderProps` (what `renderHeader` receives) / `DailyReportSearchSettingInput` / `DailyReportSearchSetting` (`config.search` as passed and as resolved) / `SourceTypeConfig` / `DailyReportLocale` / `DailyReportLabels` / `DailyReportLabelOverrides` / `DailyReportOperation` / `DailyReportErrorInfo` / `DailyReportSseConnectionStatus`,
|
|
1920
1976
|
and the end-to-end test handle's contract `DailyReportViewTestHandle` / `DailyReportViewTestReadHandle` / `DailyReportRevealOptions` / `DailyReportListNavigationHandle` (the read-only getters of the views' `VirtualScroll` handle that `DailyReportViewTestReadHandle` builds on) (see [Test hooks](#test-hooks)).
|
|
1921
1977
|
- **server** (`@aiquants/daily-report/server`, Node.js):
|
|
1922
1978
|
- Factories: `createDailyReportServer` / `createDailyReportService` (`getDailyReportIdsByExternalId` returns `{ items, streamAnchor, snapshot }`; `readSseStreamAnchor`; `attachmentDelivery`, `null` without the `attachments` block; `titleMaxLength`;
|
|
1923
|
-
and for the search `searchLimits`, `null` without the `search` block, `searchDailyReports
|
|
1979
|
+
and for the search `searchLimits`, `null` without the `search` block, `searchFilterSourceTypes` (the source types a filter may name), `searchDailyReports` (it takes `DailyReportSearchCriteria`), `getDailyReportDetailsByIdsByExternalId`, `markDailyReportSearchHits` (it takes the request's `{ signal }` and throws its reason at its next check after an abort), `listDailyReportSearchFacets`, `reconcileSearchIndex`) / `createDailyReportHandlers` / `defineDailyReportSchema` / `defineDailyReportSearchSchema` / `createEpochStore`, the viewer scope `DailyReportViewerScope`.
|
|
1924
1980
|
Their types: `DailyReportService` (what `createDailyReportService` returns and `DailyReportHandlersConfig.service` takes), `DailyReportEpochStore`, `DailyReportVisibleSourceTypeSet` (a viewer scope's normalized set), `DailyReportResourceRouteArgs` (the arguments of a resource route's loader or action that the handlers read), `DailyReportSseReaderConfig`, and `DailyReportSqlResultCacheQueryOptions` with `DailyReportSqlResultCacheMissAdmission` (the admission a read passes for the fetch its miss starts).
|
|
1925
1981
|
- Attachments: `createSharpThumbnailRenderer` / `predictSharpThumbnailWorkingSetBytes` / `DAILY_REPORT_SHARP_THUMBNAIL_WORKING_SET_MODEL` / `DAILY_REPORT_SHARP_THUMBNAIL_DECODE_BUDGET_BYTES` / `DAILY_REPORT_SHARP_THUMBNAIL_DECODE_WORK_MODEL` (the render deadline `renderMs` and the decode-work and walk bounds, for a host's conformance checks) / `DAILY_REPORT_ATTACHMENT_THUMBNAIL_OUTPUT_MEDIA_TYPES` / `DAILY_REPORT_ATTACHMENT_THUMBNAIL_MAX_OUTPUT_BYTES`.
|
|
1926
1982
|
- SSE and caching: `DailyReportSseReader` / `readDailyReportSseStreamEntry(raw, logger)` (reads one Redis stream entry, `{ id, message }` as the client returns it, once: `{ entry, json }`, the frozen `StreamEntry` the shared reader hands every subscriber and the entry's parsed JSON value; for a host whose own reader keeps the same contract) /
|
|
@@ -1933,6 +1989,7 @@ A type the closure brings in is published under a name that carries `DailyReport
|
|
|
1933
1989
|
for attachments `DailyReportAttachmentsConfig` / `DailyReportAttachmentThumbnailsConfig` (the `attachments` block and its `thumbnails`) / `DailyReportAttachmentDelivery` / `DailyReportAttachmentThumbnailDelivery` (the delivery's resolved thumbnails) / `DailyReportReadAttachment` / `DailyReportAttachmentBytes` (a successful read) / `DailyReportAttachmentError` / `DailyReportAttachmentFailure` / `DailyReportAttachmentThumbnailRenderer` /
|
|
1934
1990
|
`DailyReportAttachmentThumbnail` / `DailyReportAttachmentThumbnailError` / `DailyReportAttachmentThumbnailFailure` (the renderer's failure and its kinds) / `DailyReportAttachmentThumbnailSourceFormat` / `DailyReportAttachmentThumbnailOutputMediaType` / `SharpLike` / `DailyReportSharpPipelineLike` / `DailyReportSharpHeaderLike` (the parts of sharp the renderer reads),
|
|
1935
1991
|
and for the text search `DailyReportSearchConfig` (the `search` block) / `DailyReportSearchSourceTextReader` / `DailyReportSearchTables` with its tables `DailyReportSearchDocumentTable` / `DailyReportSearchCommentTable` / `DailyReportSearchSqlExecutor` (drizzle's `execute`) / `DailyReportSearchReconcileOptions` / `DailyReportSearchReconcileProgress` / `DailyReportSearchReconcileSummary` / `DailyReportSearchPlan`
|
|
1936
|
-
(a compiled query: `DailyReportSearchSet` keywords of `DailyReportSearchWord` words, each with its `DailyReportSearchAlternative` alternatives `{ text, tier }` and their `DailyReportSearchAlternativeTier`, 0 for the typed word's forms and 1 for a romaji reading)
|
|
1992
|
+
(a compiled query: `DailyReportSearchSet` keywords of `DailyReportSearchWord` words, each with its `DailyReportSearchAlternative` alternatives `{ text, tier }` and their `DailyReportSearchAlternativeTier`, 0 for the typed word's forms and 1 for a romaji reading),
|
|
1993
|
+
`DailyReportSearchCriteria` (what `searchDailyReports` takes: `{ plan, filters }`, the plan `null` for a filters-only search) / `DailyReportSearchFilters` (the parsed filters, each `null` when unset: the source types as a lower-case set, the authors and customers as `DailyReportSearchNameFilter`, a non-empty tuple) / `DailyReportSearchFacets` (the answer of `search-facets`).
|
|
1937
1994
|
|
|
1938
1995
|
MIT
|
package/dist/client.d.mts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as react from "react";
|
|
2
2
|
import { RefObject, ReactNode, Context } from "react";
|
|
3
|
-
import { D as DailyReportItem, a as DailyReportUser, b as DailyReportSseTerminalEvent, c as DailyReportSseMessage, d as DailyReportDetail } from "./sse-schema-
|
|
3
|
+
import { D as DailyReportItem, a as DailyReportUser, b as DailyReportSseTerminalEvent, c as DailyReportSseMessage, d as DailyReportDetail } from "./sse-schema-byewDmZK.mjs";
|
|
4
4
|
import { VirtualScrollLocale, VirtualScrollLabels, VirtualScrollHandle } from "@aiquants/virtualscroll";
|
|
5
|
-
import { D as DailyReportActionIntent, a as DailyReportSseStreamAnchor, U as UIComment } from "./comment-adapter-
|
|
5
|
+
import { D as DailyReportActionIntent, a as DailyReportSseStreamAnchor, U as UIComment } from "./comment-adapter-BsbOiHmX.mjs";
|
|
6
|
+
import { D as DailyReportSearchFilterFacet } from "./search-filters-wire-5MWi6GWu.mjs";
|
|
7
|
+
export { a as DAILY_REPORT_SEARCH_FILTER_FACETS } from "./search-filters-wire-5MWi6GWu.mjs";
|
|
6
8
|
import { UseReopeningEventSourceResult } from "@aiquants/sse/react";
|
|
7
9
|
import { ShouldRevalidateFunction } from "react-router";
|
|
8
10
|
import "zod";
|
|
@@ -130,6 +132,10 @@ type DailyReportLabels = VirtualScrollLabels & {
|
|
|
130
132
|
readonly searchMatchOutsideView: string;
|
|
131
133
|
readonly searchMatchOutsideViewDescription: string;
|
|
132
134
|
readonly searchReadingMarkDescription: string;
|
|
135
|
+
readonly searchFilterSourceType: string;
|
|
136
|
+
readonly searchFiltersLoading: string;
|
|
137
|
+
readonly searchFiltersTooMany: string;
|
|
138
|
+
readonly searchFilterUnavailable: string;
|
|
133
139
|
readonly totalCount: (count: number) => string;
|
|
134
140
|
readonly debounce: (milliseconds: number) => string;
|
|
135
141
|
readonly streamProgressCount: (loaded: number) => string;
|
|
@@ -150,6 +156,8 @@ type DailyReportLabels = VirtualScrollLabels & {
|
|
|
150
156
|
readonly searchFailed: (failure: DailyReportSearchFailure) => string;
|
|
151
157
|
readonly searchMatchedKeywords: (matched: number, total: number) => string;
|
|
152
158
|
readonly searchMatchedKeywordsDescription: (matched: number, total: number) => string;
|
|
159
|
+
readonly searchFiltersToggle: (count: number) => string;
|
|
160
|
+
readonly searchFiltersFailed: (failure: Exclude<DailyReportSearchFailure, "invalid">) => string;
|
|
153
161
|
};
|
|
154
162
|
type DailyReportLabelOverrides = {
|
|
155
163
|
readonly [K in keyof DailyReportLabels]?: DailyReportLabels[K];
|
|
@@ -251,6 +259,10 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
|
|
|
251
259
|
"searchMatchOutsideView",
|
|
252
260
|
"searchMatchOutsideViewDescription",
|
|
253
261
|
"searchReadingMarkDescription",
|
|
262
|
+
"searchFilterSourceType",
|
|
263
|
+
"searchFiltersLoading",
|
|
264
|
+
"searchFiltersTooMany",
|
|
265
|
+
"searchFilterUnavailable",
|
|
254
266
|
"totalCount",
|
|
255
267
|
"debounce",
|
|
256
268
|
"streamProgressCount",
|
|
@@ -267,10 +279,25 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
|
|
|
267
279
|
"searchHitCount",
|
|
268
280
|
"searchFailed",
|
|
269
281
|
"searchMatchedKeywords",
|
|
270
|
-
"searchMatchedKeywordsDescription"
|
|
282
|
+
"searchMatchedKeywordsDescription",
|
|
283
|
+
"searchFiltersToggle",
|
|
284
|
+
"searchFiltersFailed"
|
|
271
285
|
];
|
|
272
286
|
declare const DAILY_REPORT_LABEL_CATALOGS: Readonly<Record<DailyReportLocale, DailyReportLabels>>;
|
|
273
287
|
declare const resolveDailyReportLabels: (locale: DailyReportLocale | undefined, overrides: DailyReportLabelOverrides | undefined) => DailyReportLabels;
|
|
288
|
+
type DailyReportSearchFilterOption = {
|
|
289
|
+
readonly value: string;
|
|
290
|
+
readonly label: string;
|
|
291
|
+
};
|
|
292
|
+
type DailyReportSearchFilterSelectProps = {
|
|
293
|
+
readonly facet: DailyReportSearchFilterFacet;
|
|
294
|
+
readonly label: string;
|
|
295
|
+
readonly options: readonly DailyReportSearchFilterOption[];
|
|
296
|
+
readonly value: readonly string[];
|
|
297
|
+
readonly onChange: (values: readonly string[]) => void;
|
|
298
|
+
readonly locale: DailyReportLocale;
|
|
299
|
+
};
|
|
300
|
+
type DailyReportSearchFilterSelectRenderer = (props: DailyReportSearchFilterSelectProps) => ReactNode;
|
|
274
301
|
type DailyReportHeaderProps = {
|
|
275
302
|
title: string;
|
|
276
303
|
annotation?: ReactNode;
|
|
@@ -288,9 +315,11 @@ type SourceTypeConfig = {
|
|
|
288
315
|
};
|
|
289
316
|
type DailyReportSearchSetting = {
|
|
290
317
|
readonly debounceMs: number | null;
|
|
318
|
+
readonly renderFilterSelect: DailyReportSearchFilterSelectRenderer | null;
|
|
291
319
|
};
|
|
292
320
|
type DailyReportSearchSettingInput = {
|
|
293
321
|
readonly debounceMs?: number;
|
|
322
|
+
readonly renderFilterSelect?: DailyReportSearchFilterSelectRenderer;
|
|
294
323
|
};
|
|
295
324
|
type DailyReportClientConfig = {
|
|
296
325
|
apiBasePath: string;
|
|
@@ -537,4 +566,4 @@ declare const createDailyReportClientLoader: <TServerData extends Record<string,
|
|
|
537
566
|
};
|
|
538
567
|
declare const LATTICE_BLOCK_QUANTUM_CLASS_NAME = "[--aqdr-lattice-block:4px] [@media(resolution:1.125dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:1.375dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:1.625dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:1.875dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:2.125dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:2.375dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:2.625dppx)]:[--aqdr-lattice-block:8px] [@media(resolution:2.875dppx)]:[--aqdr-lattice-block:8px]";
|
|
539
568
|
declare const DAILY_REPORT_LAYOUT_LATTICE_PX = 4;
|
|
540
|
-
export { DAILY_REPORT_LABEL_CATALOGS, DAILY_REPORT_LABEL_KEYS, LATTICE_BLOCK_QUANTUM_CLASS_NAME as DAILY_REPORT_LATTICE_BLOCK_QUANTUM_CLASS_NAME, DAILY_REPORT_LAYOUT_LATTICE_PX, type ReportBusinessDate as DailyReportActionBusinessDate, type DailyReportActionContextType, type DailyReportActionFunctions, type DailyReportActionLedgers, DailyReportActionProvider, type DailyReportActionState, type DailyReportActions, DailyReportAttachmentIndicator, type DailyReportAttachmentIndicatorProps, type DailyReportClientConfig, type DailyReportClientConfigDefaults, type DailyReportClientConfigInput, DailyReportConfigProvider, DailyReportDetailList, type DailyReportDetailListProps, type DailyReportDetailListScrollRestore, type DailyReportDetailResource, type DailyReportErrorInfo, DailyReportErrorProvider, type DailyReportHeaderProps, DailyReportIdsStreamClient, type DailyReportIdsStreamClientOptions, type DailyReportIdsStreamItemReplacement, type DailyReportIdsStreamItemsChange, type DailyReportIdsStreamPhase, type DailyReportIdsStreamState, DailyReportIdsStreamStatus, type DailyReportLabelOverrides, type DailyReportLabels, type DailyReportLedgerView, DailyReportList, type ListNavigationHandle as DailyReportListNavigationHandle, type DailyReportListProps, type DailyReportLocale, type DailyReportOperation, DailyReportPage, type DailyReportPageProps, DailyReportResolvedContent, type DailyReportResolvedContentProps, type ListScrollToIndexOptions as DailyReportRevealOptions, type DailyReportSearchFailure, type DailyReportSearchSetting, type DailyReportSearchSettingInput, type DailyReportSseConnectionStatus, type ViewTestHandle as DailyReportViewTestHandle, type ViewTestReadHandle as DailyReportViewTestReadHandle, type SourceTypeConfig, type UseDailyReportSseConnectionOptions, bootstrapDailyReportIdsStreamSession, createDailyReportClientLoader, dailyReportShouldRevalidate, defaultDailyReportClientConfig, ensureDailyReportIdsStreamSession, primeDailyReportIdsStreamSession, resolveDailyReportLabels, resolveSourceTypeConfig, resyncDailyReportIdsStream, useDailyReportActionContext, useDailyReportComments, useDailyReportConfig, useDailyReportDetail, useDailyReportIdsStream, useDailyReportPrefetch, useDailyReportSseConnection };
|
|
569
|
+
export { DAILY_REPORT_LABEL_CATALOGS, DAILY_REPORT_LABEL_KEYS, LATTICE_BLOCK_QUANTUM_CLASS_NAME as DAILY_REPORT_LATTICE_BLOCK_QUANTUM_CLASS_NAME, DAILY_REPORT_LAYOUT_LATTICE_PX, type ReportBusinessDate as DailyReportActionBusinessDate, type DailyReportActionContextType, type DailyReportActionFunctions, type DailyReportActionLedgers, DailyReportActionProvider, type DailyReportActionState, type DailyReportActions, DailyReportAttachmentIndicator, type DailyReportAttachmentIndicatorProps, type DailyReportClientConfig, type DailyReportClientConfigDefaults, type DailyReportClientConfigInput, DailyReportConfigProvider, DailyReportDetailList, type DailyReportDetailListProps, type DailyReportDetailListScrollRestore, type DailyReportDetailResource, type DailyReportErrorInfo, DailyReportErrorProvider, type DailyReportHeaderProps, DailyReportIdsStreamClient, type DailyReportIdsStreamClientOptions, type DailyReportIdsStreamItemReplacement, type DailyReportIdsStreamItemsChange, type DailyReportIdsStreamPhase, type DailyReportIdsStreamState, DailyReportIdsStreamStatus, type DailyReportLabelOverrides, type DailyReportLabels, type DailyReportLedgerView, DailyReportList, type ListNavigationHandle as DailyReportListNavigationHandle, type DailyReportListProps, type DailyReportLocale, type DailyReportOperation, DailyReportPage, type DailyReportPageProps, DailyReportResolvedContent, type DailyReportResolvedContentProps, type ListScrollToIndexOptions as DailyReportRevealOptions, type DailyReportSearchFailure, DailyReportSearchFilterFacet, type DailyReportSearchFilterOption, type DailyReportSearchFilterSelectProps, type DailyReportSearchFilterSelectRenderer, type DailyReportSearchSetting, type DailyReportSearchSettingInput, type DailyReportSseConnectionStatus, type ViewTestHandle as DailyReportViewTestHandle, type ViewTestReadHandle as DailyReportViewTestReadHandle, type SourceTypeConfig, type UseDailyReportSseConnectionOptions, bootstrapDailyReportIdsStreamSession, createDailyReportClientLoader, dailyReportShouldRevalidate, defaultDailyReportClientConfig, ensureDailyReportIdsStreamSession, primeDailyReportIdsStreamSession, resolveDailyReportLabels, resolveSourceTypeConfig, resyncDailyReportIdsStream, useDailyReportActionContext, useDailyReportComments, useDailyReportConfig, useDailyReportDetail, useDailyReportIdsStream, useDailyReportPrefetch, useDailyReportSseConnection };
|