@aiquants/daily-report 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,90 @@
2
2
 
3
3
  All notable changes to `@aiquants/daily-report` are documented here.
4
4
 
5
+ ## Unreleased
6
+
7
+ ### Breaking
8
+
9
+ - **`@aiquants/swipe-overlay` のピアの下限が、`useSwipeOverlayDialog`・`useOverlayPresence`・`SWIPE_OVERLAY_SCROLL_AREA_STYLE` を持つ版へ上がる**。
10
+
11
+ ### Changed
12
+
13
+ - **粗いポインター (`(pointer: coarse)`) では日報のどの操作の的も 44 × 44 CSS px 以上 (`TARGET_SIZE_PX` = 11u。WCAG 2.5.5)**: 操作ボタン (List のカードと DetailList の
14
+ 見出しと側面ペインのスター・既読・編集・削除、コメントのゴミ箱) は 44px の丸い的、ビューのタブと側面ペインのタブ (側面ペインの並びは 4 + 44 + 4 = 52px)・作成のボタン・検索の切り替えと入力欄と
15
+ 消去のボタン・コメントの入力欄と送信・削除の確認・オーバーレイと誤りの知らせの閉じるボタン・2 つの再試行・編集フォームの `Button` と `Input`・添付のダウンロードと行のリンクは
16
+ 44px 以上の箱 (`COARSE_TARGET_CLASS_NAME`。最小の大きさで育つ)。自動既読のスイッチは見た目の 40 × 24 のまま中央の 44 × 44 の当たりを持つ。List のカードは粗いポインターで
17
+ 2 列の格子になり、スターと既読をプレビューの右 (2 行目) へ移すので、行の枠の高さ P は根 16px で 160 のまま (`listRowSlotHeight(R, T)` = 4⌈(52 + 2R + max(4.75R, T)) / 4⌉。T は
18
+ 粗いポインターでだけ高さを持つ隠れた探りから読む)。側面ペインの操作ボタンは件名の下の自分の 44px の行へ移り、添付のタイルの最後の行は 48px (44 を 8 へ切り上げ、タイルは
19
+ 装置の画素の格子に乗ったまま)。操作列の帯の部品 (ビューのタブ・作成のボタン・自動既読のスイッチの行・検索の入力欄と 2 つの切り替え) も同じ 48px の高さ
20
+ (`LATTICE_TARGET_SIZE_PX`。44 のままでは 2.625 の画素密度で部品の下の縁が装置の画素の間に落ちる) で、操作列の並びは 4 + 48 + 4 = 56px (2 行に折り返せば 112px)、
21
+ 検索の行の 1 行目は 8 + 48 = 56px。ビューの幅が 320px 未満なら検索の絞り込みの切り替えは自分の行へ折り返す (境目は粗いポインターの行の最小の幅から求めた値)。細かいポインター
22
+ (マウス・トラックパッド・ペン) では的も配置も変わらず 24px (2.5.8) なので、2.5.5 は粗いポインターでだけ満たし、全体としては主張しない。例外: List のリサイズの取っ手と
23
+ スクロールバー・ホストの絞り込みの選択の部品・開発用の箱の再読み込みとキャッシュの消去 (24 × 24 の的。同じ箱の SSE のスイッチは中央の 44 × 44 の当たりを持ち、
24
+ 粗いポインターでは箱の項目の間を 4 + (44 − 40) / 2 = 6px に広げて、当たりが再読み込みの的に重ならない)・VirtualScroll のタップスクロールの円。オーバーレイの閉じるボタンは
25
+ 粗いポインターで 36 → 44px。Tailwind でクラスを作るホストは、`pointer-coarse:` のユーティリティを Tailwind CSS v4.1 以上で生成する (それより前の v4 では粗いポインターの規則が
26
+ 作られず、的は細かいポインターの大きさのまま)。
27
+ - **入力欄の文字の大きさは画面の幅ではなくポインターで決まる**: 粗いポインターでは 16px (iOS のフォーカス時の拡大を防ぐ)、細かいポインターでは 14px (編集フォームの `Input` と
28
+ `Textarea` の幅で決めていた `md:text-sm` を外した)。検索の入力欄に `enterkeyhint="search"`、コメントの入力欄に `"send"`。オーバーレイの中身は上下左右に 16px と
29
+ `env(safe-area-inset-*)` の大きい方だけ内側へ寄る。
30
+ - **モバイルの詳細オーバーレイのダイアログの部品を `@aiquants/swipe-overlay` の共有のフックへ移した**: ダイアログの開閉 (`showModal()`・`close()`・`inert`)・
31
+ 最初のフォーカス (日報の見出し)・閉じる要求の経路 (Escape・`cancel`・プラットフォームが強いた閉じ・背景・スワイプ) は `useSwipeOverlayDialog`、閉じて滑り出す間の
32
+ マウントは `useOverlayPresence` (閉じてから `SWIPE_OVERLAY_EXIT_MS` = 300 ms)。`@aiquants/contact-map` の詳細のオーバーレイと同じフックで、振る舞いは
33
+ 変わらない (下の修正を除く)。Tab の巡回・誤りの知らせの置き場・自動既読の留まり・履歴の項目はパッケージが持ったまま。
34
+
35
+ ### Fixed
36
+
37
+ - **モバイルのオーバーレイの中でピンチの拡大が効く**: 面は `touch-action: none`、側面ペインのタブの中身は `pan-y` で、面の上では 2 本の指の拡大まで止めていた
38
+ (背景は `manipulation`)。面と背景は `pinch-zoom`、タブの中身は `pan-y pinch-zoom` になり、オーバーレイのどこでもピンチでページを拡大できる (横の動きは閉じるスワイプのまま)。
39
+ デスクトップの配置の側面ペインのタブの中身も `pan-y` を外し (スワイプが無いので触れ方を絞らない)、タッチの画面でピンチの拡大が効く。
40
+ - **オーバーレイの中のスクロールが背後のページへつながらない**: 側面ペインのタブの中身 (オーバーレイの縦のスクロール領域) は `overscroll-behavior-y: contain` を持ち、
41
+ 端まで送ったスクロールはページへつながらない (Android の引いて再読み込みにならない)。面と背景の上の縦の動きも何も引っ張らない。
42
+ - **IME の変換を終える Escape が、重いページでもオーバーレイを閉じない**: オーバーレイが断った Escape (IME の変換を終えるもの、閉じるのを
43
+ 拒まれたもの) が起こすプラットフォームの閉じる要求は、キーから 50ms 以内なら捨てていたため、キーの処理に 50ms より掛かるとその要求で閉じた。
44
+ 要求はキーの既定動作として同じタスクの中で届くので、断った Escape のタスクの中の要求を捨てる (`@aiquants/swipe-overlay` の
45
+ `useSwipeOverlayDialog`)。後のタスクの要求 (Android の戻る) は捨てない。
46
+ - **スワイプで閉じた直後や、オーバーレイの見出しの帯・背景を引いた直後のタップが効く**: ブラウザーはパンできない 1 本の指の動きを自分のパンの
47
+ 試みとして扱い続け、離してからおよそ 1 秒のタップを `click` にしないことがあった (閉じた直後に次の日報のカードをタップしても開かない)。
48
+ その `touchmove` をページが取る (`@aiquants/swipe-overlay`。閉じるスワイプと決まった後の動きと、面のスクロールの領域の外と背景の 1 本の指の動き)。
49
+ - **端末を回してもオーバーレイの履歴の項目が溜まらない**: オーバーレイを開いたまま配置がデスクトップへ切り替わる (端末を横にする) と、オーバーレイは
50
+ 閉じても自分が積んだ履歴の項目を残し、回し戻して開き直すたびに同じ URL の項目が増えて、戻る操作が何もしないように見えた。デスクトップの配置への
51
+ 切り替えで閉じるときも、積んだ項目を戻す。
52
+
53
+ ## 0.49.0 (2026-10-21)
54
+
55
+ ### Breaking
56
+
57
+ - **`DailyReportPage` の `sessionEpoch` は必須**: ホストが失効の後に同じ利用者で確かめたセッションの数 (0 以上の安全な整数)。欠けると描画時に
58
+ `TypeError`、ほかの値は `RangeError`。失効で読み直すホストは定数 (`0` など) を渡せば、これまでと同じに動く。
59
+ - **`DailyReportErrorInfo` は `reason: "failed" | "refused"` を持ち、`operation` は `"liveUpdates"` も取る**: 403 (権限の断り) とライブ更新の
60
+ `forbidden` の終わりは `refused`、ほかは `failed`。`operation` を網羅して分けるホストは `"liveUpdates"` を足す。
61
+ - **検索・絞り込み・印のフックと置き場の options から `onSessionExpired` を外した**: `useDailyReportSearch`・`useDailyReportSearchFacets`・
62
+ `useShownSearchMarksStore`・`createDailyReportSearchSession`・`createDailyReportSearchMarksStore` は、セッションの終わりを API の基点の
63
+ セッションの門へ知らせる (下)。公開の `useDailyReportSseConnection` は `onSessionEnded` と `onRefused` を必須で受ける。
64
+ - **`DailyReportSearchFailure` に `refused` を足した** (403)。
65
+
66
+ ### Added
67
+
68
+ - **セッションの終わりで読み直さずに止め、ホストが確かめたら続ける (WCAG 2.2.5 Re-authenticating)**: どの読み取り (ids の流れ・詳細・検索・
69
+ 絞り込み・印) か書き込みが 401 で答えるか、SSE が `auth-required` で終わると、パッケージは API の基点ごとのセッションの門で通信を止め、
70
+ `config.onSessionExpired` を止めるたびに 1 回だけ呼ぶ (これまでは場所ごとに、1 回の失効で何度も呼びえた)。読み取りは中断し、SSE は閉じ、
71
+ ids の流れ・詳細・検索は新しい要求を出さず、書き込みは送らない (何も送らずに拒み、コメントの欄は下書きを残す)。下書き・開いた編集の形・
72
+ 選択・検索・スクロールはメモリに残る。`sessionEpoch` が止めた時の値と違えば続ける: ids の流れは続きを読み (完走したことのある一覧は
73
+ 読み直す)、SSE はカーソルから開き直し、待たせた詳細と絞り込みの候補を読み、検索と印を頼み直す。書き込みは送り直さない。続ける前に送った
74
+ 要求の 401 は、その後のセッションを止めない。一度完走した一覧は、止めても本体を外さない (選択・検索・スクロールが残る)。
75
+ - **ラベル `operationRefused` (関数) と `liveUpdatesRefused`**: 断られた書き込みと、断られたライブ更新の知らせの文言。固有のキーは 116
76
+ (文字列 92 → 93・関数 22 → 23) になる。ログのチャンネル `SessionGate` (クライアントのチャンネルは 5 つ)。
77
+
78
+ ### Fixed
79
+
80
+ - **書き込みの 401 がセッションの終わりを知らせなかった**: 8 つの書き込みは 401 で汎用の「操作に失敗」を出すだけだった。いまは門を止めて
81
+ `onSessionExpired` を 1 回呼び、パッケージの誤りの知らせは出さない (ホストの知らせが伝える)。
82
+ - **403 を失効として扱っていた**: ids の流れ・検索・絞り込み・印は 403 で `onSessionExpired` を呼び、詳細の置き場は 403 を失効の断りとして
83
+ 扱っていた。403 は権限の断りで、サインインし直しても変わらない: 書き込みは `labels.operationRefused` の知らせ (`reason: "refused"`)、
84
+ 検索と絞り込みは `refused`、詳細は断りを記録の誤りとして分け合い、印は付けず、ids の流れはやり直さずに `failed` で止まる。どれもホストの
85
+ 再認証を求めない。
86
+ - **SSE の `forbidden` が記録だけだった**: ライブ更新が止まったことを読み手が知れるよう、断りとして知らせの口へ出す
87
+ (`operation: "liveUpdates"`, `reason: "refused"`, `labels.liveUpdatesRefused`)。
88
+
5
89
  ## 0.48.0 (2026-10-21)
