@aiquants/daily-report 0.49.0 → 0.51.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,81 @@
2
2
 
3
3
  All notable changes to `@aiquants/daily-report` are documented here.
4
4
 
5
+ ## Unreleased
6
+
7
+ ### Breaking
8
+
9
+ - **的の大きさと配置がどのポインターでも同じ (マウス・トラックパッド・ペンでも 44 × 44 CSS px。WCAG 2.5.5)**: 0.50.0 で粗いポインター
10
+ (`(pointer: coarse)`) にだけ当てた的の箱と配置が、媒体条件の無い規則になる。操作ボタン (List のカード・DetailList の見出し・側面ペインのスター・既読・編集・削除、
11
+ コメントのゴミ箱) は 44px の丸い的、ビューと側面ペインのタブ・作成のボタン・検索の切り替えと入力欄と消去のボタン・コメントの入力欄と送信・削除の確認・オーバーレイと
12
+ 誤りの知らせの閉じるボタン・2 つの再試行・編集フォームの `Button` と `Input`・添付のダウンロードと行のリンクは 44px 以上の箱 (`TARGET_CLASS_NAME`)、自動既読のスイッチは
13
+ 中央の 44 × 44 の当たりを持つ。List のカードは 2 列の格子 (スターと既読はプレビューの右の 2 行目。行の枠の高さ P は根 16px で 160 のまま)、側面ペインの操作ボタンは件名の
14
+ 下の自分の 44px の行、操作列の帯の部品は 48px で、操作列の並びは 4 + 48 + 4 = 56px (折り返せば 112px)、検索の行の 1 行目は 56px、ビューの幅が 320px 未満なら検索の
15
+ 絞り込みの切り替えは自分の行へ折り返す。添付のタイルの最後の行は 48px (タイルは h + 80)、コメントの削除の枠と失敗の札は 44px の行。デスクトップのホストでは操作列の帯が
16
+ 32px から 56px (検索の行があれば 64px から 112px) になってビューの上端が下がり、DetailList の見出し・コメント・側面ペイン・List のカードの並びと高さが変わる。
17
+ 大きさの段の高さが的を下回る部品は高さのクラスを持たない (的の箱だけが大きさを決める。操作列の作成のボタンは `Button` の大きさの段を持たない)。
18
+ - **的のトークンの名前から `COARSE_` を外した** (どれもパッケージの外へは公開しない): `COARSE_TARGET_CLASS_NAME` → `TARGET_CLASS_NAME`、
19
+ `COARSE_FIELD_TARGET_CLASS_NAME` → `FIELD_TARGET_CLASS_NAME`、`COARSE_TARGET_LINE_CLASS_NAME` → `TARGET_LINE_CLASS_NAME`、
20
+ `COARSE_LATTICE_TARGET_CLASS_NAME` → `LATTICE_TARGET_CLASS_NAME`、`SEARCH_FILTERS_TOGGLE_COARSE_WRAP_CLASS_NAME` → `SEARCH_FILTERS_TOGGLE_WRAP_CLASS_NAME`。
21
+ 細かいポインターのための値は消した: コメントのゴミ箱の負の余白 `ACTION_BUTTON_INLINE_INSET_CLASS_NAME`、操作ボタンと区切りのトリガーと検索の欄と切り替えの 24px の高さ、
22
+ 添付のダウンロードの `after:` の 24px の的、List のカードの折り返す flex の行と、目印と切り替えの 2 つの群を包んでいた操作の並び (カードの要素は 1 つ減る)、
23
+ 切り替えの的の大きさの探りの高さ 0 (`TARGET_SIZE_PROBE_CLASS_NAME` は `h-[44px]`)。
24
+
25
+ ### Changed
26
+
27
+ - **粗いポインターの媒体条件のまま残す規則は、欄の 16px の文字 (`pointer-coarse:text-base`) だけ**: 編集フォームの `Input` と `Textarea`・コメントと検索の
28
+ 入力欄は、粗いポインターでは 16px (iOS の Safari のフォーカスでの拡大を防ぐ)、細かいポインターでは 14px のまま。Tailwind でクラスを作るホストが v4.1 以上を
29
+ 要るのはこの規則のため (それより前の v4 では 16px の文字が作られない)。的の例外 (List のリサイズの取っ手とスクロールバー・ホストの絞り込みの選択の部品・
30
+ 開発用の箱の再読み込みとキャッシュの消去・VirtualScroll のタップスクロールの円) は今のままで、2.5.5 は全体としては主張しない。
31
+
32
+ ## 0.50.0 (2026-10-24)
33
+
34
+ ### Breaking
35
+
36
+ - **`@aiquants/swipe-overlay` のピアの下限が、`useSwipeOverlayDialog`・`useOverlayPresence`・`SWIPE_OVERLAY_SCROLL_AREA_STYLE` を持つ版へ上がる**。
37
+
38
+ ### Changed
39
+
40
+ - **粗いポインター (`(pointer: coarse)`) では日報のどの操作の的も 44 × 44 CSS px 以上 (`TARGET_SIZE_PX` = 11u。WCAG 2.5.5)**: 操作ボタン (List のカードと DetailList の
41
+ 見出しと側面ペインのスター・既読・編集・削除、コメントのゴミ箱) は 44px の丸い的、ビューのタブと側面ペインのタブ (側面ペインの並びは 4 + 44 + 4 = 52px)・作成のボタン・検索の切り替えと入力欄と
42
+ 消去のボタン・コメントの入力欄と送信・削除の確認・オーバーレイと誤りの知らせの閉じるボタン・2 つの再試行・編集フォームの `Button` と `Input`・添付のダウンロードと行のリンクは
43
+ 44px 以上の箱 (`COARSE_TARGET_CLASS_NAME`。最小の大きさで育つ)。自動既読のスイッチは見た目の 40 × 24 のまま中央の 44 × 44 の当たりを持つ。List のカードは粗いポインターで
44
+ 2 列の格子になり、スターと既読をプレビューの右 (2 行目) へ移すので、行の枠の高さ P は根 16px で 160 のまま (`listRowSlotHeight(R, T)` = 4⌈(52 + 2R + max(4.75R, T)) / 4⌉。T は
45
+ 粗いポインターでだけ高さを持つ隠れた探りから読む)。側面ペインの操作ボタンは件名の下の自分の 44px の行へ移り、添付のタイルの最後の行は 48px (44 を 8 へ切り上げ、タイルは
46
+ 装置の画素の格子に乗ったまま)。操作列の帯の部品 (ビューのタブ・作成のボタン・自動既読のスイッチの行・検索の入力欄と 2 つの切り替え) も同じ 48px の高さ
47
+ (`LATTICE_TARGET_SIZE_PX`。44 のままでは 2.625 の画素密度で部品の下の縁が装置の画素の間に落ちる) で、操作列の並びは 4 + 48 + 4 = 56px (2 行に折り返せば 112px)、
48
+ 検索の行の 1 行目は 8 + 48 = 56px。ビューの幅が 320px 未満なら検索の絞り込みの切り替えは自分の行へ折り返す (境目は粗いポインターの行の最小の幅から求めた値)。細かいポインター
49
+ (マウス・トラックパッド・ペン) では的も配置も変わらず 24px (2.5.8) なので、2.5.5 は粗いポインターでだけ満たし、全体としては主張しない。例外: List のリサイズの取っ手と
50
+ スクロールバー・ホストの絞り込みの選択の部品・開発用の箱の再読み込みとキャッシュの消去 (24 × 24 の的。同じ箱の SSE のスイッチは中央の 44 × 44 の当たりを持ち、
51
+ 粗いポインターでは箱の項目の間を 4 + (44 − 40) / 2 = 6px に広げて、当たりが再読み込みの的に重ならない)・VirtualScroll のタップスクロールの円。オーバーレイの閉じるボタンは
52
+ 粗いポインターで 36 → 44px。Tailwind でクラスを作るホストは、`pointer-coarse:` のユーティリティを Tailwind CSS v4.1 以上で生成する (それより前の v4 では粗いポインターの規則が
53
+ 作られず、的は細かいポインターの大きさのまま)。
54
+ - **入力欄の文字の大きさは画面の幅ではなくポインターで決まる**: 粗いポインターでは 16px (iOS のフォーカス時の拡大を防ぐ)、細かいポインターでは 14px (編集フォームの `Input` と
55
+ `Textarea` の幅で決めていた `md:text-sm` を外した)。検索の入力欄に `enterkeyhint="search"`、コメントの入力欄に `"send"`。オーバーレイの中身は上下左右に 16px と
56
+ `env(safe-area-inset-*)` の大きい方だけ内側へ寄る。
57
+ - **モバイルの詳細オーバーレイのダイアログの部品を `@aiquants/swipe-overlay` の共有のフックへ移した**: ダイアログの開閉 (`showModal()`・`close()`・`inert`)・
58
+ 最初のフォーカス (日報の見出し)・閉じる要求の経路 (Escape・`cancel`・プラットフォームが強いた閉じ・背景・スワイプ) は `useSwipeOverlayDialog`、閉じて滑り出す間の
59
+ マウントは `useOverlayPresence` (閉じてから `SWIPE_OVERLAY_EXIT_MS` = 300 ms)。`@aiquants/contact-map` の詳細のオーバーレイと同じフックで、振る舞いは
60
+ 変わらない (下の修正を除く)。Tab の巡回・誤りの知らせの置き場・自動既読の留まり・履歴の項目はパッケージが持ったまま。
61
+
62
+ ### Fixed
63
+
64
+ - **モバイルのオーバーレイの中でピンチの拡大が効く**: 面は `touch-action: none`、側面ペインのタブの中身は `pan-y` で、面の上では 2 本の指の拡大まで止めていた
65
+ (背景は `manipulation`)。面と背景は `pinch-zoom`、タブの中身は `pan-y pinch-zoom` になり、オーバーレイのどこでもピンチでページを拡大できる (横の動きは閉じるスワイプのまま)。
66
+ デスクトップの配置の側面ペインのタブの中身も `pan-y` を外し (スワイプが無いので触れ方を絞らない)、タッチの画面でピンチの拡大が効く。
67
+ - **オーバーレイの中のスクロールが背後のページへつながらない**: 側面ペインのタブの中身 (オーバーレイの縦のスクロール領域) は `overscroll-behavior-y: contain` を持ち、
68
+ 端まで送ったスクロールはページへつながらない (Android の引いて再読み込みにならない)。面と背景の上の縦の動きも何も引っ張らない。
69
+ - **IME の変換を終える Escape が、重いページでもオーバーレイを閉じない**: オーバーレイが断った Escape (IME の変換を終えるもの、閉じるのを
70
+ 拒まれたもの) が起こすプラットフォームの閉じる要求は、キーから 50ms 以内なら捨てていたため、キーの処理に 50ms より掛かるとその要求で閉じた。
71
+ 要求はキーの既定動作として同じタスクの中で届くので、断った Escape のタスクの中の要求を捨てる (`@aiquants/swipe-overlay` の
72
+ `useSwipeOverlayDialog`)。後のタスクの要求 (Android の戻る) は捨てない。
73
+ - **スワイプで閉じた直後や、オーバーレイの見出しの帯・背景を引いた直後のタップが効く**: ブラウザーはパンできない 1 本の指の動きを自分のパンの
74
+ 試みとして扱い続け、離してからおよそ 1 秒のタップを `click` にしないことがあった (閉じた直後に次の日報のカードをタップしても開かない)。
75
+ その `touchmove` をページが取る (`@aiquants/swipe-overlay`。閉じるスワイプと決まった後の動きと、面のスクロールの領域の外と背景の 1 本の指の動き)。
76
+ - **端末を回してもオーバーレイの履歴の項目が溜まらない**: オーバーレイを開いたまま配置がデスクトップへ切り替わる (端末を横にする) と、オーバーレイは
77
+ 閉じても自分が積んだ履歴の項目を残し、回し戻して開き直すたびに同じ URL の項目が増えて、戻る操作が何もしないように見えた。デスクトップの配置への
78
+ 切り替えで閉じるときも、積んだ項目を戻す。
79
+
5
80
  ## 0.49.0 (2026-10-21)
6
81
 
7
82
  ### Breaking
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 fields' 16 px text on touch screens (**Target size** in Selection and focus appearance) is a
184
+ `pointer-coarse:` utility, a variant Tailwind added in v4.1; an older v4 build does not generate it, so focusing a field zooms the page on a touch
185
+ browser.
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`, ...).
@@ -2201,7 +2205,7 @@ failed, and the filter row while it is open.
2201
2205
  Enter, Esc and the debounce wait while an IME composes.
2202
2206
  The filter row is inside the landmark, so its cells belong to the search for assistive technology and Tab goes from the field to the filter toggle and
2203
2207
  on into the cells; the landmark has no name (the screen has one search landmark), and the filter row's own name comes from the filter toggle.
2204
- The row's first line is one 32 px lattice block whose 24 px controls stand at its bottom, so they sit G = 8 under the toolbar's bar and, while the
2208
+ The row's first line is one 56 px lattice block whose 48 px controls stand at its bottom, so they sit G = 8 under the toolbar's bar and, while the
2205
2209
  filter row is closed and no search failed, their bottom edge is G above the view's first surface; its second line holds the failure line.
2206
2210
  The band carries the toolbar's start inset G and the active view's end inset, so the field ends where the cards end in both views, and it stacks above
2207
2211
  the view, so a control's focus outline paints over the view's G strip (**Origin on the lattice** in
@@ -2224,7 +2228,7 @@ while the screen shows searching as the indicator and a failure on the failure l
2224
2228
  line wraps instead of truncating;
2225
2229
  it comes first in the field's description (before the syntax help), and the field is `aria-invalid` only for an invalid query (busy, unavailable and
2226
2230
  other failures are no input errors).
2227
- The AND / OR toggle and the filter toggle share one look: a 24 px pill with 12 px semibold text, in the field face (OR; the closed filter row), whose
2231
+ The AND / OR toggle and the filter toggle share one look: a 48 px tall pill (the band's control height) with 12 px semibold text, in the field face (OR; the closed filter row), whose
2228
2232
  boundary comes from the text-field token as the field's does, or the filled face (AND; the open filter row), blue-800 under white text (dark: a light
2229
2233
  blue-300 fill under slate-950 text) and `Highlight` under `HighlightText` in forced colours (**Text fields** in [Selection and focus appearance](#selection-and-focus-appearance)).
2230
2234
  The faces differ only in colour, so a toggle that changes its face moves nothing. Each toggle stacks its current label and an invisible reserved label
@@ -3362,8 +3366,8 @@ the resize handle and the side pane lie outside the list.
3362
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
3363
3367
  leaves such a widget without stopping at it), and Escape closes it unless the key belongs to an IME composition.
3364
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
3365
- 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
3366
- 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),
3367
3371
  the close path runs too, and the dialog opens modally again when the close is refused.
3368
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
3369
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
@@ -3376,6 +3380,13 @@ the resize handle and the side pane lie outside the list.
3376
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
3377
3381
  layer (`data-daily-report-overlay-backdrop`, `aria-hidden`, without a role or a name, not focusable): pressing it closes the overlay, while keyboard
3378
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.
3379
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
3380
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
3381
3392
  the element's own window.
@@ -3450,12 +3461,14 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3450
3461
  `resolution: <dpr>dppx` of each of the twelve ratios with `calc(⌊2·dpr⌋px / dpr)`), and the lift reads it
3451
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
3452
3463
  compile the same rules.
3453
- - **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
3454
- 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
3455
- stays on the lattice.
3456
- R is read from the root element's computed style and read again whenever a hidden 1 rem probe inside the List
3457
- (`data-daily-report-root-font-size-probe`) changes size, and the List renders its `VirtualScroll` only once P is known (measured before the first
3458
- paint).
3464
+ - **List slot P**: the List card's content is sized in rem (its toggles in px), 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 44, the toggles beside the preview (**List card** below); at a 16 px root 4.75 rem = 76 px is taller than 44, so P is 160.
3468
+ R is read from the root element's computed style and T from the height of a hidden 44 px probe (`data-daily-report-target-size-probe`, 0 until it
3469
+ is laid out); one `ResizeObserver` reads both again whenever the hidden 1 rem probe
3470
+ (`data-daily-report-root-font-size-probe`) or the target probe changes size, and the List renders its `VirtualScroll` only once P is known
3471
+ (measured before the first paint).
3459
3472
  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
3460
3473
  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
3461
3474
  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
@@ -3490,13 +3503,14 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3490
3503
  8 px (2 u), and 440 px (110 u) until it has measured one; the view applies a changed estimate only at rest, with the first visible report kept in
3491
3504
  place — also on the lattice at every ratio of the domain, so the unmeasured rows above the window move the rows below by whole device pixels. An image attachment tile is padded to q as well (**Tile** in
3492
3505
  [Attachment display](#attachment-display)), so the rows below an image grid stay on whole device pixels.
3493
- - **List card**: a wrapping row of the primary button and the action row (markers and toggles), with the preview always on the next line. The primary
3494
- button takes the remaining width but never less than 96 px, enough for the business-date pill, and the action row keeps to the card's end; on a card
3495
- too narrow for both (a phone with a pinned host menu), the action row wraps under the button instead of squeezing it to nothing, and the card clips
3496
- the preview lines that no longer fit.
3506
+ - **List card**: a two-column grid (`minmax(0, 1fr)` and `auto`): the primary button and the markers share the header line, and the 44 px star and
3507
+ 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 160 at a
3508
+ 16 px root. The primary button takes the first column but never less than 96 px, enough for the business-date pill, and the card clips what no
3509
+ longer fits. The markers and the toggles are two groups that are the grid's own items; no element wraps them.
3497
3510
  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 ×
3498
3511
  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 +
3499
3512
  1 ≤ P − 2G with at least 4 px to spare at every root font size (140 ≤ 144 at 16 px).
3513
+
3500
3514
  - **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
3501
3515
  (−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
3502
3516
  (`calc(var(--radius-2xl) + 8px)`, 24 at a 16 px root), so the line is concentric with the surface at every root font size.
@@ -3732,7 +3746,7 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3732
3746
  The frame height is not snapped to 4 px: snapping moves the frame's shape more than 0.01 away from 3 : 2 (196 → 132 gives |t / h − 3 / 2| = 0.0152,
3733
3747
  171 → 116 gives 0.026) and letterboxes a 3 : 2 image, which fills an unsnapped frame exactly.
3734
3748
  The tile around the frame is on the lattice anyway: its last line carries the pad δ(t) = ⌈h / q⌉ · q − h (0 to q − 1 px, q the block quantum of
3735
- **Row slots on the lattice**) as a bottom margin, so every tile is ⌈h / q⌉ · q + 48 px tall, every tile row top and every grid's height are
3749
+ **Row slots on the lattice**) as a bottom margin, so every tile is ⌈h / q⌉ · q + 80 px tall, every tile row top and every grid's height are
3736
3750
  multiples of q, and what follows a grid stays on whole device pixels (**Tile** in [Attachment display](#attachment-display)).
3737
3751
  A bordered box counts its border in its inset, so that its content edge sits on the grid measured from the visible edge: border + padding is on the
3738
3752
  ladder — 16 for the card surfaces, the error panel, the sides of the error notice and the mobile overlay's close button (1 + 15), 8 for the form
@@ -3743,8 +3757,8 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3743
3757
  posted comment and the bordered pill (1 + 11); a 24 px pill has the same 12 px padding at its round ends. The tab bars are not in that tier: their 4
3744
3758
  px inset makes them concentric with their 8 px tabs (12 − 4 = 8).
3745
3759
  The only other exceptions are the visually hidden text (Tailwind's `sr-only`, and inside virtualized rows and for every attachment file name the
3746
- 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
3747
- 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
3760
+ package's transparent 1 px box, which clips nothing and contains its own layout) and the 68 px the search field keeps free at its end for the
3761
+ 24 px searching indicator and the 44 px clear target inside its box. `src/client/ui/spacing-ladder.guard.spec.ts` checks every class the package writes, that every
3748
3762
  bordered box's border + padding is on the ladder or equals its corner, and that every 12 px-corner panel's inset is 12.
3749
3763
  - **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
3750
3764
  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
@@ -3753,14 +3767,14 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3753
3767
  8 px at the odd multiples of 1/8), so every inline edge built on the origin — G, the ladder steps of 8 and 16, the card, the row frame — starts on a
3754
3768
  whole device pixel: on a 412 px phone at 2.625 a DetailList surface's left edge is 16 px in, at device pixel 42.
3755
3769
  Below the column's top inset the view-mode toolbar sits in its band, as tall as its content rounded up to q (the search row, when configured, is
3756
- 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 =
3757
- 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.
3758
- 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
3759
- 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
3760
- view's top paints over the view's G strip.
3761
- 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,
3762
- 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
3763
- inherited the half pixel).
3770
+ inside the band: one more 56 px block), so the views start q + the band below the column's top — 4 + 56 = 60 px at the multiples of 1/4 and 8 + 56 =
3771
+ 64 px at the odd eighths, or 4 + 112 = 116 px and 8 + 112 = 120 px with the search row — on the lattice at every ratio.
3772
+ The band's controls are 48 px (44 rounded up to 8), so the bar is 4 + 48 + 4 = 56 px (112 px when it wraps) and the search row's first line
3773
+ 8 + 48 = 56 px, its controls at its bottom G = 8 under the bar and G above the view's first surface: all multiples of 8, so every edge inside the
3774
+ band, not only its bottom, lands on a whole device pixel at every ratio. The band stacks above the views (`relative z-[1]`), so the 4 px focus
3775
+ outline of a search control that ends at the view's top paints over the view's G strip.
3776
+ At 1280 × 800 and 1.125 under a 32 px host header the toolbar, the view and the first row surface therefore start at device pixels 45, 108 and 117
3777
+ (with a 4 px inset and no band they would start half a device pixel off, and every row edge, ring and outline below would inherit the half pixel).
3764
3778
  Still off the lattice at the odd eighths (measured, open): an inline edge at an odd multiple of 4 px inside a surface (the 12 px tier's insets, the
3765
3779
  4 px insets of the tab bars and of the toolbar inside its band); the focus outline's 4 px offset, 10.5 device pixels at 2.625, which no CSS length
3766
3780
  moves, since Chromium 148 rounds `outline-offset` down to whole CSS px (`calc(10px / 2.625)` gives 3 px); and the positions the browser chooses
@@ -3809,8 +3823,8 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3809
3823
  - **Tab bars are one segmented control**: the side pane's tabs and the view-mode toolbar share one geometry. The bar has a 12 px corner (`rounded-xl`)
3810
3824
  and a total inset of 4 px that counts its border (4 px padding without a border; 1 px border + 3 px padding with one), the same at the top and at
3811
3825
  the sides.
3812
- The tabs are 24 px tall with 12 px labels and an 8 px corner (`rounded-lg`), so both bars are 4 + 24 + 4 = 32 px tall and every tab is concentric
3813
- with its bar: 12 − 4 = 8. Because xl − lg = 4 equals the inset both in Tailwind's scale and in a host `--radius` scale (lg = `--radius`, xl =
3826
+ The tabs are 44 px tall (the target T; 48 px in the toolbar's band) with 12 px labels and an 8 px corner (`rounded-lg`), so the side pane's bar is
3827
+ 4 + 44 + 4 = 52 px and the toolbar's 4 + 48 + 4 = 56 px tall, and every tab is concentric with its bar: 12 − 4 = 8. Because xl − lg = 4 equals the inset both in Tailwind's scale and in a host `--radius` scale (lg = `--radius`, xl =
3814
3828
  `--radius` + 4 px), the pair stays concentric for any host radius; the toolbar's children at its end corners (the create button, the development
3815
3829
  box) are `rounded-lg` too.
3816
3830
  - **View-mode toolbar** (`DailyReportResolvedContent`, above every view): its band carries the insets, so the toolbar and the search row share them:
@@ -3865,33 +3879,58 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3865
3879
  refuses one without a registered surface, and measures each against its surfaces. The quiet text (section labels, metadata, placeholders, the empty
3866
3880
  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
3867
3881
  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
3868
- 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
3869
- 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.
3882
+ 7.07–9.75). A source badge's colours are the host's (**External Source Badge Configuration**). Targets reach WCAG 2.5.5's 44 px under every pointer
3883
+ (**Target size** below), except the List's resize handle with its 10 px hit area and the 8 px scroll bar beside it (**Side pane** above) and the
3884
+ other exceptions listed there, so 2.5.5 is not claimed overall.
3870
3885
  - **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
3871
3886
  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
3872
3887
  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
3873
3888
  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
3874
3889
  3.3.6. Not met, by decision: 2.2.4 (Interruptions): live updates (SSE) apply as they arrive, with no pause, and
3875
- 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
3876
- 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
3890
+ 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**
3891
+ above), which keep their thin hit areas. Not claimed overall: 2.5.5, whose 44 px every other target of the package meets under every pointer
3892
+ (**Target size** below), the exceptions listed there aside.
3893
+ Not claimed yet: 1.4.8 (a line measure and the List preview's
3877
3894
  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
3878
3895
  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
3879
3896
  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
3880
3897
  `sessionEpoch`, and the package resumes and sends nothing again by itself ([Client wiring](#client-wiring)). 2.2.6 (Timeouts) is the host's: the
3881
3898
  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
3882
3899
  `VirtualScroll`'s tap-scroll circle options).
3900
+ - **Target size (every pointer)**: under every pointer (mouse, trackpad, pen or finger) every target is at least 44 × 44 CSS px (WCAG 2.5.5's enhanced
3901
+ size, T = 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
3902
+ the side pane's tabs (the side pane's bar is 4 + 44 + 4 = 52 px), the create button, the search box's toggles, field and clear button, the comment field
3903
+ 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,
3904
+ and the attachments' download and row links (a tile's last line is 48 px, 44 rounded up to 8, so tiles stay on the device-pixel lattice). The toolbar band's
3905
+ 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
3906
+ 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
3907
+ is 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
3908
+ 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
3909
+ does not hold a 44 × 44 square (a bordered control about 44 px wide adds `before:-inset-px` so the square also covers its border,
3910
+ as the search box's pills and the List's mobile overlay close button do). Each box grows through a minimum size, so a control that declares a larger
3911
+ size keeps it (the edit form's `Button` sizes). The auto-read switch keeps its 40 × 24 look and gets a centred
3912
+ 44 × 44 hit area in the band's 48 px line; the side pane's action buttons take a 44 px line of their own under the subject, at its end, and the List
3913
+ card puts its toggles beside the preview (**List card** above). Exceptions: the List's resize handle and the views' scroll bars, the host's filter
3914
+ select controls, the dev toolbox's reload and clear-cache icon buttons (24 × 24 targets in its 24 px box; its SSE switch has the switch's centred
3915
+ 44 × 44 hit area, and the toolbox spaces its items 6 px apart, 4 + (44 − 40) / 2, so that hit area does not cover the reload button's target) and
3916
+ `VirtualScroll`'s tap-scroll circle. Text fields use 16 px text under a coarse pointer (14 px under a fine one), so focusing them does not zoom the
3917
+ page on touch browsers; this text is the only rule the package keys on the pointer, and a Tailwind host generates it from Tailwind CSS v4.1 on
3918
+ (**Tailwind v4 host** above). The search field's Enter key is labelled as search (`enterkeyhint="search"`) and the comment field's as send
3919
+ (`enterkeyhint="send"`). When the views are narrower than 320 px (the search row's smallest width: the band's insets, both toggles, two 8 px gaps
3920
+ and a field that 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
3921
+ content at least its 16 px padding and at least the display's safe-area insets (`env(safe-area-inset-*)`) from each edge.
3883
3922
  - **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
3884
- 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
3885
- 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
3886
- pills and the source badge are 24 px tall like the targets.
3923
+ pane, and the trash button of the viewer's comments share one 44 px round target (**Target size** above), which never shrinks, with a 16 px SVG
3924
+ icon centred in it by flexbox (14 px on every side), so the icons do not depend on a font. In the List card the markers and toggles stand 4 px
3925
+ apart; the header pills and the source badge are 24 px tall (they are not targets).
3887
3926
  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,
3888
3927
  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
3889
3928
  hover background included, in both themes; in forced colours the star is drawn in `CanvasText` (off) and `Highlight` (on). The side pane shows them
3890
- in the order star, read, edit, delete, centred on the first line of the subject.
3929
+ in the order star, read, edit, delete, on a 44 px line of their own under the subject, at its end.
3891
3930
  Unread is shown by the dot on the read toggle and by the row state in the description; no surface changes its border or its layout for it (a card's
3892
3931
  border is the same 1 px in every state).
3893
- The comment's trash button takes the 16 px icon's place in the 20 px line of the comment header (its 24 px target overhangs by 2 px, so the header
3894
- keeps its height), turns red while its confirmation waits (at least 3.59:1 on the comment surfaces), and the confirmation pill carries the control
3932
+ The comment's trash button sits in a 44 px line of the comment header that holds its whole 44 px target (an overhanging target would cover the
3933
+ comment above and the text), turns red while its confirmation waits (at least 3.59:1 on the comment surfaces), and the confirmation pill carries the control
3895
3934
  focus outline.
3896
3935
 
3897
3936
  ### Attachment display
@@ -3937,9 +3976,9 @@ luminance by 0.026 in the light theme, and a `shadow-md` brings the ring down to
3937
3976
  pixel ratio of 2).
3938
3977
  - **Tile**: one link to the original wraps the thumbnail frame and the file name; below it one line holds the size and the download link. The frame is
3939
3978
  decorative (`aria-hidden="true"`, no link of its own), so the tile link is named by the file name and a tile has two Tab stops (the preview, then
3940
- the download). The download link has a 24 px hit area, and its accessible name is the download label followed by the file name.
3979
+ the download). The download link's own box is its 44 × 44 target, and its accessible name is the download label followed by the file name.
3941
3980
  The tile root (the grid item) is the tile's one inline-size container, exactly as wide as its track, so `100cqi` inside the tile is t. A tile is the
3942
- frame (h, below), the 8 px gap, the 20 px name, the 4 px gap and the 16 px last line, h + 48 px in all (48 and the grid gap of 16 are multiples of
3981
+ frame (h, below), the 8 px gap, the 20 px name, the 4 px gap and the 48 px last line, h + 80 px in all (80 and the grid gap of 16 are multiples of
3943
3982
  8), and h need not be a multiple of 4.
3944
3983
  The last line therefore carries a bottom margin, the lattice pad δ(t) = ⌈h / q⌉ · q − h
3945
3984
  (`margin-block-end: calc(round(up, h, var(--aqdr-lattice-block)) - h)`, where h is the frame height's own expression, resolved against the same