@aiquants/daily-report 0.44.0 → 0.45.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 +39 -1
- package/README.md +108 -62
- package/dist/client.d.mts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +5 -5
- package/dist/client.mjs +4 -4
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/package.json +4 -3
- /package/dist/{sse-schema-BcjRPJjN.d.ts → sse-schema-BmhpWHVK.d.ts} +0 -0
- /package/dist/{sse-schema-sKBorzg9.d.mts → sse-schema-CM_YM7xU.d.mts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@aiquants/daily-report` are documented here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.45.0 (2026-10-16)
|
|
6
|
+
|
|
7
|
+
版番号は 0.45.0 (minor) とする: ピアの `@aiquants/swipe-overlay` の下限を上げる (下の Breaking)。
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **`dailyReportShouldRevalidate` は `@aiquants/route-revalidation` で組む** (依存に加えた): 決め方は変わらない。パッケージ自身が結果を当てはめる操作の送信と、送信でない URL の変わらない再検証では
|
|
12
|
+
ローダーを走らせず、URL の変わる遷移 (ハッシュを外すものを含む) とほかの送信 (`clearCache` と操作の無い送信) は React Router の判断を保つ。型は `@aiquants/route-revalidation` の
|
|
13
|
+
`RouteShouldRevalidate` で、React Router の `ShouldRevalidateFunctionArgs` をそのまま受け取る (`ShouldRevalidateFunction` へ代入できる)。
|
|
14
|
+
- **Escape の IME の変換の見送りは `@aiquants/swipe-overlay` 1.2.6 の受け口に任せる**: 1.2.6 の受け口は IME の変換を取り消す Escape を見送り、
|
|
15
|
+
オーバーレイの要素の文書 (iframe の文書でも) の Escape で閉じる。モバイルのオーバーレイが自分で積んでいた Escape の登録とダイアログの keydown で閉じる分岐は外し、
|
|
16
|
+
コメントの削除の確認と誤りの知らせが積みへ載せる受け口は、渡された Escape で必ず閉じる (変換の確かめは受け口が持つ)。受け口は文書の捕獲段にあるので、
|
|
17
|
+
ホストが捕獲段で先に既定動作を止めた Escape でも、モバイルのオーバーレイ・削除の確認・知らせは閉じる (0.44.1 までは閉じなかった)。
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
- **ピアの `@aiquants/swipe-overlay` は 1.2.6 以上**: モバイルのオーバーレイ・コメントの削除の確認・誤りの知らせは、IME の変換を取り消す Escape の見送りを
|
|
22
|
+
swipe-overlay 1.2.6 の受け口に任せる (それより古い版では、変換中の Escape でも閉じる)。
|
|
23
|
+
Migration: `@aiquants/swipe-overlay` を 1.2.6 以上へ上げる。
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
- **README を今の振る舞いに合わせた** (コードは変えない): `clearCache` が消すのは自分のプロセスのキャッシュと ID の一覧の epoch だけであること、
|
|
28
|
+
添付の原本と縮小画像の 503 の文面と中断の答えの時機、キー操作の注記 (Page キーの見せる段は両方のビュー・`Shift+Tab`・押しの記録を捨てる時機)、
|
|
29
|
+
文字と面の色のコントラストの値 (7:1)、ラベルの表に無かった検索の 18 キー、検索の索引の表の `indexed_at` に既定値が要ること、
|
|
30
|
+
ID の一覧の速い先頭ページの行数 (30)、テストの口 (`[data-skeleton]`・`daily-report-panel-group`) などを直した。
|
|
31
|
+
|
|
32
|
+
## 0.44.1 (2026-10-16)
|
|
33
|
+
|
|
34
|
+
版番号は 0.44.1 (patch) とする: クラスを 1 つ足すだけで、公開の型と振る舞いは変えない。
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **強制配色の未読の点**: 未読の点は塗りの `currentColor` で描くが、ブラウザーは強制配色でもその色を置き換えないので、点は地の青のまま残って
|
|
39
|
+
いた (強制配色の撮影 60 枚のうち 42 枚が、システム色だけで描く規則を外していた)。操作アイコンのどの状態も、強制配色のシステム色を 1 つ明示する:
|
|
40
|
+
既読の切り替え (チェックと未読の点)・削除・削除の確認待ち・編集は `CanvasText`、スターは今までどおり `CanvasText` と `Highlight`。
|
|
41
|
+
このクラスはスターが既に使っているので、配る CSS は変わらない。
|
|
42
|
+
|
|
43
|
+
## 0.44.0 (2026-10-16)
|
|
6
44
|
|
|
7
45
|
### Added
|
|
8
46
|
|
package/README.md
CHANGED
|
@@ -105,6 +105,9 @@ label catalog reuses its 11 engine keys, the names of the scroll bar and its thu
|
|
|
105
105
|
`DailyReportRevealOptions`, the options of the test handle's `revealIndex`, is `@aiquants/virtualscroll`'s `scrollToIndex` options type, which takes
|
|
106
106
|
`align: "nearest"` since 3.11.0, and DetailList rows stay at the tree's tops and heights after a batch of re-measurements whose deltas cancel only
|
|
107
107
|
because its row memo follows the tree revision (3.11.0).
|
|
108
|
+
The peer floor of `@aiquants/swipe-overlay` is **1.2.6**: install 1.2.6 or later.
|
|
109
|
+
The mobile overlay, the comment delete confirmation and the built-in error notice leave `Escape` to its `Escape` stack, which lets an `Escape` that
|
|
110
|
+
cancels an IME composition pass; the mobile overlay's entry listens on the document of the overlay's element, an iframe's document included (1.2.6).
|
|
108
111
|
`@aiquants/sse` (the SSE wire contract, server response helpers and the reopening client) is a regular **dependency**: it arrives transitively, so
|
|
109
112
|
consumers do not declare it.
|
|
110
113
|
The server entry needs **Node.js 20.3 or later** (`engines.node` `>=20.3.0`): the thumbnail stage deadlines combine the generation's signal with a
|
|
@@ -126,7 +129,7 @@ of the floor, and the row says what those engines draw:
|
|
|
126
129
|
| `:has()` | Chrome 105, Firefox 121, Safari 15.4 | The keyboard-focus outline of a List card whose primary button holds keyboard focus (the card's surface reads its direct-child button). The selection ring, its forced-colours outline and the active row's outline come from the row frame's own `aria-selected` and `data-daily-report-active` and do not use `:has()` | A List card whose primary button holds keyboard focus (after a pointer hand-back, a key on the button) shows no focus outline; the selection ring and the active row's outline still show, and the DetailList is unaffected |
|
|
127
130
|
| Container size queries and container query units (`cqi`) | Chrome 105, Firefox 110, Safari 16 | The attachment grid's column count and its whole-pixel width (both read from the grid's own size container); the tile frame's height and the tile's lattice pad (`100cqi` of the tile root, an inline-size container as wide as its track); the view-mode toolbar's end inset in the single-column List | One attachment column at every width; frames sized by `aspect-ratio` alone and tiles without the pad (below); the toolbar's end edge overhangs the List's scroll bar by 8 px |
|
|
128
131
|
| CSS `round()` | Chrome 125, Firefox 118, Safari 15.4 | The attachment grid's whole-pixel width `calc(round(down, 100% − 16 (n − 1), n px) + 16 (n − 1))` per column count, which makes every track a whole pixel, the tile frame's whole-pixel height `round(nearest, 100cqi * 320 / 480, 1px)` and the tile's lattice pad `round(up, h, var(--aqdr-lattice-block)) − h` under its last line (see [Attachment display](#attachment-display)); the views' column origin on the lattice (see **Origin on the lattice**) | Chrome 121–124, inside the floor, drop those declarations: the grid fills its container with the fluid tracks (W − 16 (n − 1)) / n (every breakpoint keeps its column count, which no `round()` declaration sets), so tile edges can fall between pixels and the names are fitted less than 1 px narrower than the track; the frame's `aspect-ratio: 480 / 320`, always declared beside its height, sizes the frame at the exact fractional 2t / 3, so the frames of one grid can paint one device pixel apart; a tile stays its frame's fractional height + 48 px tall, so the rows below an image grid can start between device pixels; and the column keeps the `mx-auto` centre |
|
|
129
|
-
| CSS `calc-size()` | Chrome 129 | The DetailList row bodies', the error notice's and the view-mode toolbar band's height, their content height rounded up to the block quantum of the device-pixel lattice (`calc-size(auto, round(up, size, var(--aqdr-lattice-block)))`: 4 px, 8 px at the odd multiples of 1/8; see **Row slots on the lattice** and **Origin on the lattice**) | Chrome 121–128, and any engine that does not implement it,
|
|
132
|
+
| CSS `calc-size()` | Chrome 129 | The DetailList row bodies', the error notice's and the view-mode toolbar band's height, their content height rounded up to the block quantum of the device-pixel lattice (`calc-size(auto, round(up, size, var(--aqdr-lattice-block)))`: 4 px, 8 px at the odd multiples of 1/8; see **Row slots on the lattice** and **Origin on the lattice**) | Chrome 121–128, and any engine that does not implement it, find the declaration invalid at computed-value time (it holds a `var()`) and give `height` its initial `auto`: a body keeps its content height, which is on the 4 px lattice at a 16 px root and can leave it at other roots and at the odd multiples of 1/8, so the DetailList rows below can start between device pixels; the toolbar's band keeps its content height, so at the odd multiples of 1/8 the views start half a device pixel off whenever that height is not a multiple of 8 px (the toolbar alone is 32) |
|
|
130
133
|
| `scrollbar-gutter: stable` | Chrome 94, Firefox 97, Safari 18.2 | The side pane's tab panels keep their scroll bar's width whatever their content's height | Safari 17.4–18.1, inside the floor, ignore it; their default scroll bars overlay the content and take no width |
|
|
131
134
|
| `scrollbar-color` | Chrome 121, Firefox 64 (not in Safari 17.4) | The desktop side pane's native scroll bar wears the views' resting thumb and track (**Scroll bars** in [Selection and focus appearance](#selection-and-focus-appearance)); the mobile overlay keeps the browser's own bar | Safari, inside the floor, ignores it and draws its default scroll bar there; the bar's width does not change |
|
|
132
135
|
| `text-wrap: balance` | Chrome 114, Firefox 121, Safari 17.5 | The short labels that can wrap (the side pane's selection prompt, an attachment tile's unavailable label, the search box's failure line) keep their lines about equally long | Safari 17.4, inside the floor, drops the value and wraps them greedily, so a wrapped label can end with a short last line |
|
|
@@ -536,7 +539,8 @@ its payload — with one strict parser each, after authentication and before any
|
|
|
536
539
|
read by one encoder and one strict reader inside the package, so a renamed parameter fails their round trip instead of turning every resume into a
|
|
537
540
|
full replay) and the pattern of the echo id (`DAILY_REPORT_CLIENT_TEMP_ID_PATTERN`).
|
|
538
541
|
|
|
539
|
-
Every answer carries its `intent` (`deleteComment`'s too),
|
|
542
|
+
Every answer carries its `intent` (`deleteComment`'s too) and, but for the development-only `clearCache` (`{"status":"OK","intent":"clearCache"}`),
|
|
543
|
+
the echo id and the report's id as a JSON number, the same id domain every SSE event uses
|
|
540
544
|
(an integer from 1 to 2^53 − 1), beside the intent's own values (the created or published report, the toggled statuses, the posted comment, the
|
|
541
545
|
deleted comment's id, also a JSON number). The server builds each 200 answer from its command alone, so it cannot answer one intent with another's
|
|
542
546
|
shape.
|
|
@@ -581,9 +585,9 @@ its payload — with one strict parser each, after authentication and before any
|
|
|
581
585
|
since sources may call their drafts by different names). `createDailyReportService` copies and freezes the list, and throws
|
|
582
586
|
`[daily-report] draftLabelNames must be one or more non-blank label names; got <value>` for anything but one or more non-blank strings without an
|
|
583
587
|
exact duplicate, an omitted key and `[]` included: the names decide who may see a report, so there is no default name to fall back on.
|
|
584
|
-
A report is a draft while it carries a label of one of these names, and every decision about a draft reads the names inside SQL at the moment
|
|
585
|
-
made (the label table's collation ignores case, width and trailing spaces, so a name is never compared in JavaScript, and the process
|
|
586
|
-
id, which would go stale when a label is re-created):
|
|
588
|
+
A report is a draft while it carries a label of one of these names, and every decision about a draft label reads the names inside SQL at the moment
|
|
589
|
+
it is made (the label table's collation ignores case, width and trailing spaces, so a label's name is never compared in JavaScript, and the process
|
|
590
|
+
keeps no label id, which would go stale when a label is re-created):
|
|
587
591
|
every read shows a draft to its author only (one correlated `NOT EXISTS` over the report-label relation joined to the labels by name, so a report
|
|
588
592
|
with several draft labels still reads as one row), and the audience of a draft's SSE events — its content on `report-create`, `report-update` and
|
|
589
593
|
`report-publish`, and its comments' `comment-add` and `comment-delete` — is its author alone while it is a draft (one indexed one-row read with the
|
|
@@ -592,7 +596,8 @@ its payload — with one strict parser each, after authentication and before any
|
|
|
592
596
|
fails, writes nothing and names the label (a report without it would be visible to every viewer). `publishDailyReport` removes every draft-named
|
|
593
597
|
label of the report, by name and inside its transaction, and addresses its `report-publish` event to the author alone when the report is still a
|
|
594
598
|
draft after the transaction. `getDraftLabelId()` reads the canonical label's id by its name on every call (`null` when no label has that name).
|
|
595
|
-
A report whose creation category (`creationCategory`) is one of the names
|
|
599
|
+
A report whose creation category (`creationCategory`) is one of the names (compared in JavaScript, as an exact string: case, width and trailing
|
|
600
|
+
spaces count) shows the canonical label in its labels, with that label's own id, read by
|
|
596
601
|
name in the same read and only when a report of the read needs it (none when no label has the name); the read rule reads labels alone, so such a
|
|
597
602
|
report without a draft label is not hidden.
|
|
598
603
|
Every report detail the server sends — the `report`, `business-date` and `reports` answers, the action's answers that carry a report and the SSE
|
|
@@ -611,9 +616,10 @@ its payload — with one strict parser each, after authentication and before any
|
|
|
611
616
|
- `loginRedirectPath` — **Required**: the path of the host's sign-in route, where the page loader (`index.loader`) redirects a reader who is not
|
|
612
617
|
signed in. `createDailyReportServer` and `createDailyReportHandlers` stop with a `RangeError` for anything but a non-blank text
|
|
613
618
|
(`[daily-report] loginRedirectPath must be the non-blank path of the host's sign-in route; got <value>`): the package names no host route of its own.
|
|
614
|
-
- `enableDevCacheClear` — Gates the dev-only `POST /action` `intent=clearCache` (flush
|
|
619
|
+
- `enableDevCacheClear` — Gates the dev-only `POST /action` `intent=clearCache` (flush this process's SQL result and user-id caches and, with Redis,
|
|
620
|
+
advance only the epoch of the id lists, so other workers keep their business-date and detail caches). Default `false` → the action answers
|
|
615
621
|
`400` (`Invalid intent`) and never calls `service.clearCache()`. Wire `import.meta.env.DEV` to enable it only in development (any authenticated user
|
|
616
|
-
could otherwise flush
|
|
622
|
+
could otherwise flush these caches without limit).
|
|
617
623
|
- `attachments` — Optional attachment delivery block (`DailyReportAttachmentsConfig`), owned by the service: the id codec and the read port it always
|
|
618
624
|
carries, the size limit and the original route's tuning, and the optional `thumbnails` with the renderer port and its tuning. See
|
|
619
625
|
[Attachments](#attachments).
|
|
@@ -638,7 +644,7 @@ its payload — with one strict parser each, after authentication and before any
|
|
|
638
644
|
| `[DailyReportReads]` | `warn` | the 503 of **Cache misses per viewer** in [The API route](#the-api-route) |
|
|
639
645
|
| `[DailyReportSearch]` | `warn` | the 429 of the `search`, `reports`, `search-marks` and `search-facets` admissions ([Requests](#requests)) |
|
|
640
646
|
| `[DailyReportServer]` | `warn` | `createDailyReportServer` without a `redis` provider |
|
|
641
|
-
| `[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 |
|
|
647
|
+
| `[DailyReportService]` | `info` | the service: SSE publishing (no Redis client, an event over the size bound, a slow or failed publish), a failed read of the stream anchor, the create's `info` trace (`createDailyReport called`, `Starting transaction`, `Hub created` with the inserted hub row, `Internal created`, `Label assigned`) and its `error` (`Error in createDailyReport`), an id list read out of stream order (`ids array is not in stream order; re-sorting …` at `warn`), a report whose own assembly failed and its suppressed repeats (`<read>: report <id> passed the visibility predicate …`, `failed_report_suppressed …`; [Requests](#requests)), the failed presence writes (`markAttachmentMissing failed`, `markAttachmentPresent failed`), the development cache clear |
|
|
642
648
|
| `[DailyReportSseReader]` | `info` | the shared SSE reader's Redis errors |
|
|
643
649
|
| `[DailyReportExternalSource]` | `info` | `transformJsonArray`'s malformed JSON columns of an external source |
|
|
644
650
|
|
|
@@ -769,7 +775,8 @@ createDailyReportServer({
|
|
|
769
775
|
readViewerKey: async (request, { reason }) => (reason === "initial" ? yourAuth.viewerIdOf(request) : yourSession.viewerIdOf(request)),
|
|
770
776
|
resolve: async (request, { reason }) => {
|
|
771
777
|
const grants = await yourAuthz.grantsFor(request, { skipCache: reason !== "initial" })
|
|
772
|
-
|
|
778
|
+
// `Internal` is the package's own reports: every author needs its read, or creating a report answers 403.
|
|
779
|
+
return grants.canReadLegacy ? ["Internal", "legacy"] : ["Internal"]
|
|
773
780
|
},
|
|
774
781
|
}),
|
|
775
782
|
})
|
|
@@ -831,9 +838,9 @@ reason is the audit trail, so `grep -rn "unrestricted:"` in the host lists every
|
|
|
831
838
|
- **How it reaches the service**: the resolved set is wrapped in a `DailyReportViewerScope` and passed as the **first** argument of every gated
|
|
832
839
|
service method, the create and the three author mutations included.
|
|
833
840
|
The class is nominal (it holds a private field), so `{ visible: null }`, `{ ...scope, visible: null }` and `new DailyReportViewerScope(...)` are all
|
|
834
|
-
type errors — the only ways to obtain one are `DailyReportViewerScope.restrictTo(tokens)` — which accepts `readonly string[]`
|
|
835
|
-
handed a nullish value and quietly widen — and
|
|
836
|
-
explicit opt-out, with its reason.
|
|
841
|
+
type errors — the only ways to obtain one are `DailyReportViewerScope.restrictTo(tokens)` — which accepts `readonly string[]` or the per-dimension
|
|
842
|
+
`{ read, comment? }` (`DailyReportVisibilityDimensions`) only, so it cannot be handed a nullish value and quietly widen — and
|
|
843
|
+
`DailyReportViewerScope.unrestricted(auditReason)`, which the handlers call only for the port's explicit opt-out, with its reason.
|
|
837
844
|
Because a bare `null` is not spellable as a visibility value, `grep -rn "DailyReportViewerScope.unrestricted("` enumerates every unfiltered read in
|
|
838
845
|
a codebase, each carrying a written reason.
|
|
839
846
|
- **Failure**: a port that throws — an authorization-store outage, or a viewer who is gone (signed out or disabled) — or answers outside the contract
|
|
@@ -974,7 +981,7 @@ answers 404 (it never falls back to the original).
|
|
|
974
981
|
| `attachments.read` | required in the block | Reads the original bytes (`DailyReportReadAttachment`, **Read port** below). |
|
|
975
982
|
| `attachments.maxBytes` | 32 MiB | Upper bound for an original, for every delivery. Keep it below the read port's wire limit. Integer ≥ 1. |
|
|
976
983
|
| `attachments.rateLimitPerMinute` | 60 | Original downloads per user per minute; a token is spent before authorization. Integer ≥ 1. |
|
|
977
|
-
| `attachments.concurrency` | 4 | Simultaneous original reads. A slot is held while the original is referenced (below: until its body has been handed over, the client cancels it, or the client stops reading for 60 s), and one viewer holds at most half of the slots, rounded up (⌈n / 2⌉: 2 of the default 4). A request that finds no free slot, or whose viewer already holds that share, fails fast with 503 (`reason=concurrency`, `Retry-After: 5`). Integer ≥ 1. |
|
|
984
|
+
| `attachments.concurrency` | 4 | Simultaneous original reads. A slot is held while the original is referenced (below: until its body has been handed over, the client cancels it, or the client stops reading for 60 s), and one viewer holds at most half of the slots, rounded up (⌈n / 2⌉: 2 of the default 4). A request that finds no free slot, or whose viewer already holds that share, fails fast with 503 `Too many concurrent downloads` (`reason=concurrency`, `Retry-After: 5`). Integer ≥ 1. |
|
|
978
985
|
| `attachments.thumbnails.renderer` | required in `thumbnails` | Renders the preview (`DailyReportAttachmentThumbnailRenderer`: `{ id, render }`; the package's sharp implementation is `createSharpThumbnailRenderer`). Validated when the service is created (**Thumbnail renderer port** below). |
|
|
979
986
|
| `attachments.thumbnails.rateLimitPerMinute` | 120 | Thumbnail generations per user per minute, in a bucket separate from originals; revalidations (requests with `If-None-Match`) pay from a second bucket of ten times this value. Both admit before authorization, and an empty bucket answers 429 at once; a revalidation that does not end in a 304 also pays a generation token, right after authorization (see **Rate admission** below). Integer ≥ 1. |
|
|
980
987
|
| `attachments.thumbnails.concurrency` | 2 | Simultaneous thumbnail generations (read + render), with a wait queue. Integer ≥ 1. |
|
|
@@ -1060,7 +1067,7 @@ type DailyReportReadAttachment = (
|
|
|
1060
1067
|
| `invalid_path` | A scheme or a path the port does not accept | 404 | 404 |
|
|
1061
1068
|
| `too_large` | The object is over `maxBytes`; only the object's size may decide it, never a transient refusal | 413 | 404, cached only when the row's recorded size backs it (below) |
|
|
1062
1069
|
| `unavailable` | The storage cannot be reached, a read ended early, or the package's `signal` aborted the read | 502 | 502 |
|
|
1063
|
-
| `busy` | The storage refuses for now (a load limit, such as a per-connection stream limit that refuses at once) | 503 with `Retry-After: 5` | 503 with `Retry-After: 5` |
|
|
1070
|
+
| `busy` | The storage refuses for now (a load limit, such as a per-connection stream limit that refuses at once) | 503 `Attachment storage busy` with `Retry-After: 5` | 503 `Thumbnail temporarily unavailable` with `Retry-After: 5` |
|
|
1064
1071
|
| `deadline` | A storage call passed the port's own deadline (a per-call deadline, a stalled stream) | 504 | 504 |
|
|
1065
1072
|
|
|
1066
1073
|
`busy`, `deadline` and `unavailable` are transient: a thumbnail generation concludes nothing from them, caches nothing and marks nothing missing.
|
|
@@ -1107,7 +1114,8 @@ type DailyReportReadAttachment = (
|
|
|
1107
1114
|
- A read that has already been aborted when it settles is discarded whatever its kind, for an original download and a thumbnail generation alike: 503
|
|
1108
1115
|
(`reason=aborted`) and no missing- or present-object record, so an aborted `unavailable` is never logged as a storage outage (502) and a racing
|
|
1109
1116
|
`not_found` never marks the object missing.
|
|
1110
|
-
The original download logs `503 attachment=<id> viewer=<id> reason=aborted
|
|
1117
|
+
The original download logs `503 attachment=<id> viewer=<id> reason=aborted` and answers `Attachment request aborted` without `Retry-After`; the
|
|
1118
|
+
thumbnail generation also caches nothing and does not render. No
|
|
1111
1119
|
disconnected client receives either 503. An original request that is already aborted when it arrives gets the same 503 without reading.
|
|
1112
1120
|
- A thumbnail read still running at its deadline (`readMs`) is answered with 504 (`reason=read_timeout`) without waiting for it; nothing is cached and
|
|
1113
1121
|
the object is not marked missing.
|
|
@@ -1252,8 +1260,9 @@ type DailyReportAttachmentThumbnailRenderer = {
|
|
|
1252
1260
|
A render still running at its deadline is answered with 502 (`reason=render_timeout`) without waiting for it. A renderer that cannot stop
|
|
1253
1261
|
(in-process libvips) is still valid: the response is bounded by the deadline, but the generation slot stays held until the render settles
|
|
1254
1262
|
(`render_overrun ms=<elapsed>`; a render that has still not settled at twice its budget is logged once as `render_stuck ms=<elapsed>`).
|
|
1255
|
-
What a render has paid for is kept, whoever still waits for it. The generation stops waiting for its render at the deadline (the 502 above) or
|
|
1256
|
-
|
|
1263
|
+
What a render has paid for is kept, whoever still waits for it. The generation stops waiting for its render at the deadline (the 502 above) or, once
|
|
1264
|
+
every request waiting for that content has gone (the abort reaches only the renderer, through `signal`), when the render settles or the deadline
|
|
1265
|
+
passes, whichever comes first (503 `reason=aborted`, which no client receives); when the render then settles with a
|
|
1257
1266
|
thumbnail that passes the output checks below, or with `unsupported`, and the generation's read matched the row's size (**verified**, see **Read
|
|
1258
1267
|
port** above), the outcome is cached before the slot is released.
|
|
1259
1268
|
A result that settles after the generation stopped waiting is classified by the same rule as one that settles in time, so only those conclusions are
|
|
@@ -1595,7 +1604,7 @@ Both deliveries log at fixed levels, with fixed line formats:
|
|
|
1595
1604
|
| --- | --- |
|
|
1596
1605
|
| `info` | 200, 304, the 503 nobody receives (`reason=aborted`), 404 `not_eligible`, and the content-determined 404s (`signature`, `unsupported`, `too_large`; cached when verified, `too_large size=unrecorded` on a row without a recorded size) |
|
|
1597
1606
|
| `warn` | 404 `not_visible` (a sign of token enumeration), the storage 404s (`not_found`, `denied`, `invalid_path`), 413 (`db_size`, and the port's `too_large` on an original), the first 429 of a viewer's window per bucket and the window's `rate_limit_suppressed` / `revalidation_rate_limit_suppressed` line, the busy 503s (`queue`, `concurrency`, the storage's `busy`), every 502, the 504 of a storage read past a deadline (`deadline`, `read_timeout`), port settlements after a deadline (`read_overrun`, `render_overrun`), ports still unsettled at twice their stage budget (`read_stuck`, `render_stuck`) and thumbnail reads that do not match the row's size (`size_mismatch`: another length, or a `too_large` on a row whose size is known) |
|
|
1598
|
-
| `error` | 500 (`port_exception`, `port_contract` including a read over `maxBytes` and an original's `HEAD` that declares a size over it (`code=over_max_bytes`), an original's `GET` read whose declared `size` is not the length of its bytes (`code=size_mismatch`) and a read failure reason outside the union (`code=failure_reason`), the loader's `unexpected`, a route without the `token` parameter among them) and
|
|
1607
|
+
| `error` | 500 (`port_exception`, `port_contract` including a read over `maxBytes` and an original's `HEAD` that declares a size over it (`code=over_max_bytes`), an original's `GET` read whose declared `size` is not the length of its bytes (`code=size_mismatch`) and a read failure reason outside the union (`code=failure_reason`), the loader's `unexpected`, a route without the `token` parameter among them) and a `markAttachmentMissing` / `markAttachmentPresent` call that rejects (`<method> failed attachment=<id> message=<msg>`); a failed database write inside those calls is caught by the service and logged on `[DailyReportService]`, not here |
|
|
1599
1608
|
|
|
1600
1609
|
`error` is left to failures of the server itself, so an alert on `error` does not fire on user traffic or on upstream storage states.
|
|
1601
1610
|
|
|
@@ -1637,8 +1646,10 @@ and without the retry the victim would answer 500 and lose what it wrote.
|
|
|
1637
1646
|
connections). `createDailyReportServer` creates it; `createDailyReportHandlers` takes the reader as `sseReader` typed by the port
|
|
1638
1647
|
`DailyReportSseReaderPort` — `ready()` and `subscribe(onEntry, onError)`, with the contract under **`connected` frame** below — which is the type a
|
|
1639
1648
|
host's own reader implements: a plain object with the two methods is accepted, while the class type, which has private fields, would refuse one.
|
|
1640
|
-
The reader reads each live entry once per process and hands every subscriber the same
|
|
1641
|
-
`{ id, message }
|
|
1649
|
+
The reader reads each live entry once per process and hands every subscriber the same value (`DailyReportSseStreamEntry`:
|
|
1650
|
+
`{ id, message }`, frozen one level deep — the entry and its `message` object; an entry whose `message` is `null` is not frozen, and `parsed` and
|
|
1651
|
+
`published` are not frozen either): `message` is the entry's `data` field parsed as JSON and validated by the stream form
|
|
1652
|
+
`dailyReportSseStreamMessageSchema` (the
|
|
1642
1653
|
only form that declares the server-only fields: the six types that may be addressed — status-update, comment-add, comment-delete, report-create,
|
|
1643
1654
|
report-update and report-publish — carry the recipient's internal id `recipientRawUserId`, and the three that embed a report also the writer's,
|
|
1644
1655
|
`actorRawUserId`) — the validated message (`parsed`) and the JSON value as published (`published`, which only the recipient filter reads) — or
|
|
@@ -1923,8 +1934,11 @@ source types. The filters' candidates (`search-facets`, under **Requests**) are
|
|
|
1923
1934
|
#### The index
|
|
1924
1935
|
|
|
1925
1936
|
Two derived tables per database, `DailyReportSearchDocument` (one row per report: the normalized text, the digest of the raw source, the normalizer
|
|
1926
|
-
version, `row_version` and the two filter values) and `DailyReportSearchComment` (one row per comment, with its author id
|
|
1937
|
+
version, `row_version` and the two filter values) and `DailyReportSearchComment` (one row per searchable comment, with its author id: a comment whose
|
|
1938
|
+
normalized text is empty gets no row, and the ordinals count only the kept comments), their normalized text
|
|
1927
1939
|
`nvarchar(max)` in `Latin1_General_100_BIN2` so that `LIKE` compares it as it is, and both deleted with their report (`ON DELETE CASCADE`).
|
|
1940
|
+
The document's `indexed_at` (`datetime2`, `NOT NULL`) is written by a rewrite (`SYSUTCDATETIME()`) but left out of the insert of a new document, so
|
|
1941
|
+
the host's table must give it a default (such as `DEFAULT (sysutcdatetime())`).
|
|
1928
1942
|
The filter values are two nullable columns of the document, `author_name` and `customer_name`: `nvarchar(200)` in `Latin1_General_100_BIN2`, so that
|
|
1929
1943
|
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).
|
|
1930
1944
|
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
|
|
@@ -1951,6 +1965,8 @@ the comment paths run in a transaction even without the `search` block, which a
|
|
|
1951
1965
|
|
|
1952
1966
|
Writes outside the package (a sync, an import) leave the index stale until
|
|
1953
1967
|
`service.reconcileSearchIndex({ batchSize, hubIds?, signal?, onProgress? })` runs.
|
|
1968
|
+
An aborted `signal` stops it between batches by rejecting with the signal's reason, so an aborted run returns no counters (`onProgress` has had
|
|
1969
|
+
those of each finished batch); a run that ends returns the counters and the elapsed time.
|
|
1954
1970
|
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
|
|
1955
1971
|
save, and rewrites only the reports whose digest or normalizer version differ.
|
|
1956
1972
|
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
|
|
@@ -2178,8 +2194,9 @@ edges; with the default labels the field starts and ends on the 4 px lattice.
|
|
|
2178
2194
|
The reserved label is the generated content of a hidden element, so it is in neither the toggle's accessible name nor its text.
|
|
2179
2195
|
The field's description (`labels.searchSyntaxHelp`, also its tooltip) gives the syntax, says that a word in Latin letters also finds its kana reading
|
|
2180
2196
|
and that a match found through a reading is underlined with dots.
|
|
2181
|
-
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
|
|
2182
|
-
|
|
2197
|
+
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 only the
|
|
2198
|
+
answer holds is left out when the provider marked it removed, while a report the stream holds shows as the stream holds it), and the header shows
|
|
2199
|
+
the hit count. While an answer shows, the rows read their details by id in batches (`reports?ids=`)
|
|
2183
2200
|
instead of by business date, and an answer goes into the per-report cache only, never into a business date's cached list.
|
|
2184
2201
|
Each view leads its rows' inputs: it keeps its rendered rows and 24–40 rows beyond each rendered edge subscribed to the details and the marks, in
|
|
2185
2202
|
chunks of 8 rows (32 on each side after a jump; **Highlights** below), so the rows a held key mounts find their details already loaded.
|
|
@@ -2315,7 +2332,8 @@ While an answer shows, the List cards, the side pane and the DetailList rows mar
|
|
|
2315
2332
|
The sentences are built from what the row's leaves registered, each heading being the field's own label (the header's and the metadata's author are
|
|
2316
2333
|
one `author`, a customer name is `customer`, every interviewer, attachment and comment key its section's heading), so they name only what the view
|
|
2317
2334
|
draws; **Match indicator** below says where each view puts them.
|
|
2318
|
-
- **Arrival**: the screen holds one marks store per
|
|
2335
|
+
- **Arrival**: the screen holds one marks store per answered search (inside the client bundle; a new answer whose text, combine mode and words equal
|
|
2336
|
+
the shown one's keeps the store, its filters aside), only while a view draws an answer that has words: without
|
|
2319
2337
|
`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),
|
|
2320
2338
|
after the release and for a view rendered on its own there is none, and the DOM is the plain one.
|
|
2321
2339
|
A row hands its marks scope the marks of the unit that drew its card: the row reads the report's details and marks through its input source from
|
|
@@ -2383,9 +2401,9 @@ While an answer shows, the List cards, the side pane and the DetailList rows mar
|
|
|
2383
2401
|
#### Match indicator
|
|
2384
2402
|
|
|
2385
2403
|
There is no score: an answer can tell only how many of its keywords a report matched, and only an OR of two or more keywords varies it. While the
|
|
2386
|
-
marks store exists, the side pane's author line (after the source badge) and the end of a DetailList row's header hold a slot in the
|
|
2387
|
-
|
|
2388
|
-
|
|
2404
|
+
marks store exists, the side pane's author line (after the source badge) and the end of a DetailList row's header hold a slot in the quiet text
|
|
2405
|
+
colour (the page's text face at the package's 12 px floor with tabular figures, on a 16 px line; 7.73:1 on the white card and overlay, 9.01:1
|
|
2406
|
+
on the dark card and 8.62:1 on the dark overlay's slate-900), as wide as the longest text the answer can show (an invisible placeholder), so
|
|
2389
2407
|
nothing reflows when the marks arrive.
|
|
2390
2408
|
The side pane's author line wraps, its parts 8 px apart along a line and its wrapped lines 4 px apart (`gap-x-2 gap-y-1`), the label kept whole on one
|
|
2391
2409
|
line; only the gap spaces the parts (the source badge has no margin of its own), so a part that wraps starts at the line's start, and in a pane too
|
|
@@ -2461,7 +2479,8 @@ one definition of a draft (`draftLabelNames` in [DI ports](#di-ports)), so `conf
|
|
|
2461
2479
|
unknown key.
|
|
2462
2480
|
An optimistic report the viewer creates is a draft with no labels until the server's answer brings its labels (the draft label among them); an
|
|
2463
2481
|
optimistic publish clears `isDraft` at once and keeps the labels until the answer, and a failed publish restores `isDraft`.
|
|
2464
|
-
The package never navigates by itself: when the session is gone — the SSE stream ends with `auth-required`, or the ids stream
|
|
2482
|
+
The package never navigates by itself: when the session is gone — the SSE stream ends with `auth-required`, or the ids stream, a `search` request,
|
|
2483
|
+
the filters' candidates (`search-facets`) or the marks (`search-marks`) answer 401 / 403 — it
|
|
2465
2484
|
calls `onSessionExpired()` exactly once per occurrence and stops. A typical host reloads the route (`navigate(0)`) so its own loader redirects to the
|
|
2466
2485
|
login page.
|
|
2467
2486
|
`DailyReportConfigProvider` takes the same required `config`, and every component and hook of the package that reads the configuration needs it above:
|
|
@@ -2471,7 +2490,7 @@ configuration renders silently.
|
|
|
2471
2490
|
`config.headingLevel` is the level of each report's heading: an integer from 2 to 5 (default 3), so that the section headings inside a report, one
|
|
2472
2491
|
level lower, stay within `h6`. Pick the level that continues your page's outline (see
|
|
2473
2492
|
[Document structure](#keyboard-focus-and-selection-list--detaillist)); any other value (`1`, `6`, `3.5`, `"3"`, `null`) throws a `RangeError` at
|
|
2474
|
-
render (`[daily-report] config.headingLevel must be
|
|
2493
|
+
render (`[daily-report] config.headingLevel must be one of 2, 3, 4, 5 (the level of each report's heading); got 6`).
|
|
2475
2494
|
|
|
2476
2495
|
`DailyReportPage` is layout-agnostic (header rendering via `renderHeader` slot, error boundaries/footer control managed by the app). For fine-grained
|
|
2477
2496
|
usage, `DailyReportActionProvider`, `DailyReportResolvedContent`, `useDailyReportDetail`, etc. can be imported individually.
|
|
@@ -2481,7 +2500,9 @@ lead while a search answer is shown, the List's side pane and the hook — so on
|
|
|
2481
2500
|
reader's subscription starts the load and its last reader's leaving stops it (an answer that arrives after that is dropped), and a cache write
|
|
2482
2501
|
reaches the readers of its own report only, once per answer. The hook copies the record into state at default priority. On the first render after
|
|
2483
2502
|
the id changes it already returns that id's starting state: its cached report (one definition, also used by the load: the report cache, expired
|
|
2484
|
-
entries included, then the business date's cached list
|
|
2503
|
+
entries included, then the business date's cached list; when another reader already subscribes to the id, that first render returns its record, and
|
|
2504
|
+
otherwise it reads the report cache alone, so a report only the list holds arrives once the hook subscribes), `isLoading` only when nothing is
|
|
2505
|
+
cached, no error and no refetch yet — never the previous
|
|
2485
2506
|
id's report, error or loading flags. An expired cached report is therefore drawn at once, never preceded by `null`, and refetched behind it. An id of
|
|
2486
2507
|
0 or less fetches nothing and returns no report (0 also returns an `Invalid ID` error). A row and the side pane that show one report share its
|
|
2487
2508
|
record, so they never run two loads of it at once, and readers waiting on one load of a report not shown yet receive its failure once each. A
|
|
@@ -2587,10 +2608,11 @@ package does nothing silently outside its provider.
|
|
|
2587
2608
|
|
|
2588
2609
|
The provider takes no items: it derives its rows from the module-resident ids stream session (below), so establish the session first —
|
|
2589
2610
|
`createDailyReportClientLoader` in the route, or `ensureDailyReportIdsStreamSession({ apiBasePath, userKey })` on mount, as `DailyReportPage` does —
|
|
2590
|
-
and mount one provider per user (`key={userId}`), which also starts the new user with an empty editing store (**Editing** in
|
|
2611
|
+
and mount one provider per user (`key={userId ?? "anonymous"}`), which also starts the new user with an empty editing store (**Editing** in
|
|
2591
2612
|
[Keyboard, focus and selection](#keyboard-focus-and-selection-list--detaillist)).
|
|
2592
2613
|
The provider builds its actions once — the functions, the ledgers of the optimistic state and the viewing user, in one value whose identity changes
|
|
2593
|
-
only
|
|
2614
|
+
only when the `user` prop's object changes (keep `user` referentially stable) — and hands out its state apart: the rows (`items`, a persistent
|
|
2615
|
+
list read by its length, the report at a position (`at`)
|
|
2594
2616
|
and iteration, not an array: **Rows follow the stream by its changes** below), the version that announces a change of the ledgers, and the SSE state
|
|
2595
2617
|
(`isSseEnabled`, `sseStatus`).
|
|
2596
2618
|
Each function calls the implementation of the last committed render, so a function kept from an earlier render does what the current one does.
|
|
@@ -2638,16 +2660,16 @@ the failure count.
|
|
|
2638
2660
|
A line refused on every attempt ends a first scan in `failed` (a revalidation gives up with its error and keeps its data), never in `complete`, so the
|
|
2639
2661
|
completion's pruning never runs after a refused line. A line whose `kind` is not `"chunk"` or `"error"`, a line without `kind` included, is skipped
|
|
2640
2662
|
with a warning as a line of an unknown kind, so the wire can grow by new kinds.
|
|
2641
|
-
The client splits the stream into lines as the chunks arrive, examining each
|
|
2642
|
-
of the line already received), and reads a final line without a newline at the end of the stream; a
|
|
2643
|
-
the attempt like any refused line, and the next attempt resumes from the last accepted line's cursor.
|
|
2663
|
+
The client splits the stream into lines as the chunks arrive, examining each byte once (a chunk of a long line does not re-read the part
|
|
2664
|
+
of the line already received, and a line is decoded once, when it is complete), and reads a final line without a newline at the end of the stream; a
|
|
2665
|
+
line cut short there is not valid JSON, so it ends the attempt like any refused line, and the next attempt resumes from the last accepted line's cursor.
|
|
2644
2666
|
`createDailyReportClientLoader` warms an existing session (`primeDailyReportIdsStreamSession`) and then bootstraps
|
|
2645
2667
|
(`bootstrapDailyReportIdsStreamSession`): when no session exists it is **created at loader time** so the stream fetch runs in parallel with hydration
|
|
2646
2668
|
(the dominant cold-load optimization); when one exists, the obfuscated user id is reconciled (a different user destroys and recreates the session
|
|
2647
2669
|
before render).
|
|
2648
2670
|
If your app overrides `config.apiBasePath`, pass the same value to `createDailyReportClientLoader({ apiBasePath })` — forgetting it costs one
|
|
2649
2671
|
wrong-path request on the very first load, self-healed by the page-mount `ensureDailyReportIdsStreamSession`.
|
|
2650
|
-
`DailyReportPage` renders the list as soon as the first chunk arrives (on cache misses the server races a fast `TOP
|
|
2672
|
+
`DailyReportPage` renders the list as soon as the first chunk arrives (on cache misses the server races a fast `TOP 30` first page against the cached
|
|
2651
2673
|
full query, so first paint does not wait for the full id scan).
|
|
2652
2674
|
|
|
2653
2675
|
- **An items publish re-renders only what it changes, behind the keys.** The package's own components read the session through narrow views
|
|
@@ -2807,7 +2829,7 @@ the resize handle and the side pane lie outside the list.
|
|
|
2807
2829
|
| --- | --- | --- | --- |
|
|
2808
2830
|
| `ArrowUp` / `ArrowDown` | Makes the previous / next report active and selected, and reveals it by the nearer edge | Makes the previous / next report active and selected, and aligns it to the top (the reading start) | Yes, also at the ends (no wrap-around) |
|
|
2809
2831
|
| `Home` / `End` | First / last report, aligned to the top / bottom | First / last report, aligned to the top / bottom (to the top when the last row is taller than the viewport) | Yes |
|
|
2810
|
-
| `PageDown` / `PageUp` | The farthest row that still fits in the viewport together with the active row, revealed by the nearer edge
|
|
2832
|
+
| `PageDown` / `PageUp` | The farthest row that still fits in the viewport together with the active row, revealed by the nearer edge; an active row that reaches past the viewport's bottom (`PageDown`) or top (`PageUp`) is first scrolled by min(overflow, ⌊0.875 × viewport⌋) without changing the selection | The same | Yes |
|
|
2811
2833
|
| `Enter` / `Space` on the composite | Activates the active row like a press of its card's primary button: selects it (on a mobile layout it also opens the detail overlay) | Selects the active row without scrolling | Yes, also without an active row (the page does not scroll) |
|
|
2812
2834
|
| `Enter` / `Space` on a control of the active row | That control's own action | The same | Native |
|
|
2813
2835
|
| `Escape` on a control of the active row, a field or button of an in-row form included | Focus returns to the composite; the active row does not change (below: where `Escape` keeps its own meaning) | The same | Yes |
|
|
@@ -2847,8 +2869,8 @@ the resize handle and the side pane lie outside the list.
|
|
|
2847
2869
|
accumulated. While a stream lasts, the desktop side pane keeps showing the report the stream started from (see **List side pane** below), and rows
|
|
2848
2870
|
get their bodies only in the frames without a key, far rows behind the travel direction after the key is released (see **Rows mount in two
|
|
2849
2871
|
passes** below).
|
|
2850
|
-
- **Ignored keys**: `Alt` / `Ctrl` / `Meta` / `Shift` combinations other than `Ctrl+Home` / `Ctrl+End` alone
|
|
2851
|
-
their browser meaning); an event whose default is already prevented; IME composition (`isComposing` or `keyCode` 229);
|
|
2872
|
+
- **Ignored keys**: `Alt` / `Ctrl` / `Meta` / `Shift` combinations other than `Ctrl+Home` / `Ctrl+End` alone and `Shift+Tab` (so `Ctrl+PageDown`
|
|
2873
|
+
and `Shift+End` keep their browser meaning); an event whose default is already prevented; IME composition (`isComposing` or `keyCode` 229);
|
|
2852
2874
|
keys whose target is neither the view's composite nor a control inside one of its rows (the scroll bar, the side pane, the mobile overlay); the
|
|
2853
2875
|
movement keys and the exits from an input region — a `form` (every control of the in-row edit form and of the comment form, their
|
|
2854
2876
|
buttons included) or an editable element (`input`, `textarea`, `select`, `contenteditable` other than `"false"`, `audio[controls]`,
|
|
@@ -2929,8 +2951,9 @@ the resize handle and the side pane lie outside the list.
|
|
|
2929
2951
|
control before or after the list.
|
|
2930
2952
|
- **A key commits once and forces no layout.** A key move sets the active row, the selection, the scroll and the remembered selection in one
|
|
2931
2953
|
synchronous commit (`flushSync`) inside its keydown: a destination outside the rendered window gets the alignment scroll (`scrollToIndex`) in that
|
|
2932
|
-
same commit, which renders it, and `VirtualScroll` names it in the same render.
|
|
2933
|
-
|
|
2954
|
+
same commit, which renders it, and `VirtualScroll` names it in the same render. A movement key on the composite calls no `focus()`
|
|
2955
|
+
(DOM focus is already there; one pressed from a row's control hands focus back to the composite once, and Escape and the `Ctrl+Home` / `Ctrl+End`
|
|
2956
|
+
exits move focus by design) and reads no layout (the exits and Tab's reveal do), so its task forces no style recalculation and no layout of its own.
|
|
2934
2957
|
The commit renders the two row frames whose state changed (the old and the new active row, and the old and the new selection), `VirtualScroll` once
|
|
2935
2958
|
(the active index and, when the key scrolled, the new window) through the view's `VirtualScroll` wrapper, and the host component that owns the
|
|
2936
2959
|
controlled selection (the List's `selectedReportHubId`, the DetailList's `selectedItemId`); the view itself, report rows, card bodies and the row
|
|
@@ -3046,7 +3069,8 @@ the resize handle and the side pane lie outside the list.
|
|
|
3046
3069
|
| The active report leaves the list (a delete, an SSE removal, a stale removal, a search answer that replaces the list) | The report now at its index, clamped to the new length; none in an empty list |
|
|
3047
3070
|
| The selected report is deleted from the side pane or the mobile overlay | The report now at the deleted report's index |
|
|
3048
3071
|
|
|
3049
|
-
The row under a press is recorded at the press (mouse and pen) and dropped
|
|
3072
|
+
The row under a press is recorded at the press (mouse and pen) and dropped when the press ends (`pointerup` / `pointercancel` on the document), by a
|
|
3073
|
+
press outside the view, by every `focusin` in the view and by every handled key, so a press whose
|
|
3050
3074
|
focus never entered the composite — a List card's primary button takes the click's focus — cannot decide a later entry: a far key after a card
|
|
3051
3075
|
click moves from the clicked row and stays on the key's destination.
|
|
3052
3076
|
A press in the List's row gutter moves no focus, so it changes nothing; a touch scroll fires no click and no focus, so it changes nothing either.
|
|
@@ -3400,7 +3424,8 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3400
3424
|
error — rounds its content height up to q (`height: calc-size(auto, round(up, size, var(--aqdr-lattice-block)))`), so rem line boxes at a root other
|
|
3401
3425
|
than 16 px (a 25 px line at a 20 px root) and a body of an odd multiple of 4 px at an odd eighth leave the next row on whole device pixels; at a 16
|
|
3402
3426
|
px root and a quarter ratio the content is already on the lattice.
|
|
3403
|
-
Engines without `calc-size()`
|
|
3427
|
+
Engines without `calc-size()` accept the declaration when parsing it (it holds a `var()`), find it invalid at computed-value time and give
|
|
3428
|
+
`height` its initial `auto`, the content height (see the browser floor table).
|
|
3404
3429
|
Measured in Chromium 148 at 412 × 915 and 2.625: with bodies rounded to 4 px, a DetailList row of 980 px ended at 6,478.5 device pixels and the
|
|
3405
3430
|
surface edge of the next row blended into two rows of device pixels; rounded to q, the revealed row's surface top sits at device pixel 273 in every
|
|
3406
3431
|
reveal.
|
|
@@ -3445,7 +3470,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3445
3470
|
on it.
|
|
3446
3471
|
- **A key press restyles only what paints the change**: every selector that depends on another element's state ends in the styled element's own class
|
|
3447
3472
|
or attribute, and `:has()` sits only on the styled element itself. A featureless subject (`*:`, `group-*`) or an ancestor's `:has(:focus-visible)`
|
|
3448
|
-
would make the browser restyle whole rows or the whole view on each key; `src/client/ui/tailwind-selector-scope.spec.ts` compiles the package's
|
|
3473
|
+
would make the browser restyle whole rows or the whole view on each key; `src/client/ui/tailwind-selector-scope.guard.spec.ts` compiles the package's
|
|
3449
3474
|
classes and fails on either form.
|
|
3450
3475
|
- **Every row is a relayout boundary**: the row frames of both views are `contain: size layout style` (one token), so a change inside one row — a held
|
|
3451
3476
|
body rendered after the key's commit, a load that completes, the selection and focus indicators — lays out that row alone and restyles no other.
|
|
@@ -3521,7 +3546,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3521
3546
|
`--primary`, `--background` or `--muted`.
|
|
3522
3547
|
The switch track is slate-500 when off and blue-600 when on (dark: slate-400 / blue-400) under a white thumb (dark: slate-950). The selected tab is
|
|
3523
3548
|
a white surface (dark: slate-950) with slate-900 text (dark: slate-100) and a straight 2 px blue-600 bar (dark: blue-400) along the straight part of
|
|
3524
|
-
its bottom edge, the bar being the cue that does not depend on colour. Unselected labels are
|
|
3549
|
+
its bottom edge, the bar being the cue that does not depend on colour. Unselected labels are in the quiet text colour (below).
|
|
3525
3550
|
The bar is an `::after` box at the tab's bottom edge, inset on each side by the tab's own corner radius (`--radius-lg`), so it never runs into the
|
|
3526
3551
|
rounded corners: it is 2 px thick along its whole length at every device pixel ratio (a bottom border on a rounded box thins and rises along both
|
|
3527
3552
|
corners), and it stays on the straight part for any host `--radius`. It is out of the flow and takes no space, so selecting a tab moves nothing and
|
|
@@ -3529,7 +3554,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3529
3554
|
In forced colours the thumb is `ButtonText`, the track has a `ButtonText` border and the on track a `Highlight` fill, and the selected bar is
|
|
3530
3555
|
`Highlight`, painted as declared (`forced-color-adjust: none` on the bar), so the state survives the colour override.
|
|
3531
3556
|
The package's error messages — the status panel's load-error message and its badge in the host's header, and the side pane's report-load error —
|
|
3532
|
-
take their colour from one token (red-
|
|
3557
|
+
take their colour from one token (red-800, dark red-300); the badge sits on the host's header, whose colour is the host's, so it has no pair below.
|
|
3533
3558
|
`src/client/ui/ui-state-contrast.spec.ts` computes every pair below from the compiled CSS and the palette (the toolbar surface is slate-100 at 60 %
|
|
3534
3559
|
over the slate-50 page, dark slate-900 at 60 % over slate-950; the card surface is white, dark slate-900 at 70 % over the page; the overlay panel is
|
|
3535
3560
|
white, dark slate-900):
|
|
@@ -3543,12 +3568,13 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3543
3568
|
| Selected tab bar / selected tab | 5.25 | 7.64 | 3 |
|
|
3544
3569
|
| Selected tab bar / the side pane's tab track | 4.79 | 5.54 | 3 |
|
|
3545
3570
|
| Selected tab bar / toolbar surface | 4.88 | 7.16 | 3 |
|
|
3546
|
-
| Unselected label / the side pane's tab track |
|
|
3547
|
-
| Unselected label / toolbar surface | 7.
|
|
3548
|
-
| Selected label / selected tab | 17.83 | 18.40 |
|
|
3549
|
-
|
|
|
3550
|
-
| Error text /
|
|
3551
|
-
| Error text /
|
|
3571
|
+
| Unselected label / the side pane's tab track | 7.06 | 7.07 | 7 |
|
|
3572
|
+
| Unselected label / toolbar surface | 7.19 | 9.13 | 7 |
|
|
3573
|
+
| Selected label / selected tab | 17.83 | 18.40 | 7 |
|
|
3574
|
+
| Empty view's message / page surface | 7.39 | 9.75 | 7 |
|
|
3575
|
+
| Error text / card surface (the desktop side pane) | 8.36 | 9.70 | 7 |
|
|
3576
|
+
| Error text / overlay panel (the side pane's content on a phone) | 8.36 | 9.29 | 7 |
|
|
3577
|
+
| Error text / the load-error panel (the card surface over the page frame) | 8.36 | 9.70 | 7 |
|
|
3552
3578
|
|
|
3553
3579
|
- **Text fields**: the comment input, the edit form's `Input` and `Textarea` and the search field, and the outline of the search band's toggles in
|
|
3554
3580
|
their field face (the OR toggle and the closed filter toggle), take their boundary and placeholder from one token: a 1 px slate-500 border in both
|
|
@@ -3576,13 +3602,13 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3576
3602
|
so the content edge is 16 px from the visible edge and its corner concentric with the surface's) and `shadow-sm`.
|
|
3577
3603
|
Two more surfaces have one token each, and no other place writes their classes: the placeholder panel (the side pane's selection prompt and its
|
|
3578
3604
|
loading and empty tabs; a dashed 1 px border, the 12 px corner of an inner panel and the 12 px inset of every such panel (1 + 11), slate-50 / dark
|
|
3579
|
-
slate-900 at 40 %,
|
|
3605
|
+
slate-900 at 40 %, text in the quiet text colour) and the reading panel (a report's content in the side pane and in the DetailList card;
|
|
3580
3606
|
a 12 px corner and a concentric 12 px inset, slate-50 / dark slate-800 at 60 %, slate-700 / dark slate-200 text on 24 px lines;
|
|
3581
3607
|
in the side pane it reserves three reading lines while its report loads, `min-h-24`, the inset twice and three line boxes of its own `p-3` and
|
|
3582
3608
|
`leading-6`: 12 + 3 × 24 + 12 = 96 px at the 16 px root, and its skeleton is three 24 px line boxes, each centring a 16 px bar on the text line it
|
|
3583
3609
|
stands for, so a report of up to three lines keeps the panel's height when it loads, at every root font size).
|
|
3584
3610
|
The scroll bar's business-day bubble is the bordered pill (24 px tall, its text 12 px in = 1 px border + 11 px padding, the radius of its round
|
|
3585
|
-
ends). `src/client/ui/border-style.spec.ts` checks these rules, a source scan included.
|
|
3611
|
+
ends). `src/client/ui/border-style.guard.spec.ts` checks these rules, a source scan included.
|
|
3586
3612
|
- **Motion**: everything that moves animates only transform or opacity under `motion-safe` — the hover lift, the thumbnail fade, the loading pulse (on
|
|
3587
3613
|
a thumbnail frame's skin, only while the frame may show its load activity, see [Attachment display](#attachment-display)), the switch's thumb, the
|
|
3588
3614
|
entrance of the comment delete confirmation and of the error notice, the mobile overlay's slide and its backdrop.
|
|
@@ -3592,7 +3618,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3592
3618
|
So only four run until their state ends, each only while something loads or reconnects and none behind a measured key hold: the spinning icon of the
|
|
3593
3619
|
stream's retrying and revalidating badges, the List row's loading skeleton, the side pane's loading skeleton (its author and article lines) and the
|
|
3594
3620
|
waiting pulse of a visible thumbnail frame's skin.
|
|
3595
|
-
The progress pill's dot stands still: the count and the percentage beside it change on every publish. `src/client/ui/motion-policy.spec.ts` allows
|
|
3621
|
+
The progress pill's dot stands still: the count and the percentage beside it change on every publish. `src/client/ui/motion-policy.guard.spec.ts` allows
|
|
3596
3622
|
exactly those places, each with its reason and its count of class positions, in the sources and in the shipped CSS (an `--animate-*` value with
|
|
3597
3623
|
`infinite`, or `animation-iteration-count: infinite`), so a new animation that never ends fails until it is listed with a reason.
|
|
3598
3624
|
The one exception is the mobile overlay layer: its right edge follows the host's pinned-chrome offset (`--aqdr-overlay-right`) over 0.5 s, also
|
|
@@ -3628,7 +3654,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3628
3654
|
(the circle and every element inside it, overriding the visual's inline transitions; the bar's circle wrapper; the arrow buttons; the thumb; the
|
|
3629
3655
|
scroll-to-edge buttons and their overlay), so the circle follows a drag without easing and the other parts change state at once.
|
|
3630
3656
|
While the list scrolls (`data-daily-report-scrolling` on the view's scroll container) the hover lift and its shadow are held still.
|
|
3631
|
-
`src/client/ui/motion-policy.spec.ts` checks the shipped CSS, and scans the sources so that no smooth scroll behaviour, no `animate()` call and no
|
|
3657
|
+
`src/client/ui/motion-policy.guard.spec.ts` checks the shipped CSS, and scans the sources so that no smooth scroll behaviour, no `animate()` call and no
|
|
3632
3658
|
inertia option other than one chosen by `inertiaOptionsFor` is written outside `src/client/ui/motion.ts`,
|
|
3633
3659
|
and so that only the files of an allowlist with a written reason ask for an animation frame: the header total's easing, which must read the
|
|
3634
3660
|
reduced-motion preference, and two uses that move nothing (the held key's one move per frame and the attachment grid's track width handed to the
|
|
@@ -3661,7 +3687,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3661
3687
|
px inset makes them concentric with their 8 px tabs (12 − 4 = 8).
|
|
3662
3688
|
The only other exceptions are the visually hidden text (Tailwind's `sr-only`, and inside virtualized rows and for every attachment file name the
|
|
3663
3689
|
package's transparent 1 px box, which clips nothing and contains its own layout) and the 48 px the search field keeps free at its end for the
|
|
3664
|
-
searching indicator and the clear button inside its box. `src/client/ui/spacing-ladder.spec.ts` checks every class the package writes, that every
|
|
3690
|
+
searching indicator and the clear button inside its box. `src/client/ui/spacing-ladder.guard.spec.ts` checks every class the package writes, that every
|
|
3665
3691
|
bordered box's border + padding is on the ladder or equals its corner, and that every 12 px-corner panel's inset is 12.
|
|
3666
3692
|
- **Origin on the lattice**: the views' centred column (at most 72 rem, `max-w-6xl`, centred with `mx-auto`) starts at the centring offset rounded
|
|
3667
3693
|
down to a multiple of the block quantum q (`ml-[max(0px,round(down,calc((100%-72rem)/2),var(--aqdr-lattice-block)))]`, on the column that carries q
|
|
@@ -3991,7 +4017,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
3991
4017
|
frame's own style never changes after mount — its classes are its box alone (`relative block w-full`: no variant, no paint, no animation) and its
|
|
3992
4018
|
inline style is that frame style — and every state is drawn inside it, by the skin and the image, which read the frame's attributes.
|
|
3993
4019
|
- **`data-thumbnail-state`** on the frame is `pending` (waiting or loading), `loaded` (the image fades in) or `unavailable` (an icon and
|
|
3994
|
-
`labels.attachmentThumbnailUnavailable` inside the skin; the icon reaches 4.35:1 / 5.56:1 and the label
|
|
4020
|
+
`labels.attachmentThumbnailUnavailable` inside the skin; the icon reaches 4.35:1 / 5.56:1 and the label 7.06:1 / 7.07:1 in the light / dark theme,
|
|
3995
4021
|
and the label breaks its lines by **Line breaks of wrapping labels** in [Selection and focus appearance](#selection-and-focus-appearance)). The
|
|
3996
4022
|
loader's internal phases are not exposed.
|
|
3997
4023
|
- **The waiting pulse runs only where the tile can be seen**: a `pending` frame's skin pulses (under `prefers-reduced-motion: no-preference`) only
|
|
@@ -4037,7 +4063,8 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
|
|
|
4037
4063
|
unchanged, after authorization); the revalidation waits for no settle time and takes no slot, and at most one runs per mounted frame. If it fails,
|
|
4038
4064
|
the page forgets that the URL loaded, counts one failed attempt, and the frame goes on with the retry wait.
|
|
4039
4065
|
A URL whose attempts are all used up shows `unavailable` at once, without a request, for 60 s after its last failure (one rate-limit window); after
|
|
4040
|
-
that,
|
|
4066
|
+
that, the next frame that starts on the URL forgets them and runs a new series of up to the same number of attempts. A URL between attempts keeps
|
|
4067
|
+
its failure count and its retry time across remounts.
|
|
4041
4068
|
- **One writer of `src`**: React never renders the image's `src`. A layout effect projects it from the frame's state before paint on every commit (the
|
|
4042
4069
|
URL while a load is granted and once it has loaded, none while waiting) and writes the attribute only when it differs, because rewriting the same
|
|
4043
4070
|
value makes the browser drop the loaded image and fetch it again.
|
|
@@ -4077,7 +4104,7 @@ DOM hooks are `data-*` attributes, test ids and ARIA; class names are styling an
|
|
|
4077
4104
|
| `[role="treegrid"]` | Each view's composite: `VirtualScroll`'s content element, the view's one Tab stop and the element that holds DOM focus while the keys move; its `id` prefixes the row ids, `aria-activedescendant` names the active row's `id` (written by `@aiquants/virtualscroll`), `aria-rowcount` is the set's size, and `data-aqvs-active-item` is `visible` or `outside` while there is an active row |
|
|
4078
4105
|
| `[data-daily-report-row="<id>"]` | Row frame of both views (`role="row"` with `id`, `aria-rowindex`, `aria-posinset`, `aria-setsize`, `aria-level` and an explicit `aria-selected`; the set size is the declared total while the ids stream delivers and `-1` before it is declared, see **Screen readers**), in every load state |
|
|
4079
4106
|
| `[data-daily-report-active]` | The active row's frame (the row the composite's `aria-activedescendant` names) |
|
|
4080
|
-
| `[data-daily-report-row-surface]` | Surface of a row frame (`role="gridcell"`): its direct child that paints the selection ring and the focus outline, in every load state (opaque and never animated;
|
|
4107
|
+
| `[data-daily-report-row-surface]` | Surface of a row frame (`role="gridcell"`): its direct child that paints the selection ring and the focus outline, in every load state (opaque and never animated; the List's loading skeleton pulses inside it, the DetailList's does not pulse) |
|
|
4081
4108
|
| `[data-daily-report-row-shell]` | The surface of a shell, the card's surface without content: a row mounted outside the visible range whose body is still to come (**Rows mount in two passes**) |
|
|
4082
4109
|
| `[data-daily-report-card]` | List card surface (present while the card is shown) |
|
|
4083
4110
|
| `button[data-daily-report-button="<id>"]` | List card primary button (the click target that selects, and the pointer hand-back target; never a Tab stop, `tabIndex=-1`) |
|
|
@@ -4099,7 +4126,8 @@ DOM hooks are `data-*` attributes, test ids and ARIA; class names are styling an
|
|
|
4099
4126
|
| `[data-daily-report-stream-status]` | The header's ids-stream badge container (persistent; empty while there is nothing to show) |
|
|
4100
4127
|
| `[data-daily-report-status-panel]` | The status screen's panel (also `data-testid="daily-report-status-panel"`), the last of the page's focus successors |
|
|
4101
4128
|
| `[data-daily-report-view-tabs]` | The view-mode tab list (its selected tab is the focus successor of a list without rows) |
|
|
4102
|
-
| `data-
|
|
4129
|
+
| `[data-skeleton]` | A loading placeholder: the List's skeleton card (the surface of a List row whose report is still loading) and the List side pane's placeholder lines (the DetailList's skeleton carries none) |
|
|
4130
|
+
| `data-testid` | `daily-report-root`, `daily-report-list`, `daily-report-detail-list`, `daily-report-panel-group` (the desktop List's panel group: the list and the side pane), `daily-report-side-pane`, `daily-report-error-banner` (the error notice), `daily-report-status-panel` (the status screen's panel, `data-phase` `loading` / `failed`), `daily-report-load-error-retry`, `daily-report-stream-announcer` (the page's status region, never focusable), `daily-report-ids-stream-status` and `daily-report-ids-stream-retry` (the badge's content and its retry button), `daily-report-ids-stream-status-announcer` (the badge's own live regions, rendered only while it announces its phases), and the attachment ids above |
|
|
4103
4131
|
|
|
4104
4132
|
Only the package writes `data-daily-report-keyboard-focus`, `data-daily-report-scrolling`, `data-daily-report-active`, `data-daily-report-row-shell` and `data-daily-report-row-focus`. Hosts may select on them — in tests, or in CSS to adapt
|
|
4105
4133
|
their own content inside a view — but never set them. When the pane content matters, wait for `data-displayed-report-id`, not `data-report-id`: during
|
|
@@ -4305,6 +4333,19 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
|
|
|
4305
4333
|
| `searchFiltersLoading` | search filter row: the status line while the candidates load | Loading filters... | 絞り込みの候補を読み込み中... |
|
|
4306
4334
|
| `searchFiltersTooMany` | search filter row: the status line after a selection over the filter limits was refused | No more filter values can be selected | これ以上は絞り込みの値を選べません |
|
|
4307
4335
|
| `searchFilterUnavailable` | search filter row: a cell whose host control threw while it rendered | This filter cannot be shown | この絞り込みを表示できません |
|
|
4336
|
+
| `searchInputLabel` | search box: the text field's name (`aria-label`) | Search reports | 日報を検索 |
|
|
4337
|
+
| `searchPlaceholder` | search box: the text field's placeholder | Search | 検索 |
|
|
4338
|
+
| `searchCombineAnd` | search box: the combine toggle's label while it asks for every keyword | AND | AND |
|
|
4339
|
+
| `searchCombineOr` | search box: the combine toggle's label while it asks for any keyword | OR | OR |
|
|
4340
|
+
| `searchCombineAndTitle` | search box: the combine toggle's tooltip (`title`) and description in the AND mode | Show the reports that contain every keyword | すべてのキーワードを含む日報を出す |
|
|
4341
|
+
| `searchCombineOrTitle` | search box: the same in the OR mode | Show the reports that contain any keyword | どれかのキーワードを含む日報を出す |
|
|
4342
|
+
| `searchClear` | search box: the clear button's name (`aria-label`) and tooltip | Clear the search | 検索を消す |
|
|
4343
|
+
| `searching` | search box: the status while a search request runs | Searching... | 検索中... |
|
|
4344
|
+
| `searchNoHits` | search box: the status of an answer without a hit | No report matches | 当たる日報はありません |
|
|
4345
|
+
| `searchSyntaxHelp` | search box: the text field's description and tooltip (`title`) | Separate keywords with spaces. Words in double quotes form one keyword that needs every word. A word in Latin letters also finds its kana reading (denpun finds でんぷん); text found through a reading is underlined with dots. Enter searches, Esc clears. | キーワードは空白で区切ります。二重引用符で囲んだ語は、すべての語を含む 1 つのキーワードになります。英字の語はローマ字の読みでも仮名を探します (denpun → でんぷん)。読みで当たった所は点線の下線で示します。Enter で検索、Esc で消去します。 |
|
|
4346
|
+
| `searchMatchOutsideView` | match indicator: the text of a row that matched in fields the view does not draw | Hidden match | 表示外で一致 |
|
|
4347
|
+
| `searchMatchOutsideViewDescription` | match indicator: that text's tooltip (`title`) and its sentence in the row's hidden match description | Matched in fields this view does not show, such as labels or details | この画面に出していない項目 (ラベル・明細など) で一致しました |
|
|
4348
|
+
| `searchReadingMarkDescription` | marked text: the description of a mark found only through a romaji reading | Matched by its romaji reading | ローマ字の読みで一致 |
|
|
4308
4349
|
| `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 件 |
|
|
4309
4350
|
| `debounce` | header annotation | `(300)` → Debounce: 300 ms | `(300)` → デバウンス: 300 ms |
|
|
4310
4351
|
| `streamProgressCount` | stream badge before the total is known | `(1234)` → Loading 1,234 | `(1234)` → 読み込み中 1,234 件 |
|
|
@@ -4322,6 +4363,11 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
|
|
|
4322
4363
|
| `searchFieldMatched` | match sentences of a row (the DetailList row's and the side pane's hidden indicator sentences, the List card's hidden match description): a field heading the row's leaves marked with a typed word's mark (`field` is the heading's resolved label) | `("Author")` → Author matched | `("作成者")` → 作成者で一致 |
|
|
4323
4364
|
| `searchFieldMatchedByReading` | the same, for a heading whose marks matched only through a romaji reading | `("Author")` → Author matched by its romaji reading | `("作成者")` → 作成者がローマ字の読みで一致 |
|
|
4324
4365
|
| `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) |
|
|
4366
|
+
| `searchHitCount` | header annotation: the number of the answer's hits, and the search box's status of an answer with hits (digit grouping) | `(1)` → 1 hit; `(1234)` → 1,234 hits | `(1234)` → 1,234 件ヒット |
|
|
4367
|
+
| `searchFailed` | search box: the status of a failed search (`failure` is `invalid` / `busy` / `unavailable` / `failed`) | `("busy")` → The server is busy. Please search again in a moment | `("busy")` → 混み合っています。少し待ってからもう一度検索してください |
|
|
4368
|
+
| `searchMatchedKeywords` | match indicator: how many of an OR answer's keywords the row matched | `(2, 3)` → Match 2/3 | `(2, 3)` → 一致 2/3 |
|
|
4369
|
+
| `searchMatchedKeywordsDescription` | match indicator: the hidden sentence of the same count | `(2, 3)` → Matches 2 of 3 keywords | `(2, 3)` → キーワード 3 個のうち 2 個に一致 |
|
|
4370
|
+
| `searchFiltersFailed` | search filter row: the status line when the candidates could not be loaded (`failure` is `busy` / `unavailable` / `failed`) | `("unavailable")` → Filter options are not available | `("unavailable")` → 絞り込みの候補を使えません |
|
|
4325
4371
|
|
|
4326
4372
|
### External Source Badge Configuration (`sourceTypeConfigs`)
|
|
4327
4373
|
|