6
90
 
7
91
  ### Added
package/README.md CHANGED
@@ -106,7 +106,8 @@ label catalog reuses its engine keys (every label key `@aiquants/virtualscroll`
106
106
  `DailyReportRevealOptions`, the options of the test handle's `revealIndex`, is `@aiquants/virtualscroll`'s `scrollToIndex` options type, which takes
107
107
  `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
108
108
  because its row memo follows the tree revision (3.11.0).
109
- The peer floor of `@aiquants/swipe-overlay` is **1.2.6**: install 1.2.6 or later.
109
+ The peer floor of `@aiquants/swipe-overlay` is **1.3.0**: install 1.3.0 or later. The mobile overlay is its modal dialog (`useSwipeOverlayDialog`), stays mounted
110
+ while it slides out (`useOverlayPresence`) and gives its scroll areas the swipe surface's touch contract, the style that package exports for them (1.3.0).
110
111
  The mobile overlay, the comment delete confirmation and the built-in error notice leave `Escape` to its `Escape` stack, which lets an `Escape` that
111
112
  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).
112
113
  `@aiquants/sse` (the SSE wire contract, server response helpers and the reopening client) is a regular **dependency**: it arrives transitively, so
@@ -179,6 +180,9 @@ utilities are generated in one canonical build, define the `dark:` variant, and
179
180
 
180
181
  The host must also:
181
182
 
183
+ - Build with Tailwind CSS v4.1 or later. The coarse-pointer targets (**Coarse pointers** in Selection and focus appearance) are
184
+ `pointer-coarse:` utilities, a variant Tailwind added in v4.1; an older v4 build generates none of them, so every target keeps its fine-pointer
185
+ size on a touch screen.
182
186
  - Load the `tailwindcss-animate` plugin. The components use `animate-in` / `fade-in` / `zoom-in-95` / `slide-in-from-bottom-2` entrance animations
183
187
  (all under `motion-safe:`); without `@plugin "tailwindcss-animate"` those classes are not generated and the animations silently no-op.
184
188
  - Define the shadcn CSS variables themselves (`--background`, `--muted`, `--radius`, ...).
@@ -660,11 +664,12 @@ its payload — with one strict parser each, after authentication and before any
660
664
  | `[DailyReportSseReader]` | `info` | the shared SSE reader's Redis errors |
661
665
  | `[DailyReportExternalSource]` | `info` | `transformJsonArray`'s malformed JSON columns of an external source |
662
666
 
663
- The client has no host logger port: its four channels of the same list write to the console from `info` — `[DailyReportActionProvider]` (the action
667
+ The client has no host logger port: its five channels of the same list write to the console from `info` — `[DailyReportActionProvider]` (the action
664
668
  provider: a failed action, the queue of SSE messages about uncached reports, a cache reload), `[DailyReportDetailLoader]` (the detail loading: a
665
669
  stale update of a report's cache, a failed prefetch of a business date, a failed refetch that left the shown report in place),
666
670
  `[DailyReportIdsStream]` (the ids stream client: a revalidation that gave up, a rejected malformed line, which ends the attempt, a skipped line of
667
- an unknown kind, a malformed stream anchor) and `[DailyReportSseConnection]` (the SSE connection hook).
671
+ an unknown kind, a malformed stream anchor), `[DailyReportSseConnection]` (the SSE connection hook) and `[DailyReportSessionGate]` (the session
672
+ gate: a pause because the viewer's session ended, and the resume).
668
673
  Every line, the server's and the client's, carries exactly one channel prefix: no message spells a `[DailyReport…]` prefix of its own.
669
674
 
670
675
  A 500 is one line: its message (`500 endpoint=<name> elapsed=<ms>ms name=<error name> code=<code or N/A> message=<message>`,
@@ -1741,8 +1746,8 @@ unexpected error, a request that is not the package's client (another origin's p
1741
1746
 
1742
1747
  | Server-side situation | Response | Client (`useDailyReportSseConnection`) |
1743
1748
  | --- | --- | --- |
1744
- | Not authenticated | 200, `retry: 86400000` → `event: auth-required` (`Set-Cookie` forwarded) | terminated; calls `config.onSessionExpired()` once; never reconnects |
1745
- | Authenticated but no internal user | 200, `retry: 86400000` → `event: forbidden` | terminated; no navigation (a reload cannot fix it); visible through `sseStatus` |
1749
+ | Not authenticated | 200, `retry: 86400000` → `event: auth-required` (`Set-Cookie` forwarded) | terminated; pauses the package (`config.onSessionExpired()` once) and reconnects from its cursor when `sessionEpoch` changes |
1750
+ | Authenticated but no internal user | 200, `retry: 86400000` → `event: forbidden` | terminated; no navigation (a reload cannot fix it); reported as a refusal through the error surface (`operation: "liveUpdates"`, `reason: "refused"`, `labels.liveUpdatesRefused`) and visible through `sseStatus` |
1746
1751
  | A request from another origin's page that the isolation refuses ([Request isolation](#request-isolation)) | 403 JSON before authentication (`Cache-Control: no-store`, the routes' security headers, no `Set-Cookie`), counted in the bounded refusal record | never met: the package's client connects from the page's own origin |
1747
1752
  | A React Router single-fetch data request (`<path>.data`, `GET` or `HEAD`) | 404 JSON before authentication (`{"error":{"message":"Not Found"}}`, `Cache-Control: no-store`, the routes' security headers, no `Set-Cookie`), with no subscription or timer and no line in the refusal record ([Request isolation](#request-isolation)) | never sent by the client: `EventSource` requests the route itself |
1748
1753
  | Unknown endpoint | 404 | treated as retryable; backoff up to 30 s |
@@ -2301,8 +2306,8 @@ The remainder does not go between a caption and its control: at q = 8 that gap w
2301
2306
  Each request is sent `cache: "no-cache"`: the browser revalidates its previous answer with the ETag (`If-None-Match`) and, on a 304, hands over its
2302
2307
  cached 200, so an unchanged answer's body is not sent again.
2303
2308
  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
2304
- failure in the status line. 401 and 403 call `onSessionExpired`; 404 shows `unavailable`, 429 `busy`, and anything else, a malformed answer
2305
- included, `failed`.
2309
+ failure in the status line. A 401 pauses the package at the session's end (see [Client wiring](#client-wiring)) and loads the candidates again
2310
+ when it resumes; a 403 shows `refused`, 404 `unavailable`, 429 `busy`, and anything else, a malformed answer included, `failed`.
2306
2311
  The cells render once the candidates are loaded. The author and customer options are sorted with `Intl.Collator(config.locale)` (values the
2307
2312
  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
2308
2313
  with their badge label from `sourceTypeConfigs` (the source type itself without one).
@@ -2426,13 +2431,13 @@ While an answer shows, the List cards, the side pane and the DetailList rows mar
2426
2431
  A field is painted only when its marks were computed on the value drawn (the fingerprint): after a save or an SSE update the old ranges are not
2427
2432
  painted and the field asks once for fresh marks. An answer whose `wordsDigest` differs from the shown answer's words is dropped; a quiet re-query of
2428
2433
  the same search makes the marks stale and the mounted rows ask again;
2429
- 401 and 403 call `onSessionExpired`; a 429 whose `Retry-After` is at most 60 s (one window of the server's rate bucket) is asked again once, after
2434
+ a 401 pauses the package at the session's end (the marks are asked again when it resumes) and a 403 leaves the rows plain; a 429 whose `Retry-After` is at most 60 s (one window of the server's rate bucket) is asked again once, after
2430
2435
  that delay, for the reports still subscribed then (one whose subscribers left meanwhile asks again when it is subscribed again), and a 429 without a
2431
2436
  `Retry-After`, or with a longer one, sets no timer and leaves its reports plain for the answer's epoch (a delay of 2^31 ms or more would overflow
2432
2437
  the timer and ask again at once);
2433
2438
  any other failure of the request — a network failure, an abort, another status, a body that is not JSON — leaves those rows plain for as long as the
2434
2439
  shown answer stays the same and reports nothing.
2435
- Only the request's own failures are dropped: an error thrown by the host's `onSessionExpired`, by holding a 429's retry or by applying an answer and
2440
+ Only the request's own failures are dropped: an error thrown by the session report (the host's `onSessionExpired`), by holding a 429's retry or by applying an answer and
2436
2441
  notifying the rows rejects the batch, and the scheduler reports it with `reportError` (the window's `error` event, where a host's error monitoring
2437
2442
  sees it) after it has freed the request's slot; a batch that rejects with its own abort's reason reports nothing.
2438
2443
  Disposing the store — a new search, or React hiding the screen inside an `<Activity>` — aborts its requests, cancels its retry timers and gives
@@ -2487,13 +2492,15 @@ export const clientLoader = createDailyReportClientLoader() // Bootstraps the
2487
2492
 
2488
2493
  export default function Route() {
2489
2494
  const { user, userId } = useLoaderData<typeof clientLoader>()
2495
+ const { sessionEpoch, showSignInNotice } = useYourReauthentication(user) // Your notice, sign-in window and same-user check
2490
2496
  return (
2491
2497
  <YourAppLayout>
2492
2498
  <DailyReportPage
2493
2499
  user={user}
2494
2500
  userId={userId}
2501
+ sessionEpoch={sessionEpoch} // Required: advance it when the same user has signed in again (the package resumes)
2495
2502
  config={{
2496
- onSessionExpired: () => navigate(0), // Required: the host decides the navigation on session expiry
2503
+ onSessionExpired: showSignInNotice, // Required: called once each time the package pauses at a session end
2497
2504
  renderHeader: ({ title, annotation }) => <YourHeader title={title} annotation={annotation} />,
2498
2505
  showDevControls: import.meta.env.DEV, // Toggle SSE subscriptions, etc.
2499
2506
  locale: "en", // UI chrome language: "en" (default) | "ja"
@@ -2519,10 +2526,20 @@ one definition of a draft (`draftLabelNames` in [DI ports](#di-ports)), so `conf
2519
2526
  unknown key.
2520
2527
  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
2521
2528
  optimistic publish clears `isDraft` at once and keeps the labels until the answer, and a failed publish restores `isDraft`.
2522
- 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,
2523
- the filters' candidates (`search-facets`) or the marks (`search-marks`) answer 401 / 403 — it
2524
- calls `onSessionExpired()` exactly once per occurrence and stops. A typical host reloads the route (`navigate(0)`) so its own loader redirects to the
2525
- login page.
2529
+ The package never navigates or reloads by itself. When the viewer's session ends — the SSE stream ends with `auth-required`, or the ids stream, a
2530
+ detail read, a `search` request, the filters' candidates (`search-facets`), the marks (`search-marks`) or a write answers 401 — it **pauses** its
2531
+ network activity and calls `onSessionExpired()` once per pause: in-flight reads are aborted, the SSE connection closes, the ids stream, the detail
2532
+ reads and the search requests stop, no new request starts, and a write is not sent at all (it rejects without touching the server or the view; a
2533
+ comment field keeps its draft). Everything the page holds in memory stays: comment drafts, an open edit form, the selection, the search and the scroll.
2534
+ The package resumes when the page's required `sessionEpoch` prop (a safe integer >= 0; a missing one throws a `TypeError` at render and any other
2535
+ value a `RangeError`) differs from the value it paused at: the ids stream continues, the SSE reconnects from its cursor, held detail reads and the
2536
+ filters' candidates load again, and the search and the marks are asked again. Nothing is sent again on the viewer's behalf. A request sent before a
2537
+ resume whose 401 arrives after it does not pause the session that followed. A 403 is a refusal of the viewer, never a session end: a write answered 403
2538
+ is reported through the error surface with `reason: "refused"` and `labels.operationRefused(operation)`, the live updates ending with `forbidden`
2539
+ are reported the same way (`operation: "liveUpdates"`, `labels.liveUpdatesRefused`), a search or the filters show `refused`, a detail read keeps the
2540
+ refusal as the record's error, the marks stay plain, and the ids stream fails without retrying.
2541
+ A host that re-authenticates without a reload shows its own notice from `onSessionExpired`, lets the viewer sign in again (in another window, say), checks
2542
+ that the same user signed in, and then advances `sessionEpoch`; a host that reloads instead passes a constant and navigates from `onSessionExpired`.
2526
2543
  `DailyReportConfigProvider` takes the same required `config`, and every component and hook of the package that reads the configuration needs it above:
