palbase 0.19.2 → 0.21.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.
Files changed (52) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +3 -3
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/{EOMNR25V.js → 23M37HPW.js} +2 -2
  8. package/dist/chunks/{5IKIPSPK.js → 3MPGMDIO.js} +1 -1
  9. package/dist/chunks/{2D6UTNAS.js → 5BNYK6GP.js} +1 -1
  10. package/dist/chunks/{EEZBV4BZ.js → 6B2JVMVC.js} +1 -1
  11. package/dist/chunks/{SPRDYHDI.js → 7LCTRRHG.js} +1 -1
  12. package/dist/chunks/{OSREI5EV.js → BC7DKAEV.js} +2 -2
  13. package/dist/chunks/{34FI7FDO.js → BQTZYHJN.js} +2 -2
  14. package/dist/chunks/{GYAC4XGQ.js → BYKIJBPE.js} +2 -2
  15. package/dist/chunks/{MLQQWG2B.js → DEMLN2MO.js} +2 -2
  16. package/dist/chunks/{HNFLACEJ.js → LHC3VBGV.js} +1 -1
  17. package/dist/chunks/LHLXKWXT.js +2 -0
  18. package/dist/chunks/{CGPI3ZD2.js → PI2NU4TV.js} +2 -2
  19. package/dist/chunks/{OMXEFKCT.js → PJLACUYP.js} +3 -3
  20. package/dist/chunks/{ANPIBUP7.js → PKYERPHS.js} +2 -2
  21. package/dist/chunks/{PLAWOADG.js → QZMFPTXQ.js} +2 -2
  22. package/dist/chunks/{LH6MM4NX.js → RTL4OAKC.js} +1 -1
  23. package/dist/chunks/{3DPITTIG.js → SIIBIKCD.js} +2 -2
  24. package/dist/chunks/{AFNLDC5V.js → TACI6JAQ.js} +2 -2
  25. package/dist/chunks/{63ALEOYV.js → TUJQKUDT.js} +2 -2
  26. package/dist/chunks/{M23DJASA.js → UTZFJM2O.js} +2 -2
  27. package/dist/chunks/{65XX646N.js → XZY2DTL7.js} +2 -2
  28. package/dist/chunks/{KLXOLRFH.js → Y3KNVE2U.js} +1 -1
  29. package/dist/chunks/{B64PN4SN.js → YALT2FTV.js} +2 -2
  30. package/dist/cli.js +82 -82
  31. package/dist/cloud/index.js +2 -2
  32. package/dist/refuse/ai.js +1 -1
  33. package/dist/refuse/backend.js +1 -1
  34. package/dist/refuse/cloud.js +1 -1
  35. package/dist/refuse/server.js +1 -1
  36. package/dist/server/index.js +2 -2
  37. package/dist/server/testing.js +2 -2
  38. package/dist/web/index.js +2 -2
  39. package/dist/web/message.js +2 -2
  40. package/dist/web/palbe.js +2 -2
  41. package/dist/web/paywall.js +2 -2
  42. package/dist/web/push-sw.js +1 -1
  43. package/dist/web/ssr.js +2 -2
  44. package/dist/web/vite.js +2 -2
  45. package/package.json +2 -2
  46. package/types/internal/src/messaging/index.d.ts +1 -0
  47. package/types/internal/src/messaging/search.d.ts +19 -0
  48. package/types/internal/src/modules/machines.d.ts +28 -2
  49. package/types/internal/src/modules/messaging.d.ts +82 -2
  50. package/types/web/src/index.d.ts +1 -1
  51. package/types/web/src/messaging.d.ts +140 -9
  52. package/dist/chunks/AJUTYIGD.js +0 -2
@@ -798,6 +798,10 @@ export type MessagePage = {
798
798
  * The messages.
799
799
  */
800
800
  data: Message[];
801
+ /**
802
+ * The cursor to read on toward the newest (after); absent when the page reaches it. Only a page opened at a message (around), or read forward (after), has one.
803
+ */
804
+ newer_cursor?: string;
801
805
  /**
802
806
  * The next page's cursor; absent on the last page.
803
807
  */
@@ -1234,6 +1238,62 @@ export type ScheduledPage = {
1234
1238
  */
1235
1239
  next_cursor?: string;
1236
1240
  };
1241
+ /**
1242
+ * A message a search found, and where it says what was asked — or, found by its meaning, how near the question it is.
1243
+ */
1244
+ export type SearchHit = {
1245
+ /**
1246
+ * Where it says a word of the search, in the order said; none for a message found by its meaning.
1247
+ */
1248
+ matches: SearchMatch[];
1249
+ message: Message;
1250
+ /**
1251
+ * Found by its meaning (by=meaning): how near the question it is, the cosine of their vectors (higher: nearer; what is found is at least 0.3).
1252
+ */
1253
+ score?: number;
1254
+ };
1255
+ /**
1256
+ * Where a message found says a word of the search: in its text, or a file's name. offset and length are in UTF-16 code units, as mentions are.
1257
+ */
1258
+ export type SearchMatch = {
1259
+ /**
1260
+ * The file whose name it is (attachment_name).
1261
+ */
1262
+ attachment_id?: string;
1263
+ /**
1264
+ * Where: `text`, or `attachment_name` (a file's name).
1265
+ */
1266
+ field: "text" | "attachment_name";
1267
+ /**
1268
+ * How long what was asked is (the word's beginning that matched), in UTF-16 code units.
1269
+ */
1270
+ length: number;
1271
+ /**
1272
+ * Where the word starts, in UTF-16 code units.
1273
+ */
1274
+ offset: number;
1275
+ };
1276
+ /**
1277
+ * A page of what a search found, the newest first, with the conversations it is in and the people it shows — its writers, and the other person of a direct conversation — each once.
1278
+ */
1279
+ export type SearchPage = {
1280
+ /**
1281
+ * The conversations of the messages found, each once, as the reader's list shows them.
1282
+ */
1283
+ conversations: Conversation[];
1284
+ /**
1285
+ * The messages found.
1286
+ */
1287
+ data: SearchHit[];
1288
+ /**
1289
+ * The next page's cursor; absent on the last page. A page may hold fewer than limit — even none — while there is more to read: read on while next_cursor is there.
1290
+ */
1291
+ next_cursor?: string;
1292
+ /**
1293
+ * The people the page shows, each once: who wrote what was found, and the other person of a direct conversation. Someone with no profile is not here.
1294
+ */
1295
+ profiles: Profile[];
1296
+ };
1237
1297
  /**
1238
1298
  * A message to send.
1239
1299
  */
@@ -1304,6 +1364,10 @@ export type Settings = {
1304
1364
  * Messages older are removed; null: kept for good.
1305
1365
  */
1306
1366
  retention_days: number | null;
1367
+ /**
1368
+ * Messages are found by what they mean too (GET /messaging/search?by=meaning): each message's text — not a system message's, nor one seen a limited number of times, nor one of vanish mode — is embedded by the project's embedding model, through the AI module, billed to the project as its AI use (Palbase's provider: kept by it up to 30 days for abuse monitoring, never trained on). Off (the default): no message goes to a model.
1369
+ */
1370
+ search_by_meaning: boolean;
1307
1371
  };
1308
1372
  /**
1309
1373
  * Changes how the project runs its conversations; what it names changes.
@@ -1325,6 +1389,10 @@ export type SettingsRequest = {
1325
1389
  * 1 to 36,500; null: for good.
1326
1390
  */
1327
1391
  retention_days?: number | null;
1392
+ /**
1393
+ * Turned on, the messages written before are embedded too (a walk, in the background), and each one written from then on; turned off, nothing more is embedded and the vectors are removed.
1394
+ */
1395
+ search_by_meaning?: boolean;
1328
1396
  };
1329
1397
  /**
1330
1398
  * What changed for the reader since their cursor.
@@ -2048,7 +2116,7 @@ export declare function client(t: Transport): {
2048
2116
  /**
2049
2117
  * A conversation's messages
2050
2118
  *
2051
- * The conversation's own timeline as the reader sees it, the newest first, a page at a time; a message removed is there, said removed.
2119
+ * The conversation's own timeline as the reader sees it, the newest first, a page at a time; a message removed is there, said removed. Paged back from the newest (cursor: next_cursor), or opened at a message (around: the page holds it, with what came before and after it) and paged forward from there (after: newer_cursor) — a search's find shown where it was said. cursor, after and around are one at a time.
2052
2120
  */
2053
2121
  listMessages(input: {
2054
2122
  path: {
@@ -2056,7 +2124,9 @@ export declare function client(t: Transport): {
2056
2124
  };
2057
2125
  query?: {
2058
2126
  limit?: number; /** The cursor a page handed out (next_cursor). */
2059
- cursor?: string;
2127
+ cursor?: string; /** Open the timeline at this message: the page holds it, about half the page before it and half after (next_cursor pages back, newer_cursor forward). A thread's answer is read in its thread (`in_thread`). */
2128
+ around?: string; /** The cursor a page handed out to read on toward the newest (newer_cursor). */
2129
+ after?: string;
2060
2130
  }; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
2061
2131
  signal?: AbortSignal;
2062
2132
  }): Promise<Reply<200, MessagePage>>;
@@ -2328,6 +2398,16 @@ export declare function client(t: Transport): {
2328
2398
  body: UpdateScheduled; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
2329
2399
  signal?: AbortSignal;
2330
2400
  }): Promise<Reply<200, ScheduledMessage>>;
2401
+ searchMessages(input: {
2402
+ query: {
2403
+ q: string; /** words (the default): the messages that say every word of q. meaning: those nearest what q means (the project's search_by_meaning on): one page, the nearest first; cursor is not taken. */
2404
+ by?: "words" | "meaning"; /** Search one conversation of the reader's only. */
2405
+ conversation_id?: string; /** How many (1 to 50; 20 when absent). */
2406
+ limit?: number; /** The cursor a page handed out (next_cursor). */
2407
+ cursor?: string;
2408
+ }; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
2409
+ signal?: AbortSignal;
2410
+ }): Promise<Reply<200, SearchPage>>;
2331
2411
  /**
2332
2412
  * How the project runs its conversations
2333
2413
  *
@@ -10,7 +10,7 @@ export { connectVoice, type StartedVoice, type VoiceConnection, type VoiceOption
10
10
  export { Purchases, redemptionToken, type CheckoutSource, type CustomerInfo, type CustomerInfoListener, type GrantInfo, type MergeGrant, type OfferView, type Offerings, type Redeemed, type RedemptionLink, } from './purchases.js';
11
11
  export { Notify, WebPush, type PushPermission, type PushSubscribeOptions } from './notify.js';
12
12
  export { Flags, type FlagSource, type FlagsListener } from './flags.js';
13
- export { ConversationList, ConversationView, DeletedGroupList, indexedDBKeyValue, Messaging, ScheduledList, ThreadView, type Conversation, type ConversationViewState, type ConversationListState, type DeletedGroup, type DeletedGroupListState, type Entry, type Member, type Message, type MessagingOptions, type MyProfile, type Observed, type OpenedFile, type OutboxItem, type OutgoingFile, type Profile, type ScheduledMessage, type SendOptions, type ThreadState, type ThreadViewState, } from './messaging.js';
13
+ export { ConversationList, ConversationView, DeletedGroupList, indexedDBKeyValue, Messaging, ScheduledList, ThreadView, type Conversation, type ConversationMatch, type ConversationViewState, type ConversationListState, type DeletedGroup, type DeletedGroupListState, type Entry, type Hit, type Member, type Message, type MessagingOptions, type MyProfile, type Observed, type OpenedFile, type OutboxItem, type OutgoingFile, type Profile, type ScheduledMessage, type SearchHit, type SearchMatch, type SearchOptions, type SearchResults, type SendOptions, type ThreadState, type ThreadViewState, } from './messaging.js';
14
14
  export { Inbox, type InboxItem, type InboxListOptions, type InboxPage, type InboxView, type InboxViewOptions } from './inbox.js';
15
15
  export { InApp, SESSION_START, type InAppOptions } from './inapp.js';
16
16
  export type { Paywall, PaywallRenderOptions } from './paywall/placement.js';
@@ -1,5 +1,5 @@
1
1
  import { messaging as api } from '../../internal/src/modules/index.js';
2
- import { Engine, type DeletedGroup, type Entry, type KeyValue, type OutboxItem, type Touched } from '../../internal/src/messaging/index.js';
2
+ import { Engine, type DeletedGroup, type Entry, type Hit, type KeyValue, type OutboxItem, type Touched } from '../../internal/src/messaging/index.js';
3
3
  import { type Fetch, type Transport } from '../../internal/src/transport.js';
4
4
  import { type SessionManager } from './session.js';
5
5
  import type { RealtimeSocket } from './socket.js';
@@ -10,7 +10,8 @@ export type MyProfile = api.MyProfile;
10
10
  export type Profile = api.Profile;
11
11
  export type ScheduledMessage = api.ScheduledMessage;
12
12
  export type ThreadState = api.ThreadState;
13
- export type { DeletedGroup, Entry, OutboxItem } from '../../internal/src/messaging/index.js';
13
+ export type SearchMatch = api.SearchMatch;
14
+ export type { DeletedGroup, Entry, Hit, OutboxItem } from '../../internal/src/messaging/index.js';
14
15
  export interface MessagingOptions {
15
16
  /**
16
17
  * What the device keeps of a person's conversations when they sign out:
@@ -80,18 +81,92 @@ export interface DeletedGroupListState {
80
81
  }
81
82
  export interface ConversationViewState {
82
83
  conversation: Conversation | null;
83
- /** Its timeline: its messages, the oldest first, then what is on its way. */
84
+ /**
85
+ * Its timeline: its messages, the oldest first, then what is on its way. Opened at a message away from the newest
86
+ * (conversation(id, { at })), the stretch around it — what is on its way shows once it reaches the newest; nothing
87
+ * while the stretch is read (it is read once the view is subscribed), never the newest in the message's place.
88
+ */
84
89
  entries: Entry[];
85
90
  members: Member[];
86
91
  /** Who is typing now, by user id. */
87
92
  typing: string[];
88
93
  /** Older messages are there to read (loadEarlier). */
89
94
  hasEarlier: boolean;
95
+ /** Newer messages are there to read before the newest (loadLater): opened at a message away from it. */
96
+ hasLater: boolean;
97
+ /** The message it was opened at — what the app scrolls to and marks; null when opened at the newest. */
98
+ focus: string | null;
90
99
  }
91
100
  export interface ThreadViewState {
92
101
  root: Message | null;
93
102
  entries: Entry[];
94
103
  thread: ThreadState | null;
104
+ /** The answer it was opened at (thread(…, { at })); null otherwise. */
105
+ focus: string | null;
106
+ }
107
+ export interface SearchOptions {
108
+ /**
109
+ * How messages are found: by their words (the default) — every word asked, each a word's beginning — or by what
110
+ * they mean ('meaning'): those nearest the question, sharing none of its words perhaps ("flight time" finds "THY
111
+ * leaves at 14:30"), the nearest first, each with its score; one page, no conversations found by their name. By
112
+ * meaning, the project's setting search_by_meaning is on — or the search is refused, a PalbaseError whose code says
113
+ * why: 'search_by_meaning_off' (409), 'ai_payment_required' (402: the project's AI use is spent for the month;
114
+ * search by words meanwhile), 'too_many_searches' (429: a person asks 30 questions at once and one more a minute;
115
+ * retryAfter says when), 'busy' or 'embedding_unavailable' (503, retryAfter).
116
+ */
117
+ by?: 'words' | 'meaning';
118
+ /** Search one conversation of the person's only. */
119
+ conversationId?: string;
120
+ /** How many messages a page holds (1 to 50; 20 when absent). */
121
+ limit?: number;
122
+ /** The page after the one that handed out this cursor (nextCursor): a search by words alone. */
123
+ cursor?: string;
124
+ }
125
+ /** A message a search found, with what the app draws it with and where it opens. */
126
+ export interface SearchHit {
127
+ message: Message;
128
+ /** The conversation it is in. */
129
+ conversation: Conversation;
130
+ /** The conversation's name: a group's title, the other person's name (null when they have none). */
131
+ title: string | null;
132
+ /** A group's photo, the other person's image. */
133
+ photo: string | null;
134
+ /** Who wrote it (null: the project's server, or someone with no profile). */
135
+ sender: Profile | null;
136
+ /** The other person of a direct conversation. */
137
+ peer: Profile | null;
138
+ /** Where it says what was asked: its text, or a file's name (UTF-16 code units); none when found by its meaning. */
139
+ matches: SearchMatch[];
140
+ /**
141
+ * Found by its meaning (by: 'meaning'): how near the question it is — the cosine of their vectors, from 0.3 (what
142
+ * is found is at least that) to 1, higher nearer; null when found by its words.
143
+ */
144
+ score: number | null;
145
+ /**
146
+ * The thread it answers in, when its conversation's timeline does not show it: open it with
147
+ * thread(conversation.id, threadRootId, { at: message.id }); null — open conversation(conversation.id, { at: message.id }).
148
+ */
149
+ threadRootId: string | null;
150
+ }
151
+ /** A conversation of the person's whose name says what was asked. */
152
+ export interface ConversationMatch {
153
+ conversation: Conversation;
154
+ title: string | null;
155
+ photo: string | null;
156
+ peer: Profile | null;
157
+ /** Where its title says what was asked (UTF-16 code units). */
158
+ matches: Hit[];
159
+ }
160
+ export interface SearchResults {
161
+ /**
162
+ * The person's conversations whose name says every word asked, the latest first — on the first page alone of a
163
+ * search by words; none by meaning.
164
+ */
165
+ conversations: ConversationMatch[];
166
+ /** The messages found, the newest first — by meaning, the nearest first. */
167
+ messages: SearchHit[];
168
+ /** The next page's cursor (search(query, { cursor })); null on the last, and by meaning (one page). */
169
+ nextCursor: string | null;
95
170
  }
96
171
  /** Where a browser keeps it: IndexedDB, one object store of each person's state. */
97
172
  export declare function indexedDBKeyValue(name?: string): KeyValue | null;
@@ -125,6 +200,9 @@ export declare class Messaging {
125
200
  private saving;
126
201
  private readonly reopeners;
127
202
  private readonly incoming;
203
+ private readonly windows;
204
+ /** People's profiles read for a search (a direct conversation's name is the other person's), and when. */
205
+ private readonly people;
128
206
  private readonly session;
129
207
  private readonly socket;
130
208
  private readonly ref;
@@ -154,6 +232,12 @@ export declare class Messaging {
154
232
  private heardLive;
155
233
  /** Applies what changed to the person's conversations — to e's alone: what came for one who is gone goes nowhere. */
156
234
  private changed;
235
+ /** What changed, applied to the stretches read around a message too. */
236
+ private toWindows;
237
+ /** Messages put in a stretch: each goes when its time is up, as the timeline's do. */
238
+ private putWindow;
239
+ /** Runs take on each stretch of a conversation; answers whether any changed. */
240
+ private fromWindows;
157
241
  /** What follows a change: timers to take messages away, deliveries told, the device's copy saved. */
158
242
  private after;
159
243
  /** A message whose time is up goes from the device at once: the server says so too, a moment after. */
@@ -181,8 +265,14 @@ export declare class Messaging {
181
265
  * the word came (on the deleter's own device, now and then, the group the person deleted).
182
266
  */
183
267
  deletedGroups(): DeletedGroupList;
184
- /** A conversation of the person's, kept up to date: its timeline, its members, who is typing. */
185
- conversation(conversationId: string): ConversationView;
268
+ /**
269
+ * A conversation of the person's, kept up to date: its timeline, its members, who is typing. Opened at a message
270
+ * (at — a search's find), its timeline is the stretch around it, focus the message: loadEarlier and loadLater read
271
+ * on from there, and once it reaches the newest it is the conversation's timeline again.
272
+ */
273
+ conversation(conversationId: string, opts?: {
274
+ at?: string;
275
+ }): ConversationView;
186
276
  /** The direct conversation with someone — the one there is, or a new one. */
187
277
  direct(userId: string): Promise<ConversationView>;
188
278
  /** A new group, the person its owner. */
@@ -196,8 +286,23 @@ export declare class Messaging {
196
286
  conversation: ConversationView;
197
287
  notAdded: api.NotAdded[];
198
288
  }>;
199
- /** A thread: a message's answers, kept up to date. */
200
- thread(conversationId: string, rootId: string): ThreadView;
289
+ /** A thread: a message's answers, kept up to date — opened at an answer (at, a search's find), read on until it is there. */
290
+ thread(conversationId: string, rootId: string, opts?: {
291
+ at?: string;
292
+ }): ThreadView;
293
+ /**
294
+ * Finds the person's messages by their words — every word asked, each a word's beginning, as they type it
295
+ * ("cafe" finds "Café's", capitals and the Turkish i's alike) — in what they say or their files' names, the newest
296
+ * first, a page at a time: in all their conversations, or one (conversationId). Each comes with its conversation, its name and picture, who wrote
297
+ * it and where it says what was asked. On the first page, the conversations whose name says it too (a group's
298
+ * title, the other person's name), from the person's own list. A query with no word finds nothing.
299
+ *
300
+ * By meaning (by: 'meaning'; the project's setting search_by_meaning on), the messages nearest what the query
301
+ * means instead, the nearest first, each with its score: one page, no conversations by name. Opened as any find is.
302
+ */
303
+ search(query: string, opts?: SearchOptions): Promise<SearchResults>;
304
+ /** The person's conversations whose name says every word asked: a group's title, the other person's name. */
305
+ private named;
201
306
  /** The person's messages scheduled for later, kept up to date. */
202
307
  scheduled(): ScheduledList;
203
308
  /** The threads the person follows, the latest answered first. */
@@ -246,6 +351,11 @@ export declare class Messaging {
246
351
  * reportableUntil, naming no message: the last 30 messages the person saw in it go with the report.
247
352
  */
248
353
  report(input: api.ReportRequest): Promise<api.ReportCreated>;
354
+ /**
355
+ * A stretch that reaches the newest becomes the timeline's: both run to the newest, so together they are one —
356
+ * read back from the older of their ends.
357
+ */
358
+ private merge;
249
359
  }
250
360
  declare abstract class View<S> implements Observed<S> {
251
361
  protected listeners: Set<Listener>;
@@ -258,6 +368,7 @@ declare abstract class View<S> implements Observed<S> {
258
368
  get state(): S;
259
369
  subscribe(listener: Listener): () => void;
260
370
  protected opened(): void;
371
+ protected closed(): void;
261
372
  protected abstract touches(t: Touched): boolean;
262
373
  protected abstract read(): S;
263
374
  }
@@ -271,14 +382,32 @@ export declare class DeletedGroupList extends View<DeletedGroupListState> {
271
382
  }
272
383
  export declare class ConversationView extends View<ConversationViewState> {
273
384
  readonly id: string;
274
- constructor(m: Messaging, id: string);
385
+ /** The message it was opened at (conversation(id, { at })). */
386
+ readonly focus: string | null;
387
+ private window;
388
+ /** Opening it at its message: read, or failed (it opens at the newest then). */
389
+ private opening;
390
+ /** Opened at its message: until then nothing is drawn — not the newest in the message's place. */
391
+ private settled;
392
+ constructor(m: Messaging, id: string, focus?: string | null);
275
393
  protected opened(): void;
394
+ protected closed(): void;
276
395
  protected touches(t: Touched): boolean;
396
+ /** The stretch around the message it was opened at, while it is apart from the timeline. */
397
+ private stretch;
277
398
  protected read(): ConversationViewState;
278
399
  /** Reads the conversation's newest page now (opening it does). */
279
400
  refresh(): Promise<boolean>;
280
401
  /** Reads the page of messages before the oldest one drawn; answers whether there are more. */
281
402
  loadEarlier(): Promise<boolean>;
403
+ /**
404
+ * Reads the page of messages after the newest one drawn, opened at a message away from the newest; answers whether
405
+ * there are more. Once it reaches the newest, the timeline is the conversation's again — what comes live, what is on
406
+ * its way.
407
+ */
408
+ loadLater(): Promise<boolean>;
409
+ /** Puts aside the stretch it was opened at: its timeline is the conversation's newest again (focus stays). */
410
+ showLatest(): void;
282
411
  send(input: string | SendOptions): Promise<OutboxItem>;
283
412
  markRead(seq?: number): Promise<void>;
284
413
  typing(): Promise<void>;
@@ -287,7 +416,9 @@ export declare class ConversationView extends View<ConversationViewState> {
287
416
  export declare class ThreadView extends View<ThreadViewState> {
288
417
  readonly conversationId: string;
289
418
  readonly rootId: string;
290
- constructor(m: Messaging, conversationId: string, rootId: string);
419
+ /** The answer it was opened at (thread(…, { at })). */
420
+ readonly focus: string | null;
421
+ constructor(m: Messaging, conversationId: string, rootId: string, focus?: string | null);
291
422
  protected opened(): void;
292
423
  protected touches(t: Touched): boolean;
293
424
  protected read(): ThreadViewState;