@aiquants/daily-report 0.46.0 → 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,65 @@
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
+
48
+ ## 0.46.1 (2026-10-16)
49
+
50
+ 版番号は 0.46.1 (patch) とする: 公開の型は変えず、外部ソースの日報へ誤って書き込む経路を閉じる。
51
+
52
+ ### Fixed
53
+
54
+ - **外部ソースの日報は、持ち主でも変えられない**: 編集と削除の入口 (DetailList のカードと List の側面ペインのボタン、ダブルクリック) と下書きの
55
+ 自動の編集は、閲覧者が持ち主かどうかだけで出ていた。そのため外部ソースの日報 (ホストの `externalSources` のアダプタが外部の表から読む日報) の
56
+ 持ち主にも出ていた。保存すると件名だけが hub に書かれて外部の表と食い違い、本文は内部の表に行が無いので何も書かれないまま成功を答えた
57
+ (画面は保存の直後だけ新しい本文を見せ、読み直すと元の本文に戻る)。削除と公開も hub に書けた。
58
+ 今は、入口と下書きの自動の編集を、パッケージ自身の区分 (`Internal`。字面のまま比べる) の持ち主の日報にだけ出す。サーバーの更新・公開・削除は、
59
+ 外部ソースの日報を持ち主を比べる前に断り (action は 403 `{"error":"Reports from this source are read-only"}`、サービスは `Forbidden`)、
60
+ 何も書かず知らせない。区分の判定は 1 つの関数 (`isMutableDailyReportSource`) にあり、サーバーの関門とクライアントの入口が同じものを読む。
61
+ ホストへの影響: サービスの `updateDailyReport`・`publishDailyReport`・`deleteDailyReport` を外部ソースの日報で呼んでいた所は `Forbidden` を受ける
62
+ (どれも書き込みが外部の表と食い違う経路だった)。
63
+
5
64
  ## 0.46.0 (2026-10-16)
6
65
 
7
66
  版番号は 0.46.0 (minor) とする: 設定の `streamKey` と公開の `buildDailyReportSseStreamKey` を置き換える (下の Breaking)。
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,14 +525,20 @@ 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
526
535
  `{"error":"Report visibility is unavailable"}` with `Retry-After: 5` and `Cache-Control: no-store`, before the internal user lookup and any intent.
527
536
  Then the internal user (`User not found`, 404). A `create` whose scope does not show the package's own source type (`Internal`) is 403
528
537
  `{"error":"Creating reports is restricted"}` before any query; the other intents answer a report the scope hides as 404 (**Where it applies** in
529
- [Source-type visibility](#source-type-visibility)).
538
+ [Source-type visibility](#source-type-visibility)). An `update`, `publish` or `delete` of a report the scope shows but of an external source (any
539
+ source type other than the package's own `Internal`, compared exactly) is 403 `{"error":"Reports from this source are read-only"}` before the owner
540
+ is compared and before any write: the package writes only the reports it stores itself, and its client draws the edit and delete entrances and a
541
+ draft's automatic editor only for the viewer's own `Internal` reports.
530
542
  - **One wire contract for the action and the JSON endpoints**, which the server, the package's client and a host that calls the routes itself all read
531
543
  from the shared entry (`@aiquants/daily-report`; neither side restates a name):
532
544
  - the intents (`DAILY_REPORT_ACTION_INTENTS` and their type `DailyReportActionIntent`, from which the server's form reading and the client's
@@ -605,7 +617,7 @@ its payload — with one strict parser each, after authentication and before any
605
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
606
618
  throws.
607
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
608
- 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)),
609
621
  so an entry written before `isDraft` existed is never read.
610
622
  - `resolveVisibleSourceTypes(request, { reason })` — **Required row-level authorization port.** Returns the `Hub.source_type` values this request may
611
623
  view, or the explicit opt-out `{ unrestricted: "<reason>" }` of a deployment that does not restrict by source type. Applies uniformly to every server
@@ -670,7 +682,7 @@ its payload — with one strict parser each, after authentication and before any
670
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
671
683
  stops `createDailyReportService` and `createDailyReportServer` with a `RangeError` that names the key
672
684
  (`[daily-report] idsTtlMs must be an integer >= 1; got 0`).
673
- - `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
674
686
  cross-worker epochs `<prefix>:ids:epoch`, `<prefix>:date-epoch:<business date>` and `<prefix>:detail-epoch:<report id>`. Environments that share
675
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
676
688
  cached reads. The prefix is one or more characters without whitespace or control characters, neither
@@ -919,6 +931,27 @@ resolveVisibleSourceTypes: async (request, { reason }) => {
919
931
  - **Cache**: the dimension is folded into the cache-key digest, so revoking only the comment grant invalidates immediately. When `comment` follows
920
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.
921
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
+
922
955
  ### Attachments
923
956
 
924
957
  Attachment bytes are served by `dailyReportServer.attachment.loader` at `GET {apiBasePath}/attachment/{token}`. The query string names exactly one
@@ -1644,7 +1677,7 @@ and without the retry the victim would answer 500 and lose what it wrote.
1644
1677
 
1645
1678
  ### Realtime architecture
1646
1679
 
1647
- 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
1648
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
1649
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
1650
1683
  server entry's `buildDailyReportRedisKeys(redisKeyPrefix).sseStream` instead of restating the version) in sequence:
@@ -3196,7 +3229,8 @@ the resize handle and the side pane lie outside the list.
3196
3229
  detail, an open editor, its comments, its thumbnails) renders under the next, and coming back to a report mounts its attachments once (one
3197
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
3198
3231
  (article or relations) belongs to the pane, and to the mobile overlay, and is kept across reports.
3199
- 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
3200
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
3201
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
3202
3236
  overlay sliding out)
@@ -3213,7 +3247,7 @@ the resize handle and the side pane lie outside the list.
3213
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
3214
3248
  cell is never named by the whole card.
3215
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
3216
- 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,
3217
3251
  the match description; the button itself is described first by its position in the list (`labels.listRowPosition`, a visually hidden text: en
3218
3252
  "3 of 40", ja 「全 40 件中 3 件目」), because touch screen readers land on the button, which carries no position attribute.
3219
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
@@ -4181,9 +4215,9 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4181
4215
  />
4182
4216
  ```
4183
4217
 
4184
- - **One surface for all wording.** The catalog covers the 11 engine chrome keys of `@aiquants/virtualscroll` plus 111 own keys: field headings, the
4185
- page title (`title`, passed to `renderHeader` and used as the accessible name of both lists), the lists' keyboard help, the row states (read and
4186
- 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
4187
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
4188
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
4189
4223
  its thumb, and the "No items" text always speak the same language as the rest.
@@ -4196,7 +4230,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4196
4230
  `streamRetrying(loaded)`, `reportLoadFailed(reportHubId)`, `reportSummaryLoadFailed(reportHubId)`,
4197
4231
  `interviewerWithAffiliation(name, affiliation)`, `operationFailed(operation)` (`operation` is a
4198
4232
  `DailyReportOperation`: `create` / `update` / `publish` / `delete` / `addComment` / `deleteComment` /
4199
- `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)`,
4200
4234
  `listRowPositionInUnknownTotal(position)`,
4201
4235
  `searchHitCount(count)`, `searchFailed(failure)` (`failure` is `invalid` / `busy` / `unavailable` / `failed`),
4202
4236
  `searchMatchedKeywords(matched, total)`, `searchMatchedKeywordsDescription(matched, total)`, `searchFieldMatched(field)` and
@@ -4207,7 +4241,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4207
4241
  locale built once (the row positions and counts re-render on every stream publish); a host override receives
4208
4242
  the raw number and formats it itself.
4209
4243
  - **Fail-fast**: an unsupported `locale` (`"EN"`, `"ja-JP"`, `"fr"`, `""`, `null`, ...) throws a
4210
- `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
4211
4245
  key whose value is not a string with non-whitespace content, and a formatter key whose value is not a
4212
4246
  function. An `undefined` value keeps the catalog value.
4213
4247
  - **`config` has a closed key set**: `apiBasePath`, `ssePath`, `locale`, `labels`,
@@ -4245,7 +4279,7 @@ locale**: English (`"en"`, the default) and Japanese (`"ja"`). Pick the language
4245
4279
  grouping follows `locale` (`en` → `"en-US"`, `ja` → `"ja-JP"`) and dates stay in ISO form. Packages
4246
4280
  that do format dates (for example `@aiquants/period-slider`) name that separate axis `formatLocale`.
4247
4281
 
4248
- 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
4249
4283
  first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReportLabels(locale, labels)`
4250
4284
  (the effective frozen labels — the catalog object itself when `labels` is `undefined`), the resolved
4251
4285
  `locale` / `labels` on `useDailyReportConfig()`, and the types `DailyReportLocale` (= `VirtualScrollLocale`),
@@ -4319,6 +4353,8 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
4319
4353
  | `starRemove` | star toggle (starred) | Remove star | スターを外す |
4320
4354
  | `markRead` | read toggle (unread) | Mark as read | 既読にする |
4321
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 | 更新あり |
4322
4358
  | `deleteReport` | report delete button | Delete | 削除する |
4323
4359
  | `edit` | edit button of the viewer's own report (`aria-label` / tooltip) | Edit | 編集 |
4324
4360
  | `resizeHandle` | list / detail resize handle `aria-label` | Resize the border between the report list and the detail pane | 日報一覧と詳細ペインの境界のサイズ変更ハンドル |
@@ -4363,7 +4399,7 @@ first), `DAILY_REPORT_LABEL_CATALOGS` (the frozen catalogs), `resolveDailyReport
4363
4399
  | `interviewerWithAffiliation` | interviewer chip | `("Sato", "Acme")` → Sato (Acme) | `("佐藤", "山田商事")` → 佐藤 (山田商事) |
4364
4400
  | `operationFailed` | the error notice / `DailyReportErrorInfo.message` | `("update")` → Failed to save the report. Please try again later. | `("update")` → 日報の保存に失敗しました。時間をおいて再度お試しください。 |
4365
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 回目) |
4366
- | `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` → 既読後に更新あり |
4367
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 })` → 編集中 |
4368
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 件目 |
4369
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 件目 |
@@ -4517,8 +4553,10 @@ Documenting a name here is no reason to export it, and the values that tune the
4517
4553
  - Types reached by the closure: `DailyReportAttachmentState` (the verification state of an attachment's object), `DailyReportBusinessDateInput`
4518
4554
  (what `normalizeBusinessDateKey` takes), `DailyReportUIComment` (a comment as `useDailyReportComments` returns it), `DailyReportIdsStreamCursor`
4519
4555
  (the ids stream's resume cursor),
4520
- `DailyReportSseTerminalEvent` (one name of `DAILY_REPORT_SSE_TERMINAL_EVENTS`) and `DailyReportLogger` (the console-compatible logger the server's
4521
- `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`).
4522
4560
  - The text search's wire: `DAILY_REPORT_SEARCH_COMBINES` with its type `DailyReportSearchCombine` (the `combine` values, `intersection` and
4523
4561
  `union`), `DailyReportSearchHit` (one hit as the server holds it before the column-wise answer),
4524
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
@@ -4604,7 +4642,7 @@ Documenting a name here is no reason to export it, and the values that tune the
4604
4642
  decode-work and walk bounds, for a host's conformance checks) / `DAILY_REPORT_ATTACHMENT_THUMBNAIL_OUTPUT_MEDIA_TYPES` /
4605
4643
  `DAILY_REPORT_ATTACHMENT_THUMBNAIL_MAX_OUTPUT_BYTES`.
4606
4644
  - SSE and caching: `buildDailyReportRedisKeys(redisKeyPrefix)` (`DailyReportRedisKeys`: the keys the service builds under the prefix, the SSE
4607
- 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`,
4608
4646
  for a host that reads or writes the stream or the epochs itself) / `DailyReportSseReader` /
4609
4647
  `readDailyReportSseStreamEntry(raw, logger)` (reads one Redis stream entry, `{ id, message }` as the client returns it, once: `{ entry, json }`,
4610
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 & {