2527
2544
  outside it `useDailyReportConfig` throws at render (`[daily-report] useDailyReportConfig must be used within a DailyReportConfigProvider`); no default
2528
2545
  configuration renders silently.
@@ -2554,16 +2571,18 @@ inside its TTL is not fetched. A refetch of a shown report that fails (the re-va
2554
2571
  leaves the report in place with no error and only logs a warning on `[DailyReportDetailLoader]`, so the next new reader re-validates again. A settled
2555
2572
  load error is not shared either: a reader that subscribes to a record holding an error — a row that mounts, an id that enters the input lead's band,
2556
2573
  the side pane — asks again, and the answer replaces the error. Only a new subscription asks again or re-validates (a re-render or a second
2557
- subscription of the same listener does not, so a row that keeps showing a failure or an expired report never loops), and a refusal of the viewer's
2558
- session (401 or 403, which cannot succeed before `onSessionExpired` has run) is shared like an answer: it becomes the record's error even over a
2559
- shown report, and no new reader asks again or re-validates.
2574
+ subscription of the same listener does not, so a row that keeps showing a failure or an expired report never loops), and a refusal of this read to
2575
+ the viewer (403, which signing in again does not change) is shared like an answer: it becomes the record's error even over a shown report, and no new
2576
+ reader asks again or re-validates. A session end (401) is not an error: the record keeps what it shows, the package pauses (see
2577
+ [Client wiring](#client-wiring)), no load starts while it is paused, and the held reports are asked again when it resumes.
2560
2578
  The first load of a report that is not cached starts inside its first reader's subscription, with no task between the row's mount and its request,
2561
2579
  so a held arrow key's stream of keydowns cannot postpone it until the key is released; only the refetch of an expired cached report waits 120 ms, to
2562
2580
  absorb rows that only scroll past. Loads of reports of one business date share one request.
2563
2581
 
2564
2582
  Mutation failures (save / publish / delete / comment) are surfaced through an error seam: inject `config.onError(info: DailyReportErrorInfo)` to route
2565
2583
  them into your own toast/notification system, or omit it to use the package's built-in notice. Failures on a continuation that resolves after a
2566
- no-reload user switch are suppressed.
2584
+ no-reload user switch are suppressed, and so is a write answered 401 (the package pauses at the session's end and the host's notice speaks) or refused
2585
+ while the package is paused (nothing was sent). A write answered 403 is a refusal (`reason: "refused"`), which trying again does not change.
2567
2586
  Deleting a report or a comment that the server no longer has (404) counts as done: the row or the comment stays removed and nothing is shown. Only the
2568
2587
  deleting intents, `delete` and `deleteComment`, read a 404 so (their callbacks must say what a 404 means, `onGone`, and no other intent's callbacks
2569
2588
  may, by type: `ActionCallbacks`); the 404 of any other intent rolls back and is reported.
@@ -3333,8 +3352,9 @@ the resize handle and the side pane lie outside the list.
3333
3352
  The comment field is named by that heading (`aria-labelledby`; `labels.comments`), which renders whenever the form does, so the field has a name of
3334
3353
  its own in the DetailList row, the side pane and the mobile overlay; its placeholder (`labels.commentPlaceholder`) stays a hint, not its name.
3335
3354
  While a post is sent, the field keeps the draft, read-only, with the submit button disabled, and empties only when the post is accepted; a refused
3336
- post (401, 403, a 5xx) leaves the draft, its text and its caret untouched, so a viewer whose session ended sends it again after signing in
3337
- (the package's part of WCAG 2.2.5; keeping a draft across a navigation or a reload is the host's).
3355
+ post (401, 403, a 5xx), and a post while the package is paused at a session end (never sent), leave the draft, its text and its caret untouched, so a
3356
+ viewer whose session ended sends it again after signing in again, on the same page (WCAG 2.2.5, with the host's re-authentication; see
3357
+ [Client wiring](#client-wiring)).
3338
3358
  A report of a source that takes no comments and has none shows no empty section, and deleting the last comment of such a report removes the section,
3339
3359
  so focus goes to the report's anchor above.
3340
3360
  - **Mobile detail overlay** (List, single-column layout): the platform's modal dialog, a `<dialog data-daily-report-mobile-overlay>` (implicit
@@ -3346,8 +3366,8 @@ the resize handle and the side pane lie outside the list.
3346
3366
  on Shift+Tab the container of a roving-focus widget around the origin, such as the tabs' `tablist`, is not counted before it, because real Shift+Tab
3347
3367
  leaves such a widget without stopping at it), and Escape closes it unless the key belongs to an IME composition.
3348
3368
  Every close request goes through the overlay's own close path, which also pops the history entry the overlay pushed: the platform's `cancel` (an
3349
- Escape the overlay's key handling did not take, Android's back gesture) is default-prevented and routed there, except one caused within 50 ms by an
3350
- Escape the overlay declined during an IME composition; when the platform closes the dialog by itself (a `cancel` the page is not allowed to stop),
3369
+ Escape the overlay's key handling did not take, Android's back gesture) is default-prevented and routed there, except one that an
3370
+ Escape the overlay declined (an IME composition, or a close the overlay refused) caused in its own task; when the platform closes the dialog by itself (a `cancel` the page is not allowed to stop),
3351
3371
  the close path runs too, and the dialog opens modally again when the close is refused.
3352
3372
  Closing it by any means (close button, Escape, backdrop, browser back, a switch to the desktop layout) first blurs the focus inside the dialog, then
3353
3373
  ends the modal state with `close()` (the platform returns focus, without scrolling, to the element that had it when the dialog opened, when that
@@ -3360,6 +3380,13 @@ the resize handle and the side pane lie outside the list.
3360
3380
  The dialog itself takes focus (`tabIndex=-1`), so pressing a non-focusable part of it never drops focus to `body`. The backdrop is a decorative
3361
3381
  layer (`data-daily-report-overlay-backdrop`, `aria-hidden`, without a role or a name, not focusable): pressing it closes the overlay, while keyboard
3362
3382
  and assistive-technology users close it with Escape or the close button. Its transitions run only under `prefers-reduced-motion: no-preference`.
3383
+ Touch: the panel and the backdrop take `touch-action: pinch-zoom`, and the side pane's tab panels, the overlay's vertical scroll areas, take
3384
+ `touch-action: pan-y pinch-zoom` with `overscroll-behavior-y: contain`, so a pinch zooms the page anywhere in the overlay, a horizontal move that
3385
+ starts anywhere on the panel is the swipe that closes it, and no scroll in it reaches the page behind (no scroll chaining, no pull-to-refresh).
3386
+ The dialog's lifecycle (its modality and `inert`, the first focus, the close-request path, the swipe) is `@aiquants/swipe-overlay`'s
3387
+ `useSwipeOverlayDialog` and its slide-out mount is `useOverlayPresence` (mounted for the swipe's exit, 300 ms, after it closes), the same hooks as
3388
+ `@aiquants/contact-map`'s detail overlay; the package adds the Tab wrap, the error notice's slot, the dwell signal of the auto-read and the history
3389
+ entry.
3363
3390
  - **Iframes and shadow roots**: the views never read the global `document`; focus checks, the handoff, the body scroll lock and the overlay use the
3364
3391
  view's own document or shadow root, the Tab-stop search walks open shadow roots, and script-driven scrolling reads the reduced-motion preference of
3365
3392
  the element's own window.
@@ -3434,12 +3461,15 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3434
3461
  `resolution: <dpr>dppx` of each of the twelve ratios with `calc(⌊2·dpr⌋px / dpr)`), and the lift reads it
3435
3462
  (`translate-y-[calc(-1*var(--aqdr-hover-lift))]`); every class is a literal, so a host's Tailwind build and the package's standalone stylesheet
3436
3463
  compile the same rules.
3437
- - **List slot P**: the List card's content is sized in rem, so the slot follows the page's root font size R: P(R) = 4 · ⌈(2G + 2 × (1 + 15) (the
3438
- card's border and padding) + 4 (spare) + 6.75R) / 4⌉ = 4 · ⌈(52 px + 6.75 rem) / 4⌉, the sum rounded up to the 4 px layout lattice, so every row top
3439
- stays on the lattice.
3440
- R is read from the root element's computed style and read again whenever a hidden 1 rem probe inside the List
3441
- (`data-daily-report-root-font-size-probe`) changes size, and the List renders its `VirtualScroll` only once P is known (measured before the first
3442
- paint).
3464
+ - **List slot P**: the List card's content is sized in rem (its toggles in px under a coarse pointer), so the slot follows the page's root font size R
3465
+ and the toggles' target size T on the card's second line: P(R, T) = 4 · ⌈(2G + 2 × (1 + 15) (the card's border and padding) + 4 (spare) + 2R +
3466
+ max(4.75R, T)) / 4⌉ = 4 · ⌈(52 px + 2 rem + max(4.75 rem, T)) / 4⌉, the sum rounded up to the 4 px layout lattice, so every row top stays on the
3467
+ lattice. T is 0 under a fine pointer (the toggles sit on the header line) and 44 under a coarse one (the toggles move beside the preview, **List
3468
+ card** below); at a 16 px root 4.75 rem = 76 px is taller than 44, so P is 160 under either pointer.
3469
+ R is read from the root element's computed style and T from the height of a hidden probe that is 44 px tall only under a coarse pointer
3470
+ (`data-daily-report-target-size-probe`); one `ResizeObserver` reads both again whenever the hidden 1 rem probe
3471
+ (`data-daily-report-root-font-size-probe`) or the target probe changes size, and the List renders its `VirtualScroll` only once P is known
3472
+ (measured before the first paint).
3443
3473
  That one value sizes the row frames, `VirtualScroll`'s rows and the keyboard's row geometry, and when it changes the List keeps the first visible
3444
3474
  row in place by rescaling the scroll position. At the default 16 px root the sum is exactly 160, so P = 160: the card is P − 2G = 144 px, cards are
3445
3475
  16 px apart and an edge-aligned card sits 8 px from the edge (exactly 8 when the view's height is a whole number of device pixels, see **G-symmetric
@@ -3481,6 +3511,10 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3481
3511
  The content of a full card is 6.75 rem: the pills' line 1.5 rem + 0.5 + three one-line preview paragraphs of 1.25 rem, 0.5 rem apart (24 + 8 + 3 ×
3482
3512
  20 + 2 × 8 = 108 px at a 16 px root), inside a 1 + 15 inset at the top and the bottom (the inset counts the border, below), so 1 + 15 + 6.75R + 15 +
3483
3513
  1 ≤ P − 2G with at least 4 px to spare at every root font size (140 ≤ 144 at 16 px).
3514
+ Under a coarse pointer the card is a two-column grid (`minmax(0, 1fr)` and `auto`): the primary button and the markers share the header line, and the
3515
+ 44 px star and read toggles sit in the second column of the second line, 4 px apart, beside the preview, so the header line stays 1.5 rem and P keeps
3516
+ its value at a 16 px root. Under a fine pointer the two groups that hold the markers and the toggles make no box (`display: contents`), so the card
3517
+ lays out exactly as above.
3484
3518
  - **Forced colours**: the ring (a box shadow) disappears, so the row frame draws the selection itself: a 2 px solid `Highlight` outline at offset −8
3485
3519
  (−G), which puts the line in the ring's band, 0–2 px outside the surface. The frame's corner radius is the surface's radius plus G
3486
3520
  (`calc(var(--radius-2xl) + 8px)`, 24 at a 16 px root), so the line is concentric with the surface at every root font size.
@@ -3740,7 +3774,9 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3740
3774
  inside the band: one more 32 px block), so the views start q + the band below the column's top — 4 + 32 = 36 px at the multiples of 1/4 and 8 + 32 =
3741
3775
  40 px at the odd eighths, or 4 + 64 = 68 px and 8 + 64 = 72 px with the search row — on the lattice at every ratio.
3742
3776
  The search row's 24 px controls stand at its bottom, so the gap above them is 32 − 24 = 8 = G from the toolbar's bar and their bottom edge is G
3743
- above the view's first surface. The band stacks above the views (`relative z-[1]`), so the 4 px focus outline of a search control that ends at the
3777
+ above the view's first surface. Under a coarse pointer the band's controls are 48 px (44 rounded up to 8), so the bar is 4 + 48 + 4 = 56 px (112 px
3778
+ when it wraps) and the search row's first line 8 + 48 = 56 px: all multiples of 8, so every edge inside the band, not only its bottom, lands on a
3779
+ whole device pixel at every ratio. The band stacks above the views (`relative z-[1]`), so the 4 px focus outline of a search control that ends at the
3744
3780
  view's top paints over the view's G strip.
3745
3781
  Measured in Chromium at 1280 × 800 and 1.125 under a 32 px host header: the toolbar, the view and the first row surface start at device pixels 45,
3746
3782
  81 and 90 (with a 4 px inset and no band they started at 40.5, 76.5 and 85.5, half a device pixel off, and every row edge, ring and outline below
@@ -3849,22 +3885,48 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3849
3885
  refuses one without a registered surface, and measures each against its surfaces. The quiet text (section labels, metadata, placeholders, the empty
3850
3886
  view, inactive tabs) is one token, `--aqdr-text-quiet` (light `oklch(44% 0.046 257.417)`, dark `oklch(77% 0.040 256.788)`: slate-500's and
3851
3887
  slate-400's hue and chroma), whose lightness `src/client/ui/aaa-quiet-text.spec.ts` solves from the surfaces it sits on (light 7.06–7.73, dark
3852
- 7.07–9.75). A source badge's colours are the host's (**External Source Badge Configuration**). Targets stay at WCAG 2.5.8's 24 px, except the
3853
- List's resize handle with its 10 px hit area and the 8 px scroll bar beside it (**Side pane** above); 2.5.5's 44 px is not claimed.
3888
+ 7.07–9.75). A source badge's colours are the host's (**External Source Badge Configuration**). Under a fine pointer targets stay at WCAG 2.5.8's
3889
+ 24 px, except the List's resize handle with its 10 px hit area and the 8 px scroll bar beside it (**Side pane** above); under a coarse pointer they
3890
+ reach 2.5.5's 44 px (**Coarse pointers** below), so 2.5.5 is not claimed overall.
3854
3891
  - **WCAG 2.2 AAA beyond contrast**: met are 1.4.9 (no images of text), 2.1.3 (every function by the keyboard, the List splitter's arrow keys
3855
3892
  included), 2.2.3 (nothing on a timer; **The built-in notice**), 2.3.2 (no flashes), 2.3.3 (motion follows `prefers-reduced-motion`), 2.4.8 (the page
3856
3893
  and the report headings tell where the viewer is), 2.4.9 (links name their purpose), 2.4.10 (sections carry headings), 2.4.12 and 2.4.13 (the focus
3857
3894
  is never obscured, and its outline is 2 px at 3:1), 3.1.3, 3.1.4 (abbreviated source badges, **External Source Badge Configuration**), 3.2.5 and
3858
3895
  3.3.6. Not met, by decision: 2.2.4 (Interruptions): live updates (SSE) apply as they arrive, with no pause, and
3859
3896
  the scroll anchoring keeps the rows on screen in place. Not met at AA: 2.5.8 for the List's resize handle and the scroll bar beside it (**Side pane** above): larger hit areas for the handle and the
3860
- bar wait for the same layout pass as 2.5.5. Not claimed yet: 2.5.5 (44 px targets), 1.4.8 (a line measure and the List preview's
3897
+ bar wait for the same layout pass as 2.5.5 under a fine pointer. Met under a coarse pointer only: 2.5.5 (44 px targets; **Coarse pointers** below).
3898
+ Not claimed yet: 2.5.5 under a fine pointer, 1.4.8 (a line measure and the List preview's
3861
3899
  leading) and 3.3.5 (a help control for the search syntax), which need a layout pass, and 3.1.6 (readings of names), which needs readings from the
3862
- host's directory. 2.2.5 is met by the package for a refused comment post (**One comment section rule** above); keeping work across a
3863
- re-authentication elsewhere is the host's, as is 2.2.6 (telling users how long a session lasts). The floating tap-scroll circle hides while the view root carries `data-daily-report-keyboard-focus` (through the class both views pass in
3900
+ host's directory. 2.2.5 (Re-authenticating) is met with the host: at a session end the package pauses without reloading and keeps every draft, the
3901
+ open edit form, the selection, the search and the scroll in memory; the host lets the viewer sign in again without leaving the page and advances
3902
+ `sessionEpoch`, and the package resumes and sends nothing again by itself ([Client wiring](#client-wiring)). 2.2.6 (Timeouts) is the host's: the
3903
+ package keeps unsent work only in the open tab's memory until it is closed, and the host states how long a session lasts and what ends it. The floating tap-scroll circle hides while the view root carries `data-daily-report-keyboard-focus` (through the class both views pass in
3864
3904
  `VirtualScroll`'s tap-scroll circle options).
3905
+ - **Coarse pointers (touch screens)**: under `@media (pointer: coarse)` every target is at least 44 × 44 CSS px (WCAG 2.5.5's enhanced size, T =
3906
+ 44 = 11 units of the 4 px lattice, written in px so it never shrinks with the root font size): the action buttons, the toolbar's and
3907
+ the side pane's tabs (the side pane's bar becomes 4 + 44 + 4 = 52 px), the create button, the search box's toggles, field and clear button, the comment field
3908
+ and its send button, the delete confirmation, the overlay's and the notice's close buttons, both retry buttons, `Button` and `Input` of the edit form,
3909
+ and the attachments' download and row links (a tile's last line becomes 48 px, 44 rounded up to 8, so tiles stay on the device-pixel lattice). The toolbar band's
3910
+ controls take that 48 px height too (the view tabs, the create button, the auto-read switch's line, the search field and both toggles), because
3911
+ both edges of each must land on whole device pixels at the odd-eighth ratios (a 44 px box is 115.5 device pixels at 2.625): the toolbar's bar
3912
+ becomes 4 + 48 + 4 = 56 px (112 px when it wraps to two lines) and the search row's first line 8 + 48 = 56 px. A round
3913
+ or pill-shaped control also gets a transparent square hit area over its padding box, because hit testing follows the rounded corners and a 44 px circle
3914
+ does not hold a 44 × 44 square (a bordered control about 44 px wide adds `pointer-coarse:before:-inset-px` so the square also covers its border,
3915
+ as the search box's pills and the List's mobile overlay close button do). Each box grows only through a minimum size, so nothing changes under a fine pointer (mouse, trackpad, pen), where targets keep the 24 px geometry of WCAG
3916
+ 2.5.8; 2.5.5 is therefore met under a coarse pointer only and not claimed overall. The auto-read switch keeps its 40 × 24 look and gets a centred
3917
+ 44 × 44 hit area in the band's 48 px line; the side pane's action buttons move to a 44 px line of their own under the subject, at its end. Exceptions under a
3918
+ coarse pointer: the List's resize handle and the views' scroll bars, the host's filter select controls, the dev toolbox's reload and clear-cache
3919
+ icon buttons (24 × 24 targets in its 24 px box; its SSE switch has the switch's centred 44 × 44 hit area, and under a coarse pointer the toolbox
3920
+ spaces its items 6 px apart, 4 + (44 − 40) / 2, so that hit area does not cover the reload button's target) and `VirtualScroll`'s tap-scroll
3921
+ circle. A Tailwind host generates these coarse-pointer utilities from Tailwind CSS v4.1 on (**Tailwind v4 host** above). Text fields use 16 px text under a coarse pointer (14 px under a fine one), so focusing them does not zoom the page on touch
3922
+ browsers; the search field's Enter key is labelled as search (`enterkeyhint="search"`) and the comment field's as send (`enterkeyhint="send"`). When
3923
+ the views are narrower than 320 px (the search row's smallest coarse-pointer width: the band's insets, both toggles, two 8 px gaps and a field that
3924
+ keeps one 44 px target of text room), the search box's filter toggle moves to a line of its own under the field. The mobile overlay keeps its
3925
+ content at least its 16 px padding and at least the display's safe-area insets (`env(safe-area-inset-*)`) from each edge.
3865
3926
  - **Action buttons and icons**: the List card's star and read toggles, the star, read, edit and delete buttons of the DetailList header and the side
3866
- pane, and the trash button of the viewer's comments share one 24 px round target that never shrinks, with a 16 px SVG icon centred in it (4 px on
3867
- every side), so the icons sit on the 4 px grid without depending on a font. In the List card the markers and toggles stand 4 px apart; the header
3927
+ pane, and the trash button of the viewer's comments share one 24 px round target under a fine pointer (44 × 44 under a coarse one, **Coarse
3928
+ pointers** above), which never shrinks, with a 16 px SVG icon centred in it (4 px on every side, 14 px in the coarse box), so the icons sit on the
3929
+ 4 px grid without depending on a font. In the List card the markers and toggles stand 4 px apart; the header
3868
3930
  pills and the source badge are 24 px tall like the targets.
3869
3931
  The star is a regular five-pointed star, outlined when off and filled gold when on (with a darker gold edge in the light theme); read is a check,
3870
3932
  unread an 8 px dot, edit a pencil, delete a trash can. Every icon state reaches at least 3.59:1 against each background it sits on, the button's
@@ -4212,7 +4274,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4212
4274
  />
4213
4275
  ```
4214
4276
 
4215
- - **One surface for all wording.** The catalog covers the engine chrome keys of `@aiquants/virtualscroll` (every label key it exports) plus 114 own keys: field headings, the
4277
+ - **One surface for all wording.** The catalog covers the engine chrome keys of `@aiquants/virtualscroll` (every label key it exports) plus 116 own keys: field headings, the
4216
4278
  page title (`title`, passed to `renderHeader` and used as the accessible name of both lists), the lists' keyboard help, the row states (read, read and updated since, and
4217
4279
  starred, editing), the updated-since-read pill and the List card's position read to screen readers, tabs, buttons, placeholders, the `confirm()` text, stream badges, the search box, its filter row and the
4218
4280
  search's match indicator, the status screen and the page's status region and the mutation-failure message with its repeat count. The engine part of
@@ -4271,13 +4333,16 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4271
4333
  - **Not tied to `locale`**: `sourceTypeConfigs` (host data). Only the built-in default badge map used when `sourceTypeConfigs`
4272
4334
  is omitted takes its `Internal` label from `labels.sourceInternal`.
4273
4335
  - **Host notifications**: `config.onError` receives `DailyReportErrorInfo` with the stable
4274
- `operation` key and `message` = `labels.operationFailed(operation)` in the chosen locale.
4336
+ `operation` key (an operation, or `"liveUpdates"` when the server refused the viewer the live updates), the `reason` (`"refused"` for a 403 or the
4337
+ live updates' `forbidden` end, `"failed"` otherwise) and `message` = `labels.operationRefused(operation)`, `labels.liveUpdatesRefused` or
4338
+ `labels.operationFailed(operation)` in the chosen locale. A session end (401) is never reported here: the package pauses and calls
4339
+ `onSessionExpired` instead.
4275
4340
  - **`locale` is the only language axis.** This package has no BCP 47 `formatLocale` option: digit
4276
4341
  grouping follows `locale` (`en` → `"en-US"`, `ja` → `"ja-JP"`) and dates stay in ISO form. Packages
4277
4342
  that do format dates (for example `@aiquants/period-slider`) name that separate axis `formatLocale`.
4278
4343
 
4279
4344
  Exported API (`@aiquants/daily-report/client`): `DAILY_REPORT_LABEL_KEYS` (every key: the engine keys `@aiquants/virtualscroll` exports, in its order,
4280
- first, then the 114 own keys), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReportLabels(locale, labels)`
4345
+ first, then the 116 own keys), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReportLabels(locale, labels)`
4281
4346
  (the effective frozen labels — the catalog object itself when `labels` is `undefined`), the resolved
4282
4347
  `locale` / `labels` on `useDailyReportConfig()`, and the types `DailyReportLocale` (= `VirtualScrollLocale`),
4283
4348
  `DailyReportLabels`, `DailyReportLabelOverrides` and `DailyReportOperation`.
package/dist/client.d.mts CHANGED
@@ -37,7 +37,7 @@ type DailyReportDetailListProps = {
37
37
  scrollRestoreRef?: RefObject<DailyReportDetailListScrollRestore>;
38
38
  };
39
39
  declare const DailyReportDetailList: (props: DailyReportDetailListProps) => react.JSX.Element;
40
- type DailyReportSearchFailure = "invalid" | "busy" | "unavailable" | "failed";
40
+ type DailyReportSearchFailure = "invalid" | "busy" | "unavailable" | "failed" | "refused";
41
41
  type DailyReportLocale = VirtualScrollLocale;
42
42
  type DailyReportOperation = Exclude<DailyReportActionIntent, "clearCache">;
43
43
  type DailyReportLabels = VirtualScrollLabels & {
@@ -84,6 +84,7 @@ type DailyReportLabels = VirtualScrollLabels & {
84
84
  readonly reload: string;
85
85
  readonly loadingIds: string;
86
86
  readonly streamFailed: string;
87
+ readonly liveUpdatesRefused: string;
87
88
  readonly streamRetry: string;
88
89
  readonly streamRevalidating: string;
89
90
  readonly selectPrompt: string;
@@ -142,6 +143,7 @@ type DailyReportLabels = VirtualScrollLabels & {
142
143
  readonly reportSummaryLoadFailed: (reportHubId: number) => string;
143
144
  readonly interviewerWithAffiliation: (name: string, affiliation: string) => string;
144
145
  readonly operationFailed: (operation: DailyReportOperation) => string;
146
+ readonly operationRefused: (operation: DailyReportOperation) => string;
145
147
  readonly noticeRepeated: (count: number) => string;
146
148
  readonly rowState: (state: {
147
149
  readonly readState: DailyReportReadState;
@@ -217,6 +219,7 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
217
219
  "reload",
218
220
  "loadingIds",
219
221
  "streamFailed",
222
+ "liveUpdatesRefused",
220
223
  "streamRetry",
221
224
  "streamRevalidating",
222
225
  "selectPrompt",
@@ -275,6 +278,7 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
275
278
  "reportSummaryLoadFailed",
276
279
  "interviewerWithAffiliation",
277
280
  "operationFailed",
281
+ "operationRefused",
278
282
  "noticeRepeated",
279
283
  "rowState",
280
284
  "rowEditingState",
@@ -364,7 +368,8 @@ type DailyReportHeaderProps = {
364
368
  annotation?: ReactNode;
365
369
  };
366
370
  type DailyReportErrorInfo = {
367
- operation: DailyReportOperation;
371
+ operation: DailyReportOperation | "liveUpdates";
372
+ reason: "failed" | "refused";
368
373
  message: string;
369
374
  cause?: unknown;
370
375
  };
@@ -413,6 +418,7 @@ type DailyReportPageProps = {
413
418
  user?: DailyReportUser;
414
419
  userId?: string | null;
415
420
  config: DailyReportClientConfigInput;
421
+ sessionEpoch: number;
416
422
  };
417
423
  declare const DailyReportPage: ({ config, ...rest }: DailyReportPageProps) => react.JSX.Element;
418
424
  type DailyReportResolvedContentProps = {
@@ -427,8 +433,10 @@ type UseDailyReportSseConnectionOptions = {
427
433
  onMessage: (payload: Exclude<DailyReportSseMessage, {
428
434
  type: "connected";
429
435
  }>) => void;
436
+ onSessionEnded: () => void;
437
+ onRefused: () => void;
430
438
  };
431
- declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage }: UseDailyReportSseConnectionOptions) => DailyReportSseConnectionStatus;
439
+ declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage, onSessionEnded, onRefused }: UseDailyReportSseConnectionOptions) => DailyReportSseConnectionStatus;
432
440
  type ReportBusinessDate = DailyReportDetail["date"];
433
441
  type DailyReportActionFunctions = {
434
442
  toggleSse: () => void;
package/dist/client.d.ts CHANGED
@@ -37,7 +37,7 @@ type DailyReportDetailListProps = {
37
37
  scrollRestoreRef?: RefObject<DailyReportDetailListScrollRestore>;
38
38
  };
39
39
  declare const DailyReportDetailList: (props: DailyReportDetailListProps) => react.JSX.Element;
40
- type DailyReportSearchFailure = "invalid" | "busy" | "unavailable" | "failed";
40
+ type DailyReportSearchFailure = "invalid" | "busy" | "unavailable" | "failed" | "refused";
41
41
  type DailyReportLocale = VirtualScrollLocale;
42
42
  type DailyReportOperation = Exclude<DailyReportActionIntent, "clearCache">;
43
43
  type DailyReportLabels = VirtualScrollLabels & {
@@ -84,6 +84,7 @@ type DailyReportLabels = VirtualScrollLabels & {
84
84
  readonly reload: string;
85
85
  readonly loadingIds: string;
86
86
  readonly streamFailed: string;
87
+ readonly liveUpdatesRefused: string;
87
88
  readonly streamRetry: string;
88
89
  readonly streamRevalidating: string;
89
90
  readonly selectPrompt: string;
@@ -142,6 +143,7 @@ type DailyReportLabels = VirtualScrollLabels & {
142
143
  readonly reportSummaryLoadFailed: (reportHubId: number) => string;
143
144
  readonly interviewerWithAffiliation: (name: string, affiliation: string) => string;
144
145
  readonly operationFailed: (operation: DailyReportOperation) => string;
146
+ readonly operationRefused: (operation: DailyReportOperation) => string;
145
147
  readonly noticeRepeated: (count: number) => string;
146
148
  readonly rowState: (state: {
147
149
  readonly readState: DailyReportReadState;
@@ -217,6 +219,7 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
217
219
  "reload",
218
220
  "loadingIds",
219
221
  "streamFailed",
222
+ "liveUpdatesRefused",
220
223
  "streamRetry",
221
224
  "streamRevalidating",
222
225
  "selectPrompt",
@@ -275,6 +278,7 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
275
278
  "reportSummaryLoadFailed",
276
279
  "interviewerWithAffiliation",
277
280
  "operationFailed",
281
+ "operationRefused",
278
282
  "noticeRepeated",
279
283
  "rowState",
280
284
  "rowEditingState",
@@ -364,7 +368,8 @@ type DailyReportHeaderProps = {
364
368
  annotation?: ReactNode;
365
369
  };
366
370
  type DailyReportErrorInfo = {
367
- operation: DailyReportOperation;
371
+ operation: DailyReportOperation | "liveUpdates";
372
+ reason: "failed" | "refused";
368
373
  message: string;
369
374
  cause?: unknown;
370
375
  };
@@ -413,6 +418,7 @@ type DailyReportPageProps = {
413
418
  user?: DailyReportUser;
414
419
  userId?: string | null;
415
420
  config: DailyReportClientConfigInput;
421
+ sessionEpoch: number;
416
422
  };
417
423
  declare const DailyReportPage: ({ config, ...rest }: DailyReportPageProps) => react.JSX.Element;
418
424
  type DailyReportResolvedContentProps = {
@@ -427,8 +433,10 @@ type UseDailyReportSseConnectionOptions = {
427
433
  onMessage: (payload: Exclude<DailyReportSseMessage, {
428
434
  type: "connected";
429
435
  }>) => void;
436
+ onSessionEnded: () => void;
437
+ onRefused: () => void;
430
438
  };
431
- declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage }: UseDailyReportSseConnectionOptions) => DailyReportSseConnectionStatus;
439
+ declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage, onSessionEnded, onRefused }: UseDailyReportSseConnectionOptions) => DailyReportSseConnectionStatus;
432
440
  type ReportBusinessDate = DailyReportDetail["date"];
433
441
  type DailyReportActionFunctions = {
434
442
  toggleSse: () => void;