@convokitapp/vue-ui 0.4.1 → 0.6.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
@@ -1,5 +1,78 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ - Inbox previews and unread counts. SDK-backed lists page `listInbox` by
6
+ opaque cursor in activity order (newest surviving message first, empty rooms
7
+ by creation time); `hasMore` follows the server's `nextCursor`. Pages merge
8
+ by conversation ID with the later entry winning, re-ordered by
9
+ `(activityAt desc, id desc)` unless `filter.comparator` is set. Page checks:
10
+ at most `limit` entries, non-blank unique IDs, and a cursor that advances
11
+ (`Inbox pagination did not advance`); the offset-era "full page with no new
12
+ IDs" rule applies to the legacy path only. `mergeInboxEntries` is exported.
13
+ - State: `summaries` (`ReadonlyMap<string, InboxSummary>`) and `currentUserId`
14
+ on `useConversationList` / `ConversationListState`; `''` and empty without a
15
+ session, on the legacy path and after disposal.
16
+ - Default rows: preview line (`You: …`, `<name>: …` in groups, media
17
+ placeholders `Photo` / file name or `File` / `Location` / `Contact`),
18
+ activity time, unread badge (`99+` above 99 or when capped; accessible name
19
+ `<count> unread`, `99+ unread` when capped; label hidden from the
20
+ accessibility tree), bold title while unread. New theme token `badge`
21
+ (`--ckui-badge`, default primary). `#conversation-item` slot props gain
22
+ `summary` and `currentUserId`; `ConversationListView` accepts `summaries`
23
+ and `currentUserId`. Inline `Retry` requests the next page when `hasMore`
24
+ and `onLoadMore` are bound (bypassing the duplicate-request guard), else
25
+ refreshes; it renders only when a callback exists.
26
+ - Live updates: `inbox_activity` is throttled by `activityRefreshWindowMs`
27
+ (default 500, 0 = immediate; max-wait window, generation-guarded, cleared on
28
+ dispose/session end, `unref`'d where available); `inbox_changed` refreshes
29
+ immediately and drops a pending window. `refresh()` walks from the head with
30
+ `limit = min(100, target − consumed)`, `target = max(pageSize, loaded)`,
31
+ continues past fully hidden pages and never publishes an empty list with
32
+ `hasMore` while pages remain; rows, summaries, cursor and `hasMore` swap
33
+ atomically.
34
+ - Adapter: optional `listInbox?({ limit, cursor, archived })` and
35
+ `onInboxActivity?(handler, onError?)`; the default adapter implements both.
36
+ Adapters without `listInbox` and custom `pageLoader`s keep the 0.5 offset
37
+ path with empty summaries. A 404 from `listInbox` falls back to that path for
38
+ the store's life without evicting rows (warned once); a legacy 404 still
39
+ evicts, 401/403 evict, 400 keeps rows and reports.
40
+ - Requires core SDK 0.6.x (`listInbox`, `InboxPage` / `InboxEntry` /
41
+ `InboxSummary`, `realtime.onInboxActivity`) and the coordinated backend.
42
+
43
+ ## 0.5.0
44
+
45
+ - Precise read positions. Receipts resolve per user from a monotonic read-through
46
+ position `(createdAt, id)` when the server provides one, and fall back to the
47
+ acknowledgement time only for legacy participants without a position. A read
48
+ request that arrives late no longer marks messages that were rendered after it
49
+ as read. The state exposes `readPositionByUserId` beside `readAtByUserId`;
50
+ `readerIdsFor`, `defaultReadersResolver`, `MessageListView` and
51
+ `ConversationView` accept both (`readAtByUserId` alone still works).
52
+ - Targeted acknowledgements. Automatic and explicit reads name the newest
53
+ rendered, non-pending message (`throughMessageId`); nothing is sent while
54
+ nothing is rendered. A withheld media-only row is acknowledged once its
55
+ hydration renders it. One request is in flight at a time, a follow-up is
56
+ resolved at send time, and targets at or before the accepted one are skipped.
57
+ A target the server does not know (`MESSAGE_NOT_FOUND`) is skipped and the
58
+ next newest row is acknowledged once; membership failures still surface as
59
+ errors.
60
+ - Visibility gating. Automatic acknowledgements wait while the document is
61
+ hidden and resume when it becomes visible; the SDK-backed `Conversation`
62
+ wires `document.visibilityState` and `visibilitychange`, and controllers gain
63
+ `setVisible(visible)`. Unknown, prerender and server environments count as
64
+ visible. `markReadOnLoad` / `markReadOnReceive` keep their meaning; with both
65
+ off no request is ever sent, including on visibility changes.
66
+ - 0.5.0 adapter change: `ConvoKitUiClient.markConversationRead(conversationId,
67
+ options?)` receives `{ throughMessageId }`. Custom adapters must forward it
68
+ and report an unknown target with `code: 'MESSAGE_NOT_FOUND'` (or a 404
69
+ without a code). One-argument implementations still type-check but would
70
+ acknowledge the server's newest message instead of the rendered one.
71
+ - Requires core SDK 0.5.x (`Participant.readPosition`, `ReadEvent.readPosition`,
72
+ `markConversationRead` target) and the coordinated backend. Mixed fleet:
73
+ precise receipts need both the sender's and the reader's clients on 0.5;
74
+ 0.4 readers keep timestamp semantics and keep parsing the additive payload.
75
+
3
76
  ## 0.4.1
4
77
 
5
78
  - Describe a confirmed outgoing message without readers as `Sent` instead of
package/PARITY.md CHANGED
@@ -14,10 +14,20 @@ while using Vue-native composition patterns.
14
14
  | Conversation state | Controller | `useConversation` | `useConversation` |
15
15
  | Conversation-list state | Controller | `useConversationList` | `useConversationList` |
16
16
  | Inbox offset pagination and de-duplication | Yes | Yes | Yes |
17
+ | Inbox cursor pagination, activity order, later-entry merge | Yes | Yes | Yes |
18
+ | Inbox summaries and viewer in list state (`summaries`, `currentUserId`) | Yes | Yes | Yes |
19
+ | Preview / time / unread badge rows (`badge` token) | `badgeColor` | `--ckui-badge` | `--ckui-badge` |
20
+ | Row replacements receive `summary` / `currentUserId` | `rowBuilder` | Render props | `#conversation-item` slot |
21
+ | `inbox_activity` throttle (`activityRefreshWindowMs`) | Yes | Yes | Yes |
22
+ | Adapter `listInbox` / `onInboxActivity` | Required | Optional | Optional |
23
+ | Inbox 404 fallback to the offset path | Yes | Yes | Yes |
17
24
  | Search, archived, participant, predicate, sort filters | Yes | Yes | Yes |
18
25
  | Realtime messages, typing, reads | Yes | Yes | Yes |
19
26
  | Pending state without provisional timestamps or receipts | Yes | Yes | Yes |
20
27
  | Read markers and reader resolution | Yes | Yes | Yes |
28
+ | Read positions (`readPositionByUserId`, `lastReadAt` fallback) | Yes | Yes | Yes |
29
+ | Targeted acknowledgements (`throughMessageId`, newest rendered row) | Yes | Yes | Yes |
30
+ | Visibility-gated auto-read | `WidgetsBindingObserver` | `visibilitychange` | `visibilitychange` |
21
31
  | Mark read on load/receive | Yes | Yes | Yes |
22
32
  | Typing idle timeout | Yes | Yes | Yes |
23
33
  | Image, file, location, contact rendering | Yes | Yes | Yes |
@@ -32,10 +42,29 @@ Vue also exposes controlled/uncontrolled composer text through `v-model`,
32
42
  per-part `classNames` and `styles`, `unstyled` mode, native root attributes,
33
43
  image loading policy, time formatting, scroll behavior controls, callback props,
34
44
  and exposed composable controllers.
35
- ## Unreleased recovery parity
45
+ ## Read-position parity (0.5.0)
36
46
 
37
- Room and inbox session eviction, value-cursor history, full viewed-range reconnect
38
- reconciliation, ID-only deletions and server-sourced read positions are tested in
39
- this feature branch. They require the unpublished core SDK recovery contract.
40
- The parity script checks API presence only; the controller, rendered-view and
41
- actual-SDK protocol tests verify behavior. See README 0.3.0 behavior and integration notes.
47
+ Every UI applies the same receipt rule per user: a read position wins when
48
+ present (`covers` by `(createdAt, id)`), otherwise `lastReadAt` is compared
49
+ with `createdAt`. Acknowledgements target the newest rendered non-pending
50
+ message, coalesce to one request in flight, skip targets at or before the last
51
+ accepted one, re-issue once on a `MESSAGE_NOT_FOUND` miss, and wait while the
52
+ platform reports the view as hidden. The adapter interface gained the optional
53
+ target in all libraries (`markConversationRead(conversationId, options?)` here).
54
+ The parity script checks API presence only; the store, rendered-view and
55
+ actual-SDK protocol tests verify behavior.
56
+
57
+ ## Inbox parity (0.6.0)
58
+
59
+ Every UI pages the inbox by the server cursor, keeps the server's order within a
60
+ page, merges pages by conversation ID with the later entry winning, and
61
+ re-orders merged rows by `(activityAt desc, id desc)` unless a comparator is
62
+ set. Rows show the same preview (`You:` for the viewer, `<name>:` for a named
63
+ member of a group of more than two, `Photo` / file name or `File` /
64
+ `Location` / `Contact` for media-only messages), the activity time, and an
65
+ unread badge (`99+` above 99 or when capped; accessible name
66
+ `<count> unread`). `inbox_activity` signals are coalesced by a max-wait window
67
+ (`activityRefreshWindowMs`, default 500 ms); `inbox_changed` stays immediate.
68
+ A 404 from the inbox route falls back to the offset path for the store's life
69
+ without evicting rows. The parity script checks API presence only; the store,
70
+ rendered-view and actual-SDK protocol tests verify behavior.
package/README.md CHANGED
@@ -11,6 +11,105 @@ React package: system typography, separated rows, subtle borders, compact
11
11
  actions, and restrained radii. It remains framework-CSS independent and does
12
12
  not require Tailwind in the host application.
13
13
 
14
+ ## 0.6.0 inbox previews and unread counts
15
+
16
+ This release requires JavaScript SDK 0.6.x (`listInbox`, `onInboxActivity`)
17
+ and the coordinated backend (`GET /api/v1/inbox`, the `inbox_activity` event).
18
+
19
+ - SDK-backed lists arrive in inbox order: the newest surviving message time
20
+ first (an empty room sorts by its creation time), paged by an opaque server
21
+ cursor instead of an offset. The store keeps the server's order within a page
22
+ and, whenever pages are combined (load more, refresh), merges by conversation
23
+ ID with the later entry winning and re-orders by `(activityAt desc, id desc)`.
24
+ Setting `filter.comparator` replaces that order; `updatedAt` is never an
25
+ ordering input. `mergeInboxEntries(current, incoming)` is exported and applies
26
+ the same rule.
27
+ - State gains `summaries` (a `ReadonlyMap<string, InboxSummary>` by conversation
28
+ ID: `latestMessage`, `unreadCount`, `unreadCountCapped`, `readPosition`,
29
+ `lastReadAt`, `activityAt`) and `currentUserId` (the bound session's user;
30
+ `''` without a session, on the legacy path and after disposal). `conversations`,
31
+ filters, comparators, custom offset `pageLoader`s and the `#conversation-item`
32
+ slot signature are unchanged.
33
+ - Default rows show a one-line preview instead of the participants line when a
34
+ summary carries a non-empty latest message: `You: …` for the viewer's own
35
+ message (DMs included), `<name>: …` for a named member of a group of more
36
+ than two, otherwise the body alone; media-only messages read `Photo`,
37
+ the file name or `File`, `Location`, `Contact`. Rows show the activity time
38
+ in the device zone and an unread badge (`5`, `99+` above 99 or when the
39
+ server capped the count) with the accessible name `<count> unread`
40
+ (`99+ unread` when capped); the title is bold while unread. The badge uses
41
+ the new theme token `badge` (`--ckui-badge`, defaults to the primary color)
42
+ with `outgoingText` as its label color.
43
+ - `#conversation-item` slot props gain `summary` and `currentUserId` (present
44
+ when known); existing slots keep working. `ConversationListView` accepts
45
+ `summaries` and `currentUserId` for controlled use. The inline error's
46
+ `Retry` requests the next page when `hasMore` and `onLoadMore` are bound
47
+ (past the view's duplicate-request guard), otherwise it refreshes; it
48
+ renders only when one of those callbacks exists.
49
+ - Live updates: `inbox_changed` still refreshes immediately; the new
50
+ `inbox_activity` signal (message inserts/edits and read-position advances,
51
+ fanned out to every client of the app) is throttled by
52
+ `activityRefreshWindowMs` (default 500 ms, 0 = immediate) with max-wait
53
+ semantics: the first signal opens a window, later signals wait for it, and one
54
+ refresh runs when it closes. An `inbox_changed` during the window refreshes
55
+ at once and drops the timer. `refresh()`, topic-error reconciliation and the
56
+ rejoin replay stay immediate. A refresh re-walks from the head with
57
+ `limit = min(100, target − consumed)` where `target = max(pageSize, loaded)`,
58
+ continues past pages the local filter hides, and swaps rows, summaries,
59
+ cursor and `hasMore` atomically; it never publishes an empty list with
60
+ `hasMore` while more pages exist. Rooms are open with `useConversation` as
61
+ before; room stores ignore `inbox_activity`.
62
+ - Adapter additions: `ConvoKitUiClient.listInbox?({ limit, cursor, archived })`
63
+ and `onInboxActivity?(handler, onError?)` are optional. The default adapter
64
+ implements both; custom adapters without `listInbox`, and custom `pageLoader`s,
65
+ keep the 0.5 offset path with empty `summaries` and no activity subscription.
66
+ A 404 from `listInbox` (route absent on a rolled-back backend) switches the
67
+ store to that path for the rest of its life without evicting rows (a warning
68
+ is logged once); a 404 from `getConversations` still evicts, 401/403 from
69
+ either endpoint evict, and 400 keeps rows and reports the error.
70
+
71
+ ## 0.5.0 precise read positions
72
+
73
+ This release requires JavaScript SDK 0.5.x and the coordinated backend.
74
+
75
+ - Read receipts resolve per user from a monotonic read-through position, the
76
+ `(createdAt, id)` of a concrete message the server resolved, and fall back to
77
+ the acknowledgement time only for legacy participants without a position. A
78
+ read request that arrives late no longer marks messages rendered after it as
79
+ read. `useConversation` and `Conversation` expose `readPositionByUserId`
80
+ beside `readAtByUserId`; both are seeded from participants and advanced by
81
+ server read events only, never from the device clock.
82
+ - Acknowledgements name the newest rendered, non-pending message
83
+ (`throughMessageId`). Nothing is sent while nothing is rendered, and a
84
+ media-only row is acknowledged only once its hydration renders it. One
85
+ request is in flight at a time; a follow-up is resolved at send time and
86
+ skipped when the newest row is already acknowledged. A target the server
87
+ does not know (`MESSAGE_NOT_FOUND`, for example deleted meanwhile) is
88
+ skipped and the next newest row is acknowledged once. Membership failures
89
+ still surface as errors.
90
+ - Automatic acknowledgements wait while the document is hidden and resume when
91
+ it becomes visible. The SDK-backed `Conversation` follows
92
+ `document.visibilityState` and `visibilitychange`; controllers gain
93
+ `setVisible(visible)` for host-driven surfaces (tabs, drawers, background
94
+ routes). Unknown, prerender and server environments count as visible.
95
+ `markReadOnLoad` and `markReadOnReceive` keep their meaning; with both off,
96
+ no request is ever sent, including on visibility changes. `markRead()` is
97
+ the host's decision and is not gated.
98
+ - Controlled views accept `readPositionByUserId` next to `readAtByUserId`;
99
+ `readerIdsFor(message, readAtByUserId, readPositionByUserId?)` and
100
+ `defaultReadersResolver` apply the unified rule. Custom `#message` and
101
+ `#read-receipt` slots and `readersResolver` receive the same reader IDs.
102
+ - Adapter change: `ConvoKitUiClient.markConversationRead(conversationId,
103
+ options?)` receives `{ throughMessageId }`. Custom adapters must forward it
104
+ to the core SDK (or their backend) and report an unknown target with
105
+ `code: 'MESSAGE_NOT_FOUND'` (or a 404 without a code). One-argument
106
+ implementations still type-check but acknowledge the server's newest message
107
+ instead of the rendered one.
108
+ - Mixed fleet: precise receipts need both the sender's and the reader's
109
+ clients on 0.5. 0.4 readers keep timestamp semantics and keep parsing the
110
+ additive payload; the dashboard shows both the read-through and the last
111
+ acknowledgement time.
112
+
14
113
  ## 0.4.0 live inbox, sending and recovery
15
114
 
16
115
  This release requires JavaScript SDK 0.4.x and the coordinated backend. The core
@@ -45,6 +144,7 @@ membership changes; no additional customer configuration is needed.
45
144
  - Read receipts use persisted participant positions and server read events,
46
145
  monotonically. A successful mark-read request does not fabricate a timestamp
47
146
  from the device clock. Remote typing expires and clears on disconnect.
147
+ (0.5.0 replaces the timestamp comparison with read positions; see above.)
48
148
  - Pending sends are bound to their original room/session. Reconciliation uses
49
149
  the exact clientMessageId, sender and room, never identical text or file count.
50
150
  A matching live/history row replaces its pending bubble immediately, even
@@ -148,6 +248,11 @@ Available slots include `conversation-item`, `separator`, `header`, `message`,
148
248
  Use `useConversationList` and `useConversation` when you want ConvoKit's
149
249
  pagination, de-duplication, realtime, typing, and read state without the
150
250
  default UI. Both return readonly Vue refs plus actions and a `dispose()` method.
251
+ `useConversationList` also exposes `summaries` and `currentUserId` and accepts
252
+ `activityRefreshWindowMs`; `ConversationList` forwards the same option.
253
+ `useConversation` also exposes `readPositionByUserId`, `readerIdsFor(message)`,
254
+ `markRead()` and `setVisible(visible)`; call `setVisible(false)` while your own
255
+ surface hides the room so automatic acknowledgements wait until it is shown.
151
256
 
152
257
  Optimistic rows display `Sending…` until acknowledgement. The final server
153
258
  timestamp is formatted in the viewer's local timezone. Custom message slots can
@@ -155,7 +260,8 @@ use `isConvoKitPendingMessage(message)` to present the same state.
155
260
 
156
261
  ## Appearance
157
262
 
158
- Wrap any subtree with `ConvoKitThemeProvider`, set `density="compact"`, or use
263
+ Wrap any subtree with `ConvoKitThemeProvider` (tokens include `badge` for the
264
+ unread counter), set `density="compact"`, or use
159
265
  the per-part `classNames` and `styles` maps. `unstyled` removes package classes
160
266
  from the configurable parts for a fully host-owned presentation.
161
267