@aiquants/daily-report 0.46.1 → 0.47.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,49 @@
2
2
 
3
3
  All notable changes to `@aiquants/daily-report` are documented here.
4
4
 
5
+ ## 0.47.0 (2026-10-16)
6
+
7
+ 版番号は 0.47.0 (minor) とする: 状態の表に列を求め、公開の型・アクション・SSE のワイヤの形を変える (下の Breaking)。
8
+
9
+ ### Added
10
+
11
+ - **既読の後の更新**: 閲覧者が既読にした後に日報の中身が変わると、閲覧者に見分けられるようにする。サーバーは日報を読むたびに日報自身の中身の版
12
+ (`DailyReportDetail.contentDigest`: 件名・本文・営業日・区分・作成区分・訪問時間・顧客名・面談者・添付の名前と大きさと登録日時を、届く JSON のとおりに
13
+ 並べた SHA-256。小文字の 16 進 64 桁) を求め、既読の印は閲覧者の画面が見せている版を閲覧者の状態の行へ記録する (`DailyReportDetail.readContentDigest`)。
14
+ コメント・ラベル (下書きの公開)・人と時刻の欄・スターと既読は版を変えないので、コメントや同じ中身の取り込み直しは更新に数えない。
15
+ 記録した版が今の版と違えば、持ち主でない閲覧者には両ビューのカードと側面ペインの操作の列に札 (`labels.updatedSinceRead`: `Updated` / `更新あり`。
16
+ `data-daily-report-updated-since-read`、読み上げから隠す) と既読の切り替えの更新のアイコンを見せ、切り替えの名前は `labels.markUpdateRead`
17
+ (`Mark the update as read` / `更新を既読にする`)、行の状態は `Read, updated since` / `既読後に更新あり`。押すと既読のまま版を今の版へ進める。
18
+ 持ち主は自分の書いた更新を知らされない。側面ペインの自動既読も、既読の後に更新された日報を今の版で既読にする。
19
+ ほかの利用者の更新は SSE で新しい中身の版として届き、閲覧者の読んだ版は手元の値を保つので、読み直さずに既読の後の更新になる。
20
+ 共有の入口は、公開した宣言が参照する `DailyReportReadMark`・`DailyReportReadState`・`DailyReportReadStatus` を型として公開する。
21
+
22
+ ### Breaking
23
+
24
+ - **状態の表に読んだ版の列が要る**: `DailyReportUserStatus` に `read_content_digest char(64) NULL` を足してから上げる
25
+ (`defineDailyReportSchema` は宣言する)。注入する表の `tables.userStatus.readContentDigest` が無いか `char(64)` でなければ、サービスの生成のときに
26
+ `RangeError` (`[daily-report] tables.userStatus.readContentDigest must be a char(64) column (the version the viewer marked read, 64 lowercase hex digits); got <SQL の型>`)
27
+ で止まる。列の無かった頃に書かれた既読の行は版を持たず (NULL)、既読として読む。
28
+ Migration: `ALTER TABLE <schema>.DailyReportUserStatus ADD read_content_digest char(64) NULL` を流し、ホストの drizzle のモデルに
29
+ `readContentDigest: char("read_content_digest", { length: 64 })` を足す。
30
+ - **`DailyReportDetail` に必須の `contentDigest: string` と `readContentDigest: string | null` が増える**: 詳細の読み手 (`dailyReportDetailSchema`) は
31
+ どちらも既定値で補わずに求める。Migration: 詳細を自分で組むホスト (試験の代役を含む) は 2 つの欄を詰める。
32
+ - **既読の切り替えは既読の印を受ける**: クライアントの `toggleRead(reportHubId, mark, businessDate)` とサービスの
33
+ `setReadStatus(scope, userId, reportHubId, businessDate, mark, clientTempId)` は真偽の代わりに `DailyReportReadMark`
34
+ (`{ isRead: true, contentDigest }` か `{ isRead: false }`) を受ける。サービスは形の誤った版を、どのクエリよりも前に `RangeError`
35
+ (`[daily-report] setReadStatus: contentDigest must be 64 lowercase hex digits; got <値>`) で断る。action の `toggleRead` のフォームは `isRead=true` のとき
36
+ `contentDigest` を求め (`contentDigest required`・`Invalid contentDigest`)、`isRead=false` のときは拒む (`contentDigest is not allowed when isRead is false`)。
37
+ スターと既読の切り替えの答えの `updatedStatus` は `readContentDigest` を持つ。アクションの台帳の `pendingReadUpdates` の値は真偽から
38
+ `DailyReportReadStatus` (`{ isRead, readContentDigest }`) になる。
39
+ Migration: 自分で既読を書くホストは、見せている詳細の `contentDigest` で `{ isRead: true, contentDigest }` を渡す (未読は `{ isRead: false }`)。
40
+ - **ラベルの `rowState` は読みの状態を受ける**: `rowState({ readState, isStarred })` (`readState` は `unread`・`read`・`updatedSinceRead`)。
41
+ ラベルのキーが 2 つ増える (`markUpdateRead`・`updatedSinceRead`。固有のキーは 113、全体は 124)。
42
+ Migration: `rowState` を上書きするホストは `isRead` の代わりに `readState` を読む。
43
+ - **SSE のワイヤの版は v5**: ストリームのキーは `<接頭辞>:sse-stream:v5`。日報を埋め込む知らせの詳細は 2 つの版を、状態の知らせ (`status-update`) は
44
+ 書いた後の行の `readContentDigest` を必須で運ぶ。v4 のストリームは書かれなくなり (手で消せる)、配備をまたいで開いていた前の版のタブは読み直すまで
45
+ SSE の更新を受けない。データベースへ書くワーカーは一緒に配備する。
46
+ - **依存の `@aiquants/sse` は ^0.3.0**: 公開の範囲が ^0.2.0 から上がる (0.3.0 はサーバーの `createSseResponse` に、省けば切ってある `compressible` を足しただけで、日報の振る舞いは変わらない)。
47
+
5
48
  ## 0.46.1 (2026-10-16)
6
49
 
7
50
  版番号は 0.46.1 (patch) とする: 公開の型は変えず、外部ソースの日報へ誤って書き込む経路を閉じる。
package/README.md CHANGED
@@ -30,6 +30,7 @@ domain-specific knowledge. It is one module with four entries:
30
30
  - [Source-type visibility](#source-type-visibility)
31
31
  - [Contract](#contract)
32
32
  - [Comment visibility](#comment-visibility)
33
+ - [Read after update](#read-after-update)
33
34
  - [Attachments](#attachments)
34
35
  - [Configuration](#configuration)
35
36
  - [Read port](#read-port)
@@ -229,6 +230,11 @@ const tables = defineDailyReportSchema("dbo_app", { userTable: Users })
229
230
  (`[daily-report] tables.hub.sourceType must be an nvarchar(n) column with a declared length (source types compare as UTF-16 bytes); got "<SQL type>"`).
230
231
  Every write to an existing report starts its transaction with an update of the report's live hub row that changes no value and outputs the row, so
231
232
  the hub's `update` must support drizzle's `output()` (a hand-written database stand-in answers it).
233
+ `DailyReportUserStatus.read_content_digest` must be a `char(64)` column that allows NULL (`defineDailyReportSchema` declares it): the version of the
234
+ report's content the viewer marked read ([Read after update](#read-after-update)), and `createDailyReportService` stops with a `RangeError` for any
235
+ other column and for a missing one (`[daily-report] tables.userStatus.readContentDigest must be a char(64) column (the version the viewer marked read,
236
+ 64 lowercase hex digits); got <SQL type>`). An existing table takes the column with `ALTER TABLE … ADD read_content_digest char(64) NULL` before the
237
+ package is upgraded; a row written before it has no version, which reads as read.
232
238
 
233
239
  The text search adds two optional tables in a schema of their own (see [Text search](#text-search)); `defineDailyReportSearchSchema(schemaName)`
234
240
  defines them, and a host may inject its own models of the same shape. The service also needs `execute` on `DailyReportDb` (drizzle's `db.execute`; the
@@ -519,7 +525,10 @@ its payload — with one strict parser each, after authentication and before any
519
525
  200 for `defineDailyReportSchema`'s `nvarchar(200)`, which counts code units as `String.length` does, so a surrogate pair takes 2); a longer one
520
526
  is `Invalid title`. The package's client always sends both fields as typed.
521
527
  - **`toggleStar` / `toggleRead`**: `isStarred` / `isRead` is exactly `true` or `false`. Anything else — `TRUE`, `1`, `yes`, the empty text — is
522
- `Invalid isStarred` / `Invalid isRead`, and a missing one is `isStarred required` / `isRead required`.
528
+ `Invalid isStarred` / `Invalid isRead`, and a missing one is `isStarred required` / `isRead required`. `toggleRead` with `isRead=true` also
529
+ carries `contentDigest`, the version of the report the viewer's page shows (64 lowercase hex digits; a missing one is `contentDigest required`,
530
+ any other text `Invalid contentDigest`), and with `isRead=false` it carries none (`contentDigest is not allowed when isRead is false`;
531
+ [Read after update](#read-after-update)).
523
532
  - **`addComment`**: `content` is a non-empty text (`Content required` otherwise). **`deleteComment`**: `commentId` (**Ids** above).
524
533
  - **Visibility, then the user**: once the form is read and checked, the action resolves the viewer's source-type visibility once
525
534
  ([Source-type visibility](#source-type-visibility)). When the port throws or answers outside its contract, the action answers 503
@@ -608,7 +617,7 @@ its payload — with one strict parser each, after authentication and before any
608
617
  shared draft-label test, beside the hub row) or true when its creation category is one of the names; a row whose flag reads as anything but 1 or 0
609
618
  throws.
610
619
  The client compares no name: the views open the viewer's own draft in the editor by `isDraft` alone, so a draft named by any of the names, or by a
611
- variant the collation equates, behaves as one ([Client wiring](#client-wiring)). The SSE stream's key carries the wire version (`<redisKeyPrefix>:sse-stream:v4`; [Realtime architecture](#realtime-architecture)),
620
+ variant the collation equates, behaves as one ([Client wiring](#client-wiring)). The SSE stream's key carries the wire version (`<redisKeyPrefix>:sse-stream:v5`; [Realtime architecture](#realtime-architecture)),
612
621
  so an entry written before `isDraft` existed is never read.
613
622
  - `resolveVisibleSourceTypes(request, { reason })` — **Required row-level authorization port.** Returns the `Hub.source_type` values this request may
614
623
  view, or the explicit opt-out `{ unrestricted: "<reason>" }` of a deployment that does not restrict by source type. Applies uniformly to every server
@@ -673,7 +682,7 @@ its payload — with one strict parser each, after authentication and before any
673
682
  Every number among them is an integer of at least 1, and only an omitted one takes its default: `0`, a negative number, `NaN`, a fraction or 2^53
674
683
  stops `createDailyReportService` and `createDailyReportServer` with a `RangeError` that names the key
675
684
  (`[daily-report] idsTtlMs must be an integer >= 1; got 0`).
676
- - `redisKeyPrefix` (`daily-report` by default) — the prefix of every Redis key of the service: the SSE stream `<prefix>:sse-stream:v4` and the
685
+ - `redisKeyPrefix` (`daily-report` by default) — the prefix of every Redis key of the service: the SSE stream `<prefix>:sse-stream:v5` and the
677
686
  cross-worker epochs `<prefix>:ids:epoch`, `<prefix>:date-epoch:<business date>` and `<prefix>:detail-epoch:<report id>`. Environments that share
678
687
  one Redis (development and production, say) each pass their own prefix, so that neither reads the other's notifications nor drops the other's
679
688
  cached reads. The prefix is one or more characters without whitespace or control characters, neither
@@ -922,6 +931,27 @@ resolveVisibleSourceTypes: async (request, { reason }) => {
922
931
  - **Cache**: the dimension is folded into the cache-key digest, so revoking only the comment grant invalidates immediately. When `comment` follows
923
932
  `read` (the default) the digest equals the single-dimension digest of `read`, so such a deployment's cache keys do not depend on the dimension.
924
933
 
934
+ ### Read after update
935
+
936
+ A viewer who marked a report read sees when its content changed afterwards. The server computes the version of a report's own content on every read
937
+ (`DailyReportDetail.contentDigest`: 64 lowercase hex digits, one SHA-256 over the subject, body, business date, category, creation category, visit
938
+ times, customer name, interviewers (name and affiliation) and attachments (file name, size and registration time) in a fixed order, as the JSON the
939
+ client receives carries them), and a read mark records the version the viewer's page showed (`DailyReportDetail.readContentDigest`, the viewer's
940
+ `DailyReportUserStatus.read_content_digest`; null for an unread report and for a read row that recorded no version). Comments, labels (a draft's
941
+ publication), people, times, stars and the read flag never change the version, so a new comment or a re-import of the same content is no update.
942
+
943
+ - **Read state** (`DailyReportReadState`, what both views and `labels.rowState` read): `unread` when the flag is false; `read` when the recorded
944
+ version is null or equals the current one; otherwise `updatedSinceRead`, except for the report's owner, who sees `read` (their own edits are no
945
+ news to them). A viewer without an id owns no report.
946
+ - **Marks**: a report updated since read draws the pill `labels.updatedSinceRead` (`Updated` / `更新あり`; `data-daily-report-updated-since-read`,
947
+ hidden from assistive technology) between the source badge and the star, its read toggle shows an update icon and is named
948
+ `labels.markUpdateRead` (`Mark the update as read` / `更新を既読にする`), and its row state reads `Read, updated since` / `既読後に更新あり`.
949
+ - **Toggle**: `toggleRead(reportHubId, mark, businessDate)` takes a `DailyReportReadMark`: `{ isRead: false }` for a read report, and
950
+ `{ isRead: true, contentDigest }` with the shown report's `contentDigest` for an unread one and for one updated since read (marking the update
951
+ read keeps the flag and moves the version). The List side pane's auto-read marks any shown report that is not `read`.
952
+ - **Live**: another user's edit reaches the viewer as a new `contentDigest` while the viewer's own `readContentDigest` is kept, so a report the viewer
953
+ read turns `updatedSinceRead` without a reload. The toggles' `updatedStatus` and every `status-update` message carry the row's `readContentDigest`.
954
+
925
955
  ### Attachments
926
956
 
927
957
  Attachment bytes are served by `dailyReportServer.attachment.loader` at `GET {apiBasePath}/attachment/{token}`. The query string names exactly one
@@ -1647,7 +1677,7 @@ and without the retry the victim would answer 500 and lose what it wrote.
1647
1677
 
1648
1678
  ### Realtime architecture
1649
1679
 
1650
- 1. Mutation services publish to Redis Stream (`<redisKeyPrefix>:sse-stream:v4`, `daily-report:sse-stream:v4` by default: the key carries the wire version of the
1680
+ 1. Mutation services publish to Redis Stream (`<redisKeyPrefix>:sse-stream:v5`, `daily-report:sse-stream:v5` by default: the key carries the wire version of the
1651
1681
  stream's messages and their embedded report, so a deployment that changes a required key of them reads no entry an older version wrote, and the stream of the previous version
1652
1682
  stops growing and can be deleted by hand; a host that reads or writes the stream itself, such as an integration test, builds the key with the
1653
1683
  server entry's `buildDailyReportRedisKeys(redisKeyPrefix).sseStream` instead of restating the version) in sequence:
@@ -3199,7 +3229,8 @@ the resize handle and the side pane lie outside the list.
3199
3229
  detail, an open editor, its comments, its thumbnails) renders under the next, and coming back to a report mounts its attachments once (one
3200
3230
  conditional request per loaded thumbnail) and shows an editor it left open as it was left, with what was typed (**Editing** below). The chosen tab
3201
3231
  (article or relations) belongs to the pane, and to the mobile overlay, and is kept across reports.
3202
- Auto-read and the article tab's scroll to the top follow the displayed report. Auto-read marks an unread report read once the pane has dwelt on it
3232
+ Auto-read and the article tab's scroll to the top follow the displayed report. Auto-read marks a shown report that is not read (unread, or updated since the viewer read it: [Read after update](#read-after-update)) read at
3233
+ its shown version once the pane has dwelt on it
3203
3234
  for 500 ms: on the desktop layout while the pane has caught up with the selection (the displayed report is the selected one and the pane is not
3204
3235
  `aria-busy`), in the mobile overlay while the overlay is open. The clock stops when the pane stops dwelling (a newer key, a held key's stream, the
3205
3236
  overlay sliding out)
@@ -3216,7 +3247,7 @@ the resize handle and the side pane lie outside the list.
3216
3247
  `"false"` on every other, so Chromium adds no implicit selection. Its surface is the row's one `gridcell`, named by the row's name element, so a
3217
3248
  cell is never named by the whole card.
3218
3249
  A List row with a card is named by its card's primary button, whose content is "YYYY-MM-DD creator" (`aria-labelledby`), and described by the row
3219
- state (`labels.rowState`: read or unread, and starred), the markers the card paints, the preview and, while the screen holds a search marks store,
3250
+ state (`labels.rowState`: unread, read or read and updated since, and starred), the markers the card paints, the preview and, while the screen holds a search marks store,
3220
3251
  the match description; the button itself is described first by its position in the list (`labels.listRowPosition`, a visually hidden text: en
3221
3252
  "3 of 40", ja 「全 40 件中 3 件目」), because touch screen readers land on the button, which carries no position attribute.
3222
3253
  A DetailList row with a body is named by its report heading (date, author and subject; `aria-labelledby`) and described by the row state, the
@@ -4184,9 +4215,9 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4184
4215
  />
4185
4216
  ```
4186
4217
 
4187
- - **One surface for all wording.** The catalog covers the 11 engine chrome keys of `@aiquants/virtualscroll` plus 111 own keys: field headings, the
4188
- page title (`title`, passed to `renderHeader` and used as the accessible name of both lists), the lists' keyboard help, the row states (read and
4189
- starred, editing) 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
+ - **One surface for all wording.** The catalog covers the 11 engine chrome keys of `@aiquants/virtualscroll` plus 113 own keys: field headings, the
4219
+ 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
4220
+ 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
4190
4221
  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
4191
4222
  each catalog is `@aiquants/virtualscroll`'s own catalog of the same locale, reused by value, so the scroll arrows, the names of the scroll bar and
4192
4223
  its thumb, and the "No items" text always speak the same language as the rest.
@@ -4199,7 +4230,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4199
4230
  `streamRetrying(loaded)`, `reportLoadFailed(reportHubId)`, `reportSummaryLoadFailed(reportHubId)`,
4200
4231
  `interviewerWithAffiliation(name, affiliation)`, `operationFailed(operation)` (`operation` is a
4201
4232
  `DailyReportOperation`: `create` / `update` / `publish` / `delete` / `addComment` / `deleteComment` /
4202
- `toggleStar` / `toggleRead`), `noticeRepeated(count)`, `rowState({ isRead, isStarred })`, `rowEditingState({ isDraft })`, `listRowPosition(position, total)`,
4233
+ `toggleStar` / `toggleRead`), `noticeRepeated(count)`, `rowState({ readState, isStarred })`, `rowEditingState({ isDraft })`, `listRowPosition(position, total)`,
4203
4234
  `listRowPositionInUnknownTotal(position)`,
4204
4235
  `searchHitCount(count)`, `searchFailed(failure)` (`failure` is `invalid` / `busy` / `unavailable` / `failed`),
4205
4236
  `searchMatchedKeywords(matched, total)`, `searchMatchedKeywordsDescription(matched, total)`, `searchFieldMatched(field)` and
@@ -4210,7 +4241,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4210
4241
  locale built once (the row positions and counts re-render on every stream publish); a host override receives
4211
4242
  the raw number and formats it itself.
4212
4243
  - **Fail-fast**: an unsupported `locale` (`"EN"`, `"ja-JP"`, `"fr"`, `""`, `null`, ...) throws a
4213
- `RangeError` at render. So do an unknown `labels` key (a key error that lists the 122 keys), a string
4244
+ `RangeError` at render. So do an unknown `labels` key (a key error that lists the 124 keys), a string
4214
4245
  key whose value is not a string with non-whitespace content, and a formatter key whose value is not a
4215
4246
  function. An `undefined` value keeps the catalog value.
4216
4247
  - **`config` has a closed key set**: `apiBasePath`, `ssePath`, `locale`, `labels`,
@@ -4248,7 +4279,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4248
4279
  grouping follows `locale` (`en` → `"en-US"`, `ja` → `"ja-JP"`) and dates stay in ISO form. Packages
4249
4280
  that do format dates (for example `@aiquants/period-slider`) name that separate axis `formatLocale`.
4250
4281
 
4251
- Exported API (`@aiquants/daily-report/client`): `DAILY_REPORT_LABEL_KEYS` (all 122 keys, the 11 engine keys
4282
+ Exported API (`@aiquants/daily-report/client`): `DAILY_REPORT_LABEL_KEYS` (all 124 keys, the 11 engine keys
4252
4283
  first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReportLabels(locale, labels)`
4253
4284
  (the effective frozen labels — the catalog object itself when `labels` is `undefined`), the resolved
4254
4285
  `locale` / `labels` on `useDailyReportConfig()`, and the types `DailyReportLocale` (= `VirtualScrollLocale`),
@@ -4322,6 +4353,8 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
4322
4353
  | `starRemove` | star toggle (starred) | Remove star | スターを外す |
4323
4354
  | `markRead` | read toggle (unread) | Mark as read | 既読にする |
4324
4355
  | `markUnread` | read toggle (read) | Mark as unread | 未読にする |
4356
+ | `markUpdateRead` | read toggle (updated since the viewer read it; [Read after update](#read-after-update)) | Mark the update as read | 更新を既読にする |
4357
+ | `updatedSinceRead` | pill of a report updated since the viewer read it (hidden from assistive technology; the row state says it) | Updated | 更新あり |
4325
4358
  | `deleteReport` | report delete button | Delete | 削除する |
4326
4359
  | `edit` | edit button of the viewer's own report (`aria-label` / tooltip) | Edit | 編集 |
4327
4360
  | `resizeHandle` | list / detail resize handle `aria-label` | Resize the border between the report list and the detail pane | 日報一覧と詳細ペインの境界のサイズ変更ハンドル |
@@ -4366,7 +4399,7 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
4366
4399
  | `interviewerWithAffiliation` | interviewer chip | `("Sato", "Acme")` → Sato (Acme) | `("佐藤", "山田商事")` → 佐藤 (山田商事) |
4367
4400
  | `operationFailed` | the error notice / `DailyReportErrorInfo.message` | `("update")` → Failed to save the report. Please try again later. | `("update")` → 日報の保存に失敗しました。時間をおいて再度お試しください。 |
4368
4401
  | `noticeRepeated` | the error notice, beside its message when failures with the same message follow one another (`count` is how many came in a row, from 2; part of the notice's name) | `(2)` → (2 times) | `(2)` → (2 回目) |
4369
- | `rowState` | visually hidden row state, the first description of a card row in both views and the second of the List card's primary button | `({ isRead: false, isStarred: true })` → Unread, starred | `({ isRead: false, isStarred: true })` → 未読、スター付き |
4402
+ | `rowState` | visually hidden row state, the first description of a card row in both views and the second of the List card's primary button | `({ readState: "unread", isStarred: true })` → Unread, starred; `updatedSinceRead` → Read, updated since | `({ readState: "unread", isStarred: true })` → 未読、スター付き; `updatedSinceRead` → 既読後に更新あり |
4370
4403
  | `rowEditingState` | visually hidden state of a DetailList row that shows its edit form in place of the card (the viewer's own draft opens there by itself), the row's description | `({ isDraft: true })` → Editing draft; `({ isDraft: false })` → Editing | `({ isDraft: true })` → 下書きを編集中; `({ isDraft: false })` → 編集中 |
4371
4404
  | `listRowPosition` | visually hidden position of a List card in the list, the first description of its primary button (`position` is 1-based; `total` is the size of the views' set) | `(3, 1234)` → 3 of 1,234 | `(3, 1234)` → 全 1,234 件中 3 件目 |
4372
4405
  | `listRowPositionInUnknownTotal` | the same position while the size of the views' set is unknown (the ids stream has not declared its total yet), in place of `listRowPosition`; it also ends the description of a card row in both views while the size is unknown | `(3)` → Item 3 | `(3)` → 3 件目 |
@@ -4520,8 +4553,10 @@ Documenting a name here is no reason to export it, and the values that tune the
4520
4553
  - Types reached by the closure: `DailyReportAttachmentState` (the verification state of an attachment's object), `DailyReportBusinessDateInput`
4521
4554
  (what `normalizeBusinessDateKey` takes), `DailyReportUIComment` (a comment as `useDailyReportComments` returns it), `DailyReportIdsStreamCursor`
4522
4555
  (the ids stream's resume cursor),
4523
- `DailyReportSseTerminalEvent` (one name of `DAILY_REPORT_SSE_TERMINAL_EVENTS`) and `DailyReportLogger` (the console-compatible logger the server's
4524
- `logger` takes).
4556
+ `DailyReportSseTerminalEvent` (one name of `DAILY_REPORT_SSE_TERMINAL_EVENTS`), `DailyReportLogger` (the console-compatible logger the server's
4557
+ `logger` takes), and the read state of the read-after-update marks ([Read after update](#read-after-update)): `DailyReportReadMark` (what
4558
+ `toggleRead` writes: `{ isRead: true, contentDigest }` or `{ isRead: false }`), `DailyReportReadState` (`unread`, `read` or `updatedSinceRead`,
4559
+ what `labels.rowState` reads) and `DailyReportReadStatus` (`{ isRead, readContentDigest }`, the value of `pendingReadUpdates`).
4525
4560
  - The text search's wire: `DAILY_REPORT_SEARCH_COMBINES` with its type `DailyReportSearchCombine` (the `combine` values, `intersection` and
4526
4561
  `union`), `DailyReportSearchHit` (one hit as the server holds it before the column-wise answer),
4527
4562
  `DAILY_REPORT_SEARCH_BATCHES_IN_FLIGHT` (2, the batched reads the package's client keeps in flight per API base path, from which the server sizes
@@ -4607,7 +4642,7 @@ Documenting a name here is no reason to export it, and the values that tune the
4607
4642
  decode-work and walk bounds, for a host's conformance checks) / `DAILY_REPORT_ATTACHMENT_THUMBNAIL_OUTPUT_MEDIA_TYPES` /
4608
4643
  `DAILY_REPORT_ATTACHMENT_THUMBNAIL_MAX_OUTPUT_BYTES`.
4609
4644
  - SSE and caching: `buildDailyReportRedisKeys(redisKeyPrefix)` (`DailyReportRedisKeys`: the keys the service builds under the prefix, the SSE
4610
- stream `sseStream` with the wire version, `<prefix>:sse-stream:v4`, and the epoch keys `idsEpoch`, `dateEpochPrefix` and `detailEpochPrefix`,
4645
+ stream `sseStream` with the wire version, `<prefix>:sse-stream:v5`, and the epoch keys `idsEpoch`, `dateEpochPrefix` and `detailEpochPrefix`,
4611
4646
  for a host that reads or writes the stream or the epochs itself) / `DailyReportSseReader` /
4612
4647
  `readDailyReportSseStreamEntry(raw, logger)` (reads one Redis stream entry, `{ id, message }` as the client returns it, once: `{ entry, json }`,
4613
4648
  the frozen `StreamEntry` the shared reader hands every subscriber and the entry's parsed JSON value; for a host whose own reader keeps the same
package/dist/client.d.mts CHANGED
@@ -1,15 +1,15 @@
1
1
  import * as react from "react";
2
2
  import { RefObject, ReactNode, Context } from "react";
3
- import { D as DailyReportRows } from "./instrumentation-CobsTeyw.mjs";
4
- export { a as DailyReportInstrumentation, b as DailyReportInstrumentationContext, I as DailyReportInstrumentedView, L as DailyReportListNavigationHandle, c as DailyReportRevealOptions, V as DailyReportViewInstrumentation } from "./instrumentation-CobsTeyw.mjs";
5
- import { D as DailyReportItem, a as DailyReportUser, b as DailyReportDetail } from "./types-BP1kIGMo.mjs";
3
+ import { D as DailyReportRows } from "./instrumentation-Bks6vyM7.mjs";
4
+ export { a as DailyReportInstrumentation, b as DailyReportInstrumentationContext, I as DailyReportInstrumentedView, L as DailyReportListNavigationHandle, c as DailyReportRevealOptions, V as DailyReportViewInstrumentation } from "./instrumentation-Bks6vyM7.mjs";
5
+ import { D as DailyReportItem, a as DailyReportUser, b as DailyReportDetail } from "./types-CcUNWIyC.mjs";
6
6
  import { VirtualScrollLocale, VirtualScrollLabels } from "@aiquants/virtualscroll";
7
- import { D as DailyReportActionIntent, U as UIComment } from "./source-types-Hy_znWJl.mjs";
8
- export { f as foldDailyReportSourceTypeToken } from "./source-types-Hy_znWJl.mjs";
7
+ import { D as DailyReportActionIntent, U as UIComment } from "./source-types-a15dGIN1.mjs";
8
+ export { f as foldDailyReportSourceTypeToken } from "./source-types-a15dGIN1.mjs";
9
+ import { D as DailyReportReadState, a as DailyReportSseStreamAnchor, b as DailyReportSseTerminalEvent, c as DailyReportSseMessage, d as DailyReportReadMark, e as DailyReportReadStatus } from "./sse-schema-Bk_iO1vF.mjs";
9
10
  import { D as DailyReportSearchFilterFacet } from "./search-filters-wire-5MWi6GWu.mjs";
10
11
  export { a as DAILY_REPORT_SEARCH_FILTER_FACETS } from "./search-filters-wire-5MWi6GWu.mjs";
11
12
  import { UseReopeningEventSourceResult } from "@aiquants/sse/react";
12
- import { D as DailyReportSseStreamAnchor, a as DailyReportSseTerminalEvent, b as DailyReportSseMessage } from "./sse-schema-CM_YM7xU.mjs";
13
13
  import * as _aiquants_route_revalidation from "@aiquants/route-revalidation";
14
14
  import "zod/v3";
15
15
  type DailyReportAttachmentIndicatorProps = {
@@ -96,6 +96,8 @@ type DailyReportLabels = VirtualScrollLabels & {
96
96
  readonly starRemove: string;
97
97
  readonly markRead: string;
98
98
  readonly markUnread: string;
99
+ readonly markUpdateRead: string;
100
+ readonly updatedSinceRead: string;
99
101
  readonly deleteReport: string;
100
102
  readonly edit: string;
101
103
  readonly resizeHandle: string;
@@ -141,7 +143,7 @@ type DailyReportLabels = VirtualScrollLabels & {
141
143
  readonly operationFailed: (operation: DailyReportOperation) => string;
142
144
  readonly noticeRepeated: (count: number) => string;
143
145
  readonly rowState: (state: {
144
- readonly isRead: boolean;
146
+ readonly readState: DailyReportReadState;
145
147
  readonly isStarred: boolean;
146
148
  }) => string;
147
149
  readonly rowEditingState: (state: {
@@ -228,6 +230,8 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
228
230
  "starRemove",
229
231
  "markRead",
230
232
  "markUnread",
233
+ "markUpdateRead",
234
+ "updatedSinceRead",
231
235
  "deleteReport",
232
236
  "edit",
233
237
  "resizeHandle",
@@ -431,7 +435,7 @@ type DailyReportActionFunctions = {
431
435
  addComment: (reportHubId: number, content: string, businessDate: ReportBusinessDate) => Promise<void>;
432
436
  deleteComment: (reportHubId: number, commentId: number, businessDate: ReportBusinessDate) => Promise<void>;
433
437
  toggleStar: (reportHubId: number, isStarred: boolean, businessDate: ReportBusinessDate) => Promise<void>;
434
- toggleRead: (reportHubId: number, isRead: boolean, businessDate: ReportBusinessDate) => Promise<void>;
438
+ toggleRead: (reportHubId: number, mark: DailyReportReadMark, businessDate: ReportBusinessDate) => Promise<void>;
435
439
  createReport: (businessDate: string) => Promise<number>;
436
440
  updateReport: (reportHubId: number, data: {
437
441
  title: string;
@@ -450,7 +454,7 @@ type DailyReportActionLedgers = {
450
454
  readonly pendingAddCommentIds: DailyReportLedgerView<ReadonlyMap<number, ReadonlyMap<number, string>>>;
451
455
  readonly pendingDeleteCommentIds: DailyReportLedgerView<ReadonlyMap<number, ReadonlySet<number>>>;
452
456
  readonly pendingStarUpdates: DailyReportLedgerView<ReadonlyMap<number, boolean>>;
453
- readonly pendingReadUpdates: DailyReportLedgerView<ReadonlyMap<number, boolean>>;
457
+ readonly pendingReadUpdates: DailyReportLedgerView<ReadonlyMap<number, DailyReportReadStatus>>;
454
458
  readonly resolvedIdMap: DailyReportLedgerView<ReadonlyMap<number, number>>;
455
459
  };
456
460
  type DailyReportActions = DailyReportActionFunctions & DailyReportActionLedgers & {
package/dist/client.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import * as react from "react";
2
2
  import { RefObject, ReactNode, Context } from "react";
3
- import { D as DailyReportRows } from "./instrumentation-Dz7Uh7dT.js";
4
- export { a as DailyReportInstrumentation, b as DailyReportInstrumentationContext, I as DailyReportInstrumentedView, L as DailyReportListNavigationHandle, c as DailyReportRevealOptions, V as DailyReportViewInstrumentation } from "./instrumentation-Dz7Uh7dT.js";
5
- import { D as DailyReportItem, a as DailyReportUser, b as DailyReportDetail } from "./types-BP1kIGMo.js";
3
+ import { D as DailyReportRows } from "./instrumentation-CCr0sezP.js";
4
+ export { a as DailyReportInstrumentation, b as DailyReportInstrumentationContext, I as DailyReportInstrumentedView, L as DailyReportListNavigationHandle, c as DailyReportRevealOptions, V as DailyReportViewInstrumentation } from "./instrumentation-CCr0sezP.js";
5
+ import { D as DailyReportItem, a as DailyReportUser, b as DailyReportDetail } from "./types-CcUNWIyC.js";
6
6
  import { VirtualScrollLocale, VirtualScrollLabels } from "@aiquants/virtualscroll";
7
- import { D as DailyReportActionIntent, U as UIComment } from "./source-types-Cg7T2dm6.js";
8
- export { f as foldDailyReportSourceTypeToken } from "./source-types-Cg7T2dm6.js";
7
+ import { D as DailyReportActionIntent, U as UIComment } from "./source-types-C1LvfRRJ.js";
8
+ export { f as foldDailyReportSourceTypeToken } from "./source-types-C1LvfRRJ.js";
9
+ import { D as DailyReportReadState, a as DailyReportSseStreamAnchor, b as DailyReportSseTerminalEvent, c as DailyReportSseMessage, d as DailyReportReadMark, e as DailyReportReadStatus } from "./sse-schema-BLe0x7BD.js";
9
10
  import { D as DailyReportSearchFilterFacet } from "./search-filters-wire-5MWi6GWu.js";
10
11
  export { a as DAILY_REPORT_SEARCH_FILTER_FACETS } from "./search-filters-wire-5MWi6GWu.js";
11
12
  import { UseReopeningEventSourceResult } from "@aiquants/sse/react";
12
- import { D as DailyReportSseStreamAnchor, a as DailyReportSseTerminalEvent, b as DailyReportSseMessage } from "./sse-schema-BmhpWHVK.js";
13
13
  import * as _aiquants_route_revalidation from "@aiquants/route-revalidation";
14
14
  import "zod/v3";
15
15
  type DailyReportAttachmentIndicatorProps = {
@@ -96,6 +96,8 @@ type DailyReportLabels = VirtualScrollLabels & {
96
96
  readonly starRemove: string;
97
97
  readonly markRead: string;
98
98
  readonly markUnread: string;
99
+ readonly markUpdateRead: string;
100
+ readonly updatedSinceRead: string;
99
101
  readonly deleteReport: string;
100
102
  readonly edit: string;
101
103
  readonly resizeHandle: string;
@@ -141,7 +143,7 @@ type DailyReportLabels = VirtualScrollLabels & {
141
143
  readonly operationFailed: (operation: DailyReportOperation) => string;
142
144
  readonly noticeRepeated: (count: number) => string;
143
145
  readonly rowState: (state: {
144
- readonly isRead: boolean;
146
+ readonly readState: DailyReportReadState;
145
147
  readonly isStarred: boolean;
146
148
  }) => string;
147
149
  readonly rowEditingState: (state: {
@@ -228,6 +230,8 @@ declare const DAILY_REPORT_LABEL_KEYS: readonly [
228
230
  "starRemove",
229
231
  "markRead",
230
232
  "markUnread",
233
+ "markUpdateRead",
234
+ "updatedSinceRead",
231
235
  "deleteReport",
232
236
  "edit",
233
237
  "resizeHandle",
@@ -431,7 +435,7 @@ type DailyReportActionFunctions = {
431
435
  addComment: (reportHubId: number, content: string, businessDate: ReportBusinessDate) => Promise<void>;
432
436
  deleteComment: (reportHubId: number, commentId: number, businessDate: ReportBusinessDate) => Promise<void>;
433
437
  toggleStar: (reportHubId: number, isStarred: boolean, businessDate: ReportBusinessDate) => Promise<void>;
434
- toggleRead: (reportHubId: number, isRead: boolean, businessDate: ReportBusinessDate) => Promise<void>;
438
+ toggleRead: (reportHubId: number, mark: DailyReportReadMark, businessDate: ReportBusinessDate) => Promise<void>;
435
439
  createReport: (businessDate: string) => Promise<number>;
436
440
  updateReport: (reportHubId: number, data: {
437
441
  title: string;
@@ -450,7 +454,7 @@ type DailyReportActionLedgers = {
450
454
  readonly pendingAddCommentIds: DailyReportLedgerView<ReadonlyMap<number, ReadonlyMap<number, string>>>;
451
455
  readonly pendingDeleteCommentIds: DailyReportLedgerView<ReadonlyMap<number, ReadonlySet<number>>>;
452
456
  readonly pendingStarUpdates: DailyReportLedgerView<ReadonlyMap<number, boolean>>;
453
- readonly pendingReadUpdates: DailyReportLedgerView<ReadonlyMap<number, boolean>>;
457
+ readonly pendingReadUpdates: DailyReportLedgerView<ReadonlyMap<number, DailyReportReadStatus>>;
454
458
  readonly resolvedIdMap: DailyReportLedgerView<ReadonlyMap<number, number>>;
455
459
  };
456
460
  type DailyReportActions = DailyReportActionFunctions & DailyReportActionLedgers & {