Commit c51e1fe62291
tarball13 files changed+23-672
@@ -2,9 +2,9 @@ use std::collections::HashMap;
22
33
use futures_util::{Stream, StreamExt, stream};44
use macltok::{5
- ConnectionState, EncryptionState, HistoryRepairReport, ImagePacks, MentionsPage,6
- MentionsUpdate, MirrorHandle, RoomSummary, SessionStatus, State, StateChange, TimelineChange,7
- TimelineId, TimelineItem, UploadOutcome, VerificationState, imbl::Vector,5
+ ConnectionState, EncryptionState, ImagePacks, MentionsPage, MentionsUpdate, MirrorHandle,6
+ RoomSummary, SessionStatus, State, StateChange, TimelineChange, TimelineId, TimelineItem,7
+ UploadOutcome, VerificationState, imbl::Vector,88
};99
use serde::Serialize;1010
@@ -20,14 +20,10 @@ pub enum WireEvent {
2020
verification: Option<VerificationState>,2121
image_packs: ImagePacks,2222
mentions: Option<MentionsPage>,23
- repairs: HashMap<String, HistoryRepairReport>,2423
},2524
Mentions {2625
update: MentionsUpdate,2726
},28
- HistoryRepair {29
- report: HistoryRepairReport,30
- },3127
ImagePacks {3228
packs: ImagePacks,3329
},@@ -119,11 +115,6 @@ pub fn snapshot_event(state: &State) -> WireEvent {
119115
verification: state.verification.clone(),120116
image_packs: state.image_packs.clone(),121117
mentions: state.mentions.clone(),122
- repairs: state123
- .repairs124
- .iter()125
- .map(|(room_id, report)| (room_id.as_str().to_owned(), report.clone()))126
- .collect(),127118
}128119
}129120
@@ -193,9 +184,6 @@ pub fn wire_events(change: &StateChange) -> Vec<WireEvent> {
193184
StateChange::Mentions(update) => vec![WireEvent::Mentions {194185
update: update.clone(),195186
}],196
- StateChange::HistoryRepair(report) => vec![WireEvent::HistoryRepair {197
- report: report.clone(),198
- }],199187
StateChange::Failed(failure) => vec![error(&failure.message)],200188
StateChange::SessionSaveFailed(_) => {201189
vec![error(@@ -223,6 +211,7 @@ pub fn wire_events(change: &StateChange) -> Vec<WireEvent> {
223211
| StateChange::VerificationTarget(_)224212
| StateChange::NotificationSettings225213
| StateChange::History { .. }214
+ | StateChange::HistoryRepair(_)226215
| StateChange::Typing(_) => vec![],227216
}228217
}@@ -1,9 +1,8 @@
11
use macltok::ruma::{OwnedTransactionId, events::GlobalAccountDataEventType, serde::Raw};22
use macltok::{3
- AttachmentUpload, ClientHandle, CreateRoomOptions, HistoryRepairBudget, HistoryRepairPolicy,4
- ImagePackSource, MediaId, MediaSize, MemberFilter, MentionKey, MentionsBoundary,5
- MessageContent, MessageEditContent, MirrorHandle, PackImageInfo, PackUsage, Patch, RichText,6
- TimelineId, VerificationAction, VerificationTarget,3
+ AttachmentUpload, ClientHandle, CreateRoomOptions, ImagePackSource, MediaId, MediaSize,4
+ MemberFilter, MentionKey, MentionsBoundary, MessageContent, MessageEditContent, MirrorHandle,5
+ PackImageInfo, PackUsage, Patch, RichText, TimelineId, VerificationAction, VerificationTarget,76
};87
use serde::Deserialize;98
use serde_json::json;@@ -85,21 +84,6 @@ pub enum Request {
8584
#[serde(default)]8685
room_id: String,8786
},88
- HistoryRepair {89
- room_id: String,90
- #[serde(default)]91
- policy: Option<HistoryRepairPolicy>,92
- #[serde(default)]93
- budget: HistoryRepairBudget,94
- },95
- ContinueHistoryRepair {96
- room_id: String,97
- #[serde(default)]98
- budget: HistoryRepairBudget,99
- },100
- CancelHistoryRepair {101
- room_id: String,102
- },10387
StartRecentHistory {10488
room_id: String,10589
},@@ -107,10 +91,6 @@ pub enum Request {
10791
room_id: String,10892
gap_id: String,10993
},110
- HistoryRepairPolicy,111
- SetHistoryRepairPolicy {112
- policy: HistoryRepairPolicy,113
- },11494
EventContext {11595
#[serde(default)]11696
room_id: String,@@ -574,33 +554,11 @@ pub async fn dispatch(
574554
handle.paginate(TimelineId::Room(room_id), 30).await?;575555
Ok(Reply::Done)576556
}577
- Request::HistoryRepair {578
- room_id,579
- policy,580
- budget,581
- } => {582
- let report = handle583
- .history_repair(parse_room_id(&room_id)?, policy, budget)584
- .await?;585
- Ok(Reply::Json(json!(report)))586
- }587
- Request::ContinueHistoryRepair { room_id, budget } => {588
- let report = handle589
- .continue_history_repair(parse_room_id(&room_id)?, budget)590
- .await?;591
- Ok(Reply::Json(json!(report)))592
- }593
- Request::CancelHistoryRepair { room_id } => {594
- handle595
- .cancel_history_repair(parse_room_id(&room_id)?)596
- .await?;597
- Ok(Reply::Done)598
- }599557
Request::StartRecentHistory { room_id } => {600
- let report = handle558
+ handle601559
.start_recent_history(parse_room_id(&room_id)?)602560
.await?;603
- Ok(Reply::Json(json!(report)))561
+ Ok(Reply::Done)604562
}605563
Request::FillHistoryGap { room_id, gap_id } => {606564
if gap_id.is_empty() {@@ -611,13 +569,6 @@ pub async fn dispatch(
611569
.await?;612570
Ok(Reply::Done)613571
}614
- Request::HistoryRepairPolicy => {615
- Ok(Reply::Json(json!(handle.history_repair_policy().await?)))616
- }617
- Request::SetHistoryRepairPolicy { policy } => {618
- handle.set_history_repair_policy(policy).await?;619
- Ok(Reply::Done)620
- }621572
Request::EventContext {622573
room_id,623574
event_id,@@ -565,13 +565,8 @@ async fn post_command(
565565
| Request::SetMentionsRead { .. }566566
| Request::MarkMentionsReadThrough { .. }567567
| Request::RecoverMentions568
- | Request::HistoryRepair { .. }569
- | Request::ContinueHistoryRepair { .. }570
- | Request::CancelHistoryRepair { .. }571568
| Request::StartRecentHistory { .. }572569
| Request::FillHistoryGap { .. }573
- | Request::HistoryRepairPolicy574
- | Request::SetHistoryRepairPolicy { .. }575570
| Request::Kick { .. }576571
| Request::Ban { .. }577572
| Request::Unban { .. }@@ -4,8 +4,6 @@
44
import LoginPanel from './lib/LoginPanel.svelte';55
import MessageRow from './lib/MessageRow.svelte';66
import MentionsInbox from './lib/MentionsInbox.svelte';7
- import HistoryRepairPanel from './lib/HistoryRepairPanel.svelte';8
- import type { HistoryRepairPolicy, HistoryRepairReport } from './lib/repair-model';97
import MxcAvatar from './lib/MxcAvatar.svelte';108
import PinButton from './lib/PinButton.svelte';119
import ContextMenu, { type MenuItem } from './lib/ContextMenu.svelte';@@ -32,10 +30,6 @@
3230
getRooms,3331
getSession,3432
getSpaceChildren,35
- historyRepair,36
- continueHistoryRepair,37
- cancelHistoryRepair,38
- startRecentHistory,3933
fillHistoryGap,4034
joinRoom,4135
leaveRoom,@@ -72,8 +66,6 @@
7266
pushBanner,7367
markTimelineOpened,7468
imagePacks,75
- historyRepairs,76
- publishHistoryRepair,7769
resetClientState,7870
rooms,7971
selectedRoomId,@@ -187,116 +179,6 @@
187179
let spaceChildrenError: string | null = null;188180
let avatarByUser: Record<string, string | null> = {};189181
let restoringRoute = false;190
- let repairPanels: Record<string, boolean> = {};191
- let repairBusy: Record<string, boolean> = {};192
- let repairCancelling: Record<string, boolean> = {};193
- let repairErrors: Record<string, string | null> = {};194
- let repairJumps: Record<string, { previous: HistoryRepairReport | null }> = {};195
- let repairAccount: string | null | undefined;196
- let repairEpoch = 0;197
- $: if ($sessionUser !== repairAccount) {198
- repairAccount = $sessionUser;199
- repairEpoch++;200
- repairPanels = {};201
- repairBusy = {};202
- repairCancelling = {};203
- repairErrors = {};204
- repairJumps = {};205
- }206
- $: currentRepair = $selectedRoomId ? $historyRepairs[$selectedRoomId] ?? null : null;207
- $: currentRepairActive = !!($selectedRoomId && repairBusy[$selectedRoomId]) || repairActive(currentRepair);208
- $: maybeJumpAfterRepair($selectedRoomId, currentRepair, repairJumps);209
- $: settleRepairCancellation($historyRepairs, repairBusy);210
-211
- function repairActive(report: HistoryRepairReport | null | undefined): boolean {212
- return report?.phase === 'FetchingRecent' || report?.phase === 'Bridging';213
- }214
-215
- function settleRepairCancellation(reports: Record<string, HistoryRepairReport>, busy: Record<string, boolean>): void {216
- for (const [roomId, report] of Object.entries(reports)) {217
- if (repairCancelling[roomId] && !busy[roomId] && !repairActive(report)) {218
- repairCancelling = { ...repairCancelling, [roomId]: false };219
- }220
- }221
- }222
-223
- function maybeJumpAfterRepair(roomId: string | null, report: HistoryRepairReport | null, jumps: typeof repairJumps): void {224
- if (!roomId || roomId !== $selectedRoomId || !report?.recent_available || !jumps[roomId] || report === jumps[roomId].previous) return;225
- const next = { ...repairJumps };226
- delete next[roomId];227
- repairJumps = next;228
- stickToBottom = true;229
- const epoch = repairEpoch;230
- void tick().then(() => {231
- if (epoch === repairEpoch && $selectedRoomId === roomId) scrollToBottom();232
- });233
- }234
-235
- async function showHistoryRepair(roomId: string): Promise<void> {236
- if ($selectedRoomId !== roomId) await selectRoom(roomId);237
- if ($selectedRoomId === roomId) repairPanels = { ...repairPanels, [roomId]: true };238
- }239
-240
- async function runHistoryRepair(roomId: string, action: 'start' | 'continue' | 'recent', policy: HistoryRepairPolicy | null = null): Promise<void> {241
- if (!$sessionUser || repairBusy[roomId] || repairActive($historyRepairs[roomId]) || repairCancelling[roomId]) return;242
- const account = $sessionUser;243
- const epoch = repairEpoch;244
- repairPanels = { ...repairPanels, [roomId]: true };245
- repairBusy = { ...repairBusy, [roomId]: true };246
- repairErrors = { ...repairErrors, [roomId]: null };247
- if (action !== 'continue') repairJumps = { ...repairJumps, [roomId]: { previous: $historyRepairs[roomId] ?? null } };248
- try {249
- if (!openReady[roomId]) {250
- await openRoom(roomId);251
- if ($sessionUser !== account || epoch !== repairEpoch) return;252
- openReady = { ...openReady, [roomId]: true };253
- const failures = { ...openFailed };254
- delete failures[roomId];255
- openFailed = failures;256
- touchWarmRoom(roomId);257
- }258
- if (repairCancelling[roomId]) return;259
- const report = action === 'continue' ? await continueHistoryRepair(roomId)260
- : action === 'recent' ? await startRecentHistory(roomId) : await historyRepair(roomId, policy);261
- if ($sessionUser !== account || epoch !== repairEpoch) return;262
- if (report.room_id !== roomId) throw new Error('History repair returned a different room');263
- publishHistoryRepair(report, account);264
- maybeJumpAfterRepair(roomId, report, repairJumps);265
- } catch (cause) {266
- if ($sessionUser === account && epoch === repairEpoch) {267
- repairErrors = { ...repairErrors, [roomId]: cause instanceof Error ? cause.message : 'Could not repair history' };268
- }269
- } finally {270
- if ($sessionUser === account && epoch === repairEpoch) {271
- repairBusy = { ...repairBusy, [roomId]: false };272
- repairCancelling = { ...repairCancelling, [roomId]: false };273
- if (repairJumps[roomId]) {274
- const next = { ...repairJumps };275
- delete next[roomId];276
- repairJumps = next;277
- }278
- }279
- }280
- }281
-282
- async function cancelRoomRepair(roomId: string): Promise<void> {283
- if (!$sessionUser || repairCancelling[roomId]) return;284
- const account = $sessionUser;285
- const epoch = repairEpoch;286
- repairCancelling = { ...repairCancelling, [roomId]: true };287
- try {288
- await cancelHistoryRepair(roomId);289
- if ($sessionUser === account && epoch === repairEpoch && !repairBusy[roomId] && !repairActive($historyRepairs[roomId])) {290
- repairCancelling = { ...repairCancelling, [roomId]: false };291
- }292
- } catch (cause) {293
- if ($sessionUser === account && epoch === repairEpoch) {294
- repairCancelling = { ...repairCancelling, [roomId]: false };295
- repairErrors = { ...repairErrors, [roomId]: cause instanceof Error ? cause.message : 'Could not cancel history repair' };296
- }297
- }298
- }299
-300182
function motionDuration(ms: number): number {301183
return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : ms;302184
}@@ -781,11 +663,6 @@
781663
782664
function noteUserScroll(): void {783665
timelineViewport?.releaseScroll();784
- if ($selectedRoomId && repairJumps[$selectedRoomId]) {785
- const next = { ...repairJumps };786
- delete next[$selectedRoomId];787
- repairJumps = next;788
- }789666
}790667
791668
function syncComposer(node: HTMLTextAreaElement) {@@ -1177,11 +1054,10 @@
11771054
let pinsExpanded = false;11781055
let pinPreview: TimelineEvent | null = null;11791056
let pinJumpBusy = false;1180
- $: syncPinned(selectedRoom?.room_id ?? null, (selectedRoom?.pinned_events.length ?? 0) > 0, $selectedRoomId ? $pinnedTimelineVersions[$selectedRoomId] ?? 0 : 0, currentRepairActive);1181
- function syncPinned(roomId: string | null, hasPins: boolean, version: number, repairing: boolean): void {1057
+ $: syncPinned(selectedRoom?.room_id ?? null, (selectedRoom?.pinned_events.length ?? 0) > 0, $selectedRoomId ? $pinnedTimelineVersions[$selectedRoomId] ?? 0 : 0);1058
+ function syncPinned(roomId: string | null, hasPins: boolean, version: number): void {11821059
const want = hasPins ? roomId : null;11831060
if (want === pinnedOpenFor && version === pinnedCloseVersion) return;1184
- if (want && repairing) return;11851061
if (pinnedOpenFor) runCommand({ op: 'close_pinned', room_id: pinnedOpenFor }).catch(() => undefined);11861062
pinnedOpenFor = want;11871063
pinnedCloseVersion = version;@@ -1482,7 +1358,7 @@
14821358
14831359
async function paginate(): Promise<void> {14841360
const roomId = $selectedRoomId;1485
- if (!roomId || paginateBusy || currentRepairActive) return;1361
+ if (!roomId || paginateBusy) return;14861362
paginateBusy = true;14871363
try {14881364
await paginateRoom(roomId);@@ -1746,9 +1622,6 @@
17461622
items.push({ label: room.favourite ? 'Unpin' : room.space ? 'Pin to top' : 'Pin', action: () => void togglePin(room) });17471623
items.push({ label: 'Copy link', action: () => copyText(`https://matrix.to/#/${room.canonical_alias ?? room.room_id}`) });17481624
items.push({ label: 'Copy room ID', action: () => copyText(room.room_id) });1749
- if (!room.space && room.membership === 'Joined') {1750
- items.push({ label: 'Repair message history', action: () => void showHistoryRepair(room.room_id) });1751
- }17521625
if (room.membership === 'Joined') items.push({ label: 'Leave', danger: true, divider: true, action: () => void leave(room.room_id) });17531626
return items;17541627
}@@ -2129,9 +2002,6 @@
21292002
{#if roomMenuOpen}21302003
<div class="room-menu" role="menu">21312004
{#if selectedRoom.membership === 'Joined'}2132
- <button type="button" role="menuitem" on:click={() => roomMenuAction(() => void showHistoryRepair(selectedRoom.room_id))}>2133
- Repair message history2134
- </button>21352005
<button21362006
type="button"21372007
role="menuitem"@@ -2484,19 +2354,6 @@
24842354
</p>24852355
</div>24862356
{:else}2487
- {#if repairPanels[selectedRoom.room_id] ?? !!currentRepair}2488
- <HistoryRepairPanel2489
- report={currentRepair}2490
- busy={!!repairBusy[selectedRoom.room_id]}2491
- cancelling={!!repairCancelling[selectedRoom.room_id]}2492
- error={repairErrors[selectedRoom.room_id] ?? null}2493
- onStart={(policy) => void runHistoryRepair(selectedRoom.room_id, 'start', policy)}2494
- onCancel={() => void cancelRoomRepair(selectedRoom.room_id)}2495
- onContinue={() => void runHistoryRepair(selectedRoom.room_id, 'continue')}2496
- onRecent={() => void runHistoryRepair(selectedRoom.room_id, 'recent')}2497
- onClose={() => (repairPanels = { ...repairPanels, [selectedRoom.room_id]: false })}2498
- />2499
- {/if}25002357
{#if pins.length}25012358
<div class="pinned-strip" class:open={pinsExpanded}>25022359
<div class="pinned-head">@@ -2600,7 +2457,7 @@
26002457
{#if hasStart}26012458
<p class="start-note">Beginning of history</p>26022459
{:else}2603
- <button type="button" class="ghost-btn" disabled={paginateBusy || currentRepairActive} on:click={() => void paginate()}>2460
+ <button type="button" class="ghost-btn" disabled={paginateBusy} on:click={() => void paginate()}>26042461
{paginateBusy ? 'Loading…' : 'Load older messages'}26052462
</button>26062463
{/if}@@ -1,148 +0,0 @@
1
-<script lang="ts">2
- import EmojiText from './EmojiText.svelte';3
- import type { HistoryRepairPolicy, HistoryRepairReport } from './repair-model';4
-5
- export let report: HistoryRepairReport | null = null;6
- export let busy = false;7
- export let cancelling = false;8
- export let error: string | null = null;9
- export let onStart: (policy: HistoryRepairPolicy | null) => void;10
- export let onCancel: () => void;11
- export let onContinue: () => void;12
- export let onRecent: () => void;13
- export let onClose: () => void;14
-15
- let expanded = true;16
- let choice: 'saved' | 'StartRecent' | 'Bridge' | 'BridgeShortGaps' = 'saved';17
- let threshold: number | undefined = 15000;18
-19
- $: active = busy || cancelling || report?.phase === 'FetchingRecent' || report?.phase === 'Bridging';20
- $: canRepair = choice !== 'BridgeShortGaps' || (threshold !== undefined && Number.isSafeInteger(threshold) && threshold >= 0);21
- $: serverFailure = serverError(report?.failure?.server);22
- $: paused = !active && report?.phase === 'Paused';23
- $: canContinue = paused && report?.strategy === 'Bridge';24
-25
- function serverError(value: unknown): string {26
- if (!value || typeof value !== 'object' || Array.isArray(value)) return '';27
- const server = value as Record<string, unknown>;28
- const status = server.status;29
- const code = server.errcode;30
- const message = server.message;31
- if ((typeof status !== 'number' || !Number.isFinite(status)) && typeof code !== 'string' && typeof message !== 'string') return '';32
- const parts = [typeof status === 'number' ? `HTTP ${status}` : '', typeof code === 'string' ? code : '', typeof message === 'string' ? message : ''];33
- return parts.filter(Boolean).join(' · ');34
- }35
-36
- function policy(): HistoryRepairPolicy | null {37
- if (choice === 'saved') return null;38
- if (choice === 'BridgeShortGaps') return { BridgeShortGaps: { threshold_ms: threshold! } };39
- return choice;40
- }41
-42
- function stopLabel(value: NonNullable<HistoryRepairReport['stop']>): string {43
- const labels: Record<string, string> = {44
- Budget: 'Paused at the repair budget',45
- Cancelled: 'Repair cancelled',46
- NoAnchor: 'No retained history anchor was found',47
- HistoryUnavailable: 'History is unavailable',48
- CursorStalled: 'Repair made no progress'49
- };50
- return labels[value];51
- }52
-53
- function continuityLabel(value: HistoryRepairReport['continuity']): string {54
- if (value === 'Unresolved') return 'Continuity unresolved';55
- if (value === 'Verified') return 'Continuity verified';56
- if (value === 'AccessibleStart') return 'Beginning of accessible history reached';57
- return value;58
- }59
-60
- function start(): void {61
- if (canRepair) onStart(policy());62
- }63
-</script>64
-65
-<section class="repair" aria-label="History repair">66
- <div class="banner">67
- <button type="button" class="summary" aria-expanded={expanded} on:click={() => expanded = !expanded}>68
- <span>History repair</span>69
- {#if active}<span class="muted">{cancelling ? 'Cancelling…' : report?.phase === 'Bridging' ? 'Bridging history…' : 'Loading recent messages…'}</span>{/if}70
- {#if report?.recent_available === true}<span class="recent">Recent messages available</span>71
- {:else if report?.recent_available === false}<span class="muted">Recent messages unavailable</span>72
- {:else}<span class="muted">{active ? 'Repair in progress' : 'History recovery'}</span>{/if}73
- {#if report?.recent_available === true && report.continuity === 'Unresolved'}<span class="muted">Continuity unresolved</span>{/if}74
- <span aria-hidden="true">{expanded ? '▴' : '▾'}</span>75
- </button>76
- <button type="button" class="close" aria-label="Close history repair" on:click={onClose}>✕</button>77
- </div>78
- {#if expanded}79
- <div class="details" aria-live="polite">80
- {#if report}81
- <p class="state">{#if active}{report.phase === 'FetchingRecent' ? 'Loading recent messages' : 'Bridging retained history'}{:else if report.recent_available}Recent messages are available{:else}Recent messages could not be restored{/if}</p>82
- <p>{continuityLabel(report.continuity)}{report.continuity === 'Unresolved' ? '. The missing interval has not been linked to retained history.' : '.'}</p>83
- <p>{report.strategy === 'Recent' ? 'Recent history' : report.strategy === 'Overlap' ? 'Retained overlap' : 'Bridge progress'} · {report.pages} pages and {report.events} events</p>84
- {#if report.coverage_duration_ms !== null}<p>Gap duration {report.coverage_duration_ms} ms</p>{/if}85
- {#if report.stop}<p>{stopLabel(report.stop)}</p>{/if}86
- {#if report.phase === 'Paused' && report.strategy === 'Bridge'}<p class="muted">Keep this room open to continue bridging. Closing it or restarting the app clears unfinished progress.</p>{/if}87
- {#if report.failure}88
- <p class="failure"><EmojiText text={report.failure.message} /></p>89
- {#if serverFailure}<p class="server-error"><EmojiText text={serverFailure} /></p>{/if}90
- {/if}91
- {#if !report.threshold_evaluated && typeof report.requested_policy !== 'string'}92
- <p>The requested threshold of {report.requested_policy.BridgeShortGaps.threshold_ms} ms was not evaluated. Recent messages were used.</p>93
- {/if}94
- {/if}95
- {#if error}<p class="failure" role="alert"><EmojiText text={error} /></p>{/if}96
- {#if !active}97
- <label class="field">Repair policy98
- <select bind:value={choice}>99
- <option value="saved">Use saved policy</option>100
- <option value="StartRecent">Start recent</option>101
- <option value="Bridge">Bridge retained history</option>102
- <option value="BridgeShortGaps">Bridge short gaps</option>103
- </select>104
- </label>105
- {#if choice === 'BridgeShortGaps'}106
- <label class="field">Threshold in milliseconds107
- <input type="number" min="0" step="1" bind:value={threshold} />108
- </label>109
- <p class="muted">The threshold cannot yet be evaluated, so this currently starts at recent messages. A threshold of zero disables automatic bridging. Bridge retained history remains available.</p>110
- {/if}111
- {/if}112
- <div class="actions">113
- {#if active}114
- <button type="button" class="ghost-btn" disabled={cancelling} on:click={onCancel}>{cancelling ? 'Cancelling…' : 'Cancel repair'}</button>115
- {#if cancelling}<span role="status">Waiting for the active repair to stop</span>{/if}116
- {:else}117
- <button type="button" class="primary-btn" disabled={!canRepair} on:click={start}>Repair history</button>118
- {#if canContinue}119
- <button type="button" class="ghost-btn" on:click={onContinue}>Continue bridging</button>120
- {/if}121
- {#if paused}122
- <button type="button" class="ghost-btn" on:click={onRecent}>Use recent window</button>123
- {/if}124
- {/if}125
- <button type="button" class="ghost-btn" on:click={onClose}>Close</button>126
- </div>127
- </div>128
- {/if}129
-</section>130
-131
-<style>132
- .repair { max-height: min(42vh, 320px); overflow: auto; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); color: var(--text); }133
- .banner { display: flex; align-items: center; gap: 6px; padding: 6px; }134
- .summary { display: flex; flex: 1; min-width: 0; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; padding: 5px 7px; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; }135
- .summary span:first-child { font-weight: 600; }136
- .recent { color: var(--ok-text); font-size: 12px; }137
- .muted { color: var(--muted); font-size: 12px; }138
- .close { border: 0; background: transparent; color: var(--muted); cursor: pointer; }139
- .details { overflow: auto; padding: 0 12px 12px; font-size: 13px; }140
- .details p { margin: 7px 0; }141
- .state { font-weight: 600; }142
- .field { display: flex; flex-direction: column; gap: 5px; margin-top: 10px; }143
- select, input { width: 100%; min-height: var(--ctl-h); padding: 5px 7px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg-soft); color: var(--text); font: inherit; }144
- .actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }145
- .actions [role='status'] { color: var(--muted); }146
- .failure { color: var(--danger-text); }147
- @media (max-width: 520px) { .summary { flex-wrap: wrap; justify-content: flex-start; } }148
-</style>@@ -1,103 +0,0 @@
1
-<script lang="ts">2
- import { get } from 'svelte/store';3
- import { onMount } from 'svelte';4
- import { historyRepairPolicy, setHistoryRepairPolicy } from './api';5
- import { sessionUser } from './store';6
- import type { HistoryRepairPolicy } from './repair-model';7
-8
- let selected: 'StartRecent' | 'Bridge' | 'BridgeShortGaps' = 'StartRecent';9
- let threshold: number | undefined = 15000;10
- let loading = true;11
- let loaded = false;12
- let busy = false;13
- let error = '';14
- let notice = '';15
- let alive = true;16
-17
- async function loadPolicy(): Promise<void> {18
- const account = get(sessionUser);19
- if (!account) {20
- loading = false;21
- loaded = false;22
- error = 'Sign in to load the saved history repair policy.';23
- return;24
- }25
- loading = true;26
- loaded = false;27
- error = '';28
- try {29
- const policy = await historyRepairPolicy();30
- if (!alive || get(sessionUser) !== account) return;31
- if (policy === 'StartRecent' || policy === 'Bridge') selected = policy;32
- else {33
- selected = 'BridgeShortGaps';34
- threshold = policy.BridgeShortGaps.threshold_ms;35
- }36
- loaded = true;37
- } catch (cause) {38
- if (alive && get(sessionUser) === account) error = cause instanceof Error ? cause.message : 'Could not load the saved history repair policy.';39
- } finally {40
- if (alive && get(sessionUser) === account) loading = false;41
- }42
- }43
-44
- onMount(() => {45
- void loadPolicy();46
- return () => { alive = false; };47
- });48
-49
- async function save(): Promise<void> {50
- error = '';51
- notice = '';52
- const ms = threshold;53
- if (!loaded || busy || loading) return;54
- if (selected === 'BridgeShortGaps' && (ms === undefined || !Number.isSafeInteger(ms) || ms < 0)) {55
- error = 'Enter a whole number of milliseconds that is zero or greater.';56
- return;57
- }58
- const account = get(sessionUser);59
- if (!account) return;60
- const policy: HistoryRepairPolicy = selected === 'BridgeShortGaps'61
- ? { BridgeShortGaps: { threshold_ms: ms! } }62
- : selected;63
- busy = true;64
- try {65
- await setHistoryRepairPolicy(policy);66
- if (alive && get(sessionUser) === account) notice = 'History repair policy saved.';67
- } catch (cause) {68
- if (alive && get(sessionUser) === account) error = cause instanceof Error ? cause.message : 'Could not save the history repair policy.';69
- } finally {70
- if (alive && get(sessionUser) === account) busy = false;71
- }72
- }73
-</script>74
-75
-<section aria-label="History repair settings">76
- <h3 class="sec-h">History repair</h3>77
- <label class="field">Saved policy78
- <select bind:value={selected} disabled={loading || busy}>79
- <option value="StartRecent">Start recent</option>80
- <option value="Bridge">Bridge retained history</option>81
- <option value="BridgeShortGaps">Bridge short gaps</option>82
- </select>83
- </label>84
- {#if selected === 'BridgeShortGaps'}85
- <label class="field">Gap threshold in milliseconds86
- <input type="number" min="0" step="1" bind:value={threshold} disabled={loading || busy} />87
- </label>88
- <p class="note">The threshold cannot yet be evaluated, so this policy currently starts at recent messages. A threshold of zero disables automatic bridging. Choose Bridge retained history to bridge explicitly.</p>89
- {/if}90
- <button type="button" class="primary-btn" disabled={!loaded || loading || busy || (selected === 'BridgeShortGaps' && (threshold === undefined || !Number.isSafeInteger(threshold) || threshold < 0))} on:click={save}>{busy ? 'Saving…' : 'Save'}</button>91
- {#if !loaded && !loading}<button type="button" class="ghost-btn" disabled={busy} on:click={() => void loadPolicy()}>Retry loading saved policy</button>{/if}92
- {#if error}<p class="error" role="alert">{error}</p>{/if}93
- {#if notice}<p class="notice" role="status">{notice}</p>{/if}94
-</section>95
-96
-<style>97
- section { margin-top: 18px; }98
- .sec-h { margin: 0 0 8px; color: var(--muted); font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }99
- .field { display: flex; flex-direction: column; gap: 6px; margin: 10px 0; font-size: 13px; }100
- select, input { width: 100%; min-height: var(--ctl-h); padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg-soft); color: var(--text); font: inherit; }101
- .note, .notice { color: var(--muted); font-size: 13px; }102
- .error { color: var(--danger-text); background: var(--danger-bg); padding: 8px; border-radius: var(--r-sm); }103
-</style>@@ -13,7 +13,6 @@
1313
import { DEFAULT_QUICK_REACTIONS } from './quick-reactions';1414
import ReactionPicker from './ReactionPicker.svelte';1515
import ThemeSettings from './ThemeSettings.svelte';16
- import HistoryRepairSettings from './HistoryRepairSettings.svelte';1716
import type { ImagePack, QuickReaction } from './types';1817
1918
export let quick: QuickReaction[];@@ -36,7 +35,6 @@
3635
</div>3736
3837
<ThemeSettings />39
- <HistoryRepairSettings />4038
4139
<section aria-label="Message spacing">4240
<h3 class="sec-h">Space between messages</h3>@@ -3,16 +3,13 @@
33
import type { EventContext, EventContextPage, MemberSummary, RoomSummary } from './types';44
import type { Emote } from './emotes';55
import type { MentionKey, MentionsBoundary, MentionsPage, SavedMentionContext } from './mentions-model';6
-import type { HistoryRepairBudget, HistoryRepairPolicy, HistoryRepairReport } from './repair-model';7
-export { parseHistoryRepairPolicy, parseHistoryRepairReport } from './repair-model';8
-export type { HistoryRepairBudget, HistoryRepairFailure, HistoryRepairPolicy, HistoryRepairReport } from './repair-model';96
export { mentionKey, parseMentionsPage, parseSavedMentionContext, parseMentionsUpdate } from './mentions-model';107
export type { MentionKey, MentionReason, MentionCaptureState, MentionEntry, MentionCoverage, MentionGap, MentionsBoundary, MentionsPage, SavedMentionContext, MentionsUpdate } from './mentions-model';118
import { formatBody } from './markdown';129
import { mentionedUserIds, type Mention } from './mentions';1310
import { HttpTransport } from './transport-http';1411
import type { CommandRequest, SendKind, SessionInfo, Transport, VerifyAction } from './transport';15
-export type { HistoryRepairRequest } from './transport';12
+export type { HistoryRequest } from './transport';1613
import { createDeviceTransport } from 'malice:device-transport';1714
1815
export { ApiError } from './transport';@@ -68,15 +65,8 @@ export const retryMentionContext = (room_id: string, event_id: string): Promise<
6865
export const setMentionsRead = (keys: MentionKey[], read: boolean) => transport.setMentionsRead(keys, read);6966
export const markMentionsReadThrough = (boundary: MentionsBoundary) => transport.markMentionsReadThrough(boundary);7067
export const recoverMentions = () => transport.recoverMentions();71
-export const historyRepair = (room_id: string, policy: HistoryRepairPolicy | null = null, budget?: HistoryRepairBudget): Promise<HistoryRepairReport> =>72
- transport.historyRepair(room_id, policy, budget);73
-export const continueHistoryRepair = (room_id: string, budget?: HistoryRepairBudget): Promise<HistoryRepairReport> =>74
- transport.continueHistoryRepair(room_id, budget);75
-export const cancelHistoryRepair = (room_id: string): Promise<void> => transport.cancelHistoryRepair(room_id);76
-export const startRecentHistory = (room_id: string): Promise<HistoryRepairReport> => transport.startRecentHistory(room_id);68
+export const startRecentHistory = (room_id: string): Promise<void> => transport.startRecentHistory(room_id);7769
export const fillHistoryGap = (room_id: string, gap_id: string): Promise<void> => transport.fillHistoryGap(room_id, gap_id);78
-export const historyRepairPolicy = (): Promise<HistoryRepairPolicy> => transport.historyRepairPolicy();79
-export const setHistoryRepairPolicy = (policy: HistoryRepairPolicy): Promise<void> => transport.setHistoryRepairPolicy(policy);8070
export const setTyping = (room_id: string, typing: boolean) => transport.setTyping(room_id, typing);8171
export const getAccountData = (eventType: string) => transport.getAccountData(eventType);8272
export const getUrlPreview = (room_id: string, url: string) => transport.getUrlPreview(room_id, url);@@ -1,94 +0,0 @@
1
-export type HistoryRepairPolicy =2
- | 'StartRecent'3
- | 'Bridge'4
- | { BridgeShortGaps: { threshold_ms: number } };5
-6
-export interface HistoryRepairBudget {7
- max_pages: number;8
- max_events: number;9
- max_duration_ms: number;10
-}11
-12
-export interface HistoryRepairFailure {13
- kind: string;14
- message: string;15
- server?: { status: number; errcode: string; message: string } | null;16
- [key: string]: unknown;17
-}18
-19
-export interface HistoryRepairReport {20
- room_id: string;21
- requested_policy: HistoryRepairPolicy;22
- strategy: 'Recent' | 'Overlap' | 'Bridge';23
- coverage_duration_ms: number | null;24
- threshold_evaluated: boolean;25
- pages: number;26
- events: number;27
- phase: 'FetchingRecent' | 'Bridging' | 'Ready' | 'Paused';28
- continuity: 'Unresolved' | 'Verified' | 'AccessibleStart';29
- recent_available: boolean;30
- stop: 'Cancelled' | 'Budget' | 'HistoryUnavailable' | 'NoAnchor' | 'CursorStalled' | null;31
- failure: HistoryRepairFailure | null;32
-}33
-34
-export const DEFAULT_HISTORY_REPAIR_BUDGET: HistoryRepairBudget = {35
- max_pages: 10,36
- max_events: 1000,37
- max_duration_ms: 1500038
-};39
-40
-function record(value: unknown): Record<string, unknown> | null {41
- return typeof value === 'object' && value !== null && !Array.isArray(value)42
- ? (value as Record<string, unknown>)43
- : null;44
-}45
-46
-function safeNonnegativeInteger(value: unknown): value is number {47
- return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;48
-}49
-50
-export function parseHistoryRepairPolicy(value: unknown): HistoryRepairPolicy | null {51
- if (value === 'StartRecent' || value === 'Bridge') return value;52
- const outer = record(value);53
- const details = outer && record(outer['BridgeShortGaps']);54
- if (!outer || Object.keys(outer).length !== 1 || !details || Object.keys(details).length !== 1 || !safeNonnegativeInteger(details['threshold_ms'])) return null;55
- return { BridgeShortGaps: { threshold_ms: details['threshold_ms'] } };56
-}57
-58
-function isOneOf<T extends string>(value: unknown, values: readonly T[]): value is T {59
- return typeof value === 'string' && (values as readonly string[]).includes(value);60
-}61
-62
-export function parseHistoryRepairReport(value: unknown): HistoryRepairReport | null {63
- const data = record(value);64
- if (!data) return null;65
- const policy = parseHistoryRepairPolicy(data['requested_policy']);66
- const failure = data['failure'];67
- const failureData = failure === null ? null : record(failure);68
- if (69
- typeof data['room_id'] !== 'string' || !policy ||70
- !isOneOf(data['strategy'], ['Recent', 'Overlap', 'Bridge']) ||71
- !(data['coverage_duration_ms'] === null || safeNonnegativeInteger(data['coverage_duration_ms'])) ||72
- typeof data['threshold_evaluated'] !== 'boolean' ||73
- !safeNonnegativeInteger(data['pages']) || !safeNonnegativeInteger(data['events']) ||74
- !isOneOf(data['phase'], ['FetchingRecent', 'Bridging', 'Ready', 'Paused']) ||75
- !isOneOf(data['continuity'], ['Unresolved', 'Verified', 'AccessibleStart']) ||76
- typeof data['recent_available'] !== 'boolean' ||77
- !(data['stop'] === null || isOneOf(data['stop'], ['Cancelled', 'Budget', 'HistoryUnavailable', 'NoAnchor', 'CursorStalled'])) ||78
- !(failure === null || (failureData && typeof failureData['kind'] === 'string' && typeof failureData['message'] === 'string'))79
- ) return null;80
- return {81
- room_id: data['room_id'],82
- requested_policy: policy,83
- strategy: data['strategy'],84
- coverage_duration_ms: data['coverage_duration_ms'],85
- threshold_evaluated: data['threshold_evaluated'],86
- pages: data['pages'],87
- events: data['events'],88
- phase: data['phase'],89
- continuity: data['continuity'],90
- recent_available: data['recent_available'],91
- stop: data['stop'],92
- failure: failureData as HistoryRepairFailure | null93
- };94
-}@@ -1,8 +1,6 @@
11
import { get, writable } from 'svelte/store';22
import { parseMentionsPage, parseMentionsUpdate } from './mentions-model';33
import type { MentionsPage } from './mentions-model';4
-import { parseHistoryRepairReport } from './repair-model';5
-import type { HistoryRepairReport } from './repair-model';64
import { getSession, subscribeEvents } from './api';75
import type { SoftLogout } from './api';86
import { isRecord } from './guards';@@ -45,12 +43,6 @@ export const rooms = writable<RoomSummary[]>([]);
4543
export const timelines = writable<Record<string, TimelineItem[]>>({});4644
export const pinnedTimelines = writable<Record<string, TimelineItem[]>>({});4745
export const pinnedTimelineVersions = writable<Record<string, number>>({});48
-export const historyRepairs = writable<Record<string, HistoryRepairReport>>({});49
-50
-export function publishHistoryRepair(report: HistoryRepairReport, account: string): void {51
- if (account !== get(sessionUser)) return;52
- historyRepairs.update((all) => ({ ...all, [report.room_id]: report }));53
-}5446
export interface EventContextState {5547
room_id: string;5648
request_id: string;@@ -88,7 +80,6 @@ export function resetClientState(): void {
8880
timelines.set({});8981
pinnedTimelines.set({});9082
pinnedTimelineVersions.set({});91
- historyRepairs.set({});9283
eventContexts.set({});9384
connection.set('connecting');9485
selectedRoomId.set(null);@@ -163,14 +154,6 @@ function handlePayload(text: string): void {
163154
const kind = data['type'];164155
if (kind === 'snapshot') {165156
if (typeof data['connection'] === 'string') setConnection(data['connection']);166
- const repairs: Record<string, HistoryRepairReport> = {};167
- if (isRecord(data['repairs'])) {168
- for (const [roomId, value] of Object.entries(data['repairs'])) {169
- const report = parseHistoryRepairReport(value);170
- if (report && report.room_id === roomId) repairs[roomId] = report;171
- }172
- }173
- historyRepairs.set(repairs);174157
const mentions = parseMentionsPage(data['mentions']);175158
if (mentions) publishMentionsPage(mentions);176159
setRooms(data['rooms']);@@ -202,12 +185,6 @@ function handlePayload(text: string): void {
202185
if (update) publishMentionsPage(update.snapshot);203186
return;204187
}205
- if (kind === 'history_repair') {206
- const report = parseHistoryRepairReport(data['report']);207
- const account = get(sessionUser);208
- if (report && account) publishHistoryRepair(report, account);209
- return;210
- }211188
if (kind === 'image_packs') {212189
imagePacks.set(parseImagePacks(data['packs']));213190
return;@@ -2,8 +2,6 @@ import type { EventContext, EventContextPage, MemberSummary, RoomSummary } from
22
import { parseEventContext, parseEventContextPage } from './types';33
import { parseMentionsPage, parseSavedMentionContext } from './mentions-model';44
import type { MentionKey, MentionsBoundary, MentionsPage, SavedMentionContext } from './mentions-model';5
-import { DEFAULT_HISTORY_REPAIR_BUDGET, parseHistoryRepairPolicy, parseHistoryRepairReport } from './repair-model';6
-import type { HistoryRepairBudget, HistoryRepairPolicy, HistoryRepairReport } from './repair-model';75
import { ApiError, type CommandRequest, type SendKind, type SessionInfo, type Transport, type UploadExtras, type VerifyAction } from './transport';86
97
async function parseFailure(res: Response, fallback: string): Promise<never> {@@ -142,35 +140,12 @@ export class HttpTransport implements Transport {
142140
await this.command({ op: 'mark_mentions_read_through', boundary });143141
}144142
async recoverMentions(): Promise<void> { await this.command({ op: 'recover_mentions' }); }145
- async historyRepair(room_id: string, policy: HistoryRepairPolicy | null = null, budget: HistoryRepairBudget = DEFAULT_HISTORY_REPAIR_BUDGET): Promise<HistoryRepairReport> {146
- const parsed = parseHistoryRepairReport(await this.command({ op: 'history_repair', room_id, policy, budget }));147
- if (!parsed) throw new ApiError(502, 'Invalid history repair response');148
- return parsed;149
- }150
- async continueHistoryRepair(room_id: string, budget: HistoryRepairBudget = DEFAULT_HISTORY_REPAIR_BUDGET): Promise<HistoryRepairReport> {151
- const parsed = parseHistoryRepairReport(await this.command({ op: 'continue_history_repair', room_id, budget }));152
- if (!parsed) throw new ApiError(502, 'Invalid history repair response');153
- return parsed;154
- }155
- async cancelHistoryRepair(room_id: string): Promise<void> {156
- await this.command({ op: 'cancel_history_repair', room_id });157
- }158
- async startRecentHistory(room_id: string): Promise<HistoryRepairReport> {159
- const parsed = parseHistoryRepairReport(await this.command({ op: 'start_recent_history', room_id }));160
- if (!parsed) throw new ApiError(502, 'Invalid history repair response');161
- return parsed;143
+ async startRecentHistory(room_id: string): Promise<void> {144
+ await this.command({ op: 'start_recent_history', room_id });162145
}163146
async fillHistoryGap(room_id: string, gap_id: string): Promise<void> {164147
await this.command({ op: 'fill_history_gap', room_id, gap_id });165148
}166
- async historyRepairPolicy(): Promise<HistoryRepairPolicy> {167
- const parsed = parseHistoryRepairPolicy(await this.command({ op: 'history_repair_policy' }));168
- if (!parsed) throw new ApiError(502, 'Invalid history repair policy response');169
- return parsed;170
- }171
- async setHistoryRepairPolicy(policy: HistoryRepairPolicy): Promise<void> {172
- await this.command({ op: 'set_history_repair_policy', policy });173
- }174149
async eventContextBefore(room_id: string, event_id: string, from: string, limit = 20): Promise<EventContextPage> {175150
return this.eventContextPage('before', room_id, event_id, from, limit);176151
}@@ -1,8 +1,6 @@
11
import { parseEventContext, parseEventContextPage } from './types';22
import { parseMentionsPage, parseSavedMentionContext } from './mentions-model';33
import type { MentionKey, MentionsBoundary, MentionsPage, SavedMentionContext } from './mentions-model';4
-import { DEFAULT_HISTORY_REPAIR_BUDGET, parseHistoryRepairPolicy, parseHistoryRepairReport } from './repair-model';5
-import type { HistoryRepairBudget, HistoryRepairPolicy, HistoryRepairReport } from './repair-model';64
import type { EventContext, EventContextPage, MemberSummary, RoomSummary } from './types';75
import { ApiError, type CommandRequest, type SendKind, type SessionInfo, type SoftLogout, type Transport, type UploadExtras, type VerifyAction } from './transport';86
import { statusForKind } from './wire-status';@@ -249,35 +247,12 @@ export class WorkerTransport implements Transport {
249247
async setMentionsRead(keys: MentionKey[], read: boolean): Promise<void> { await this.client.call('set_mentions_read', { keys, read }); }250248
async markMentionsReadThrough(boundary: MentionsBoundary): Promise<void> { await this.client.call('mark_mentions_read_through', { boundary }); }251249
async recoverMentions(): Promise<void> { await this.client.call('recover_mentions'); }252
- async historyRepair(room_id: string, policy: HistoryRepairPolicy | null = null, budget: HistoryRepairBudget = DEFAULT_HISTORY_REPAIR_BUDGET): Promise<HistoryRepairReport> {253
- const parsed = parseHistoryRepairReport(await this.client.call('history_repair', { room_id, policy, budget }));254
- if (!parsed) throw new ApiError(502, 'Invalid history repair response');255
- return parsed;256
- }257
- async continueHistoryRepair(room_id: string, budget: HistoryRepairBudget = DEFAULT_HISTORY_REPAIR_BUDGET): Promise<HistoryRepairReport> {258
- const parsed = parseHistoryRepairReport(await this.client.call('continue_history_repair', { room_id, budget }));259
- if (!parsed) throw new ApiError(502, 'Invalid history repair response');260
- return parsed;261
- }262
- async cancelHistoryRepair(room_id: string): Promise<void> {263
- await this.client.call('cancel_history_repair', { room_id });264
- }265
- async startRecentHistory(room_id: string): Promise<HistoryRepairReport> {266
- const parsed = parseHistoryRepairReport(await this.client.call('start_recent_history', { room_id }));267
- if (!parsed) throw new ApiError(502, 'Invalid history repair response');268
- return parsed;250
+ async startRecentHistory(room_id: string): Promise<void> {251
+ await this.client.call('start_recent_history', { room_id });269252
}270253
async fillHistoryGap(room_id: string, gap_id: string): Promise<void> {271254
await this.client.call('fill_history_gap', { room_id, gap_id });272255
}273
- async historyRepairPolicy(): Promise<HistoryRepairPolicy> {274
- const parsed = parseHistoryRepairPolicy(await this.client.call('history_repair_policy'));275
- if (!parsed) throw new ApiError(502, 'Invalid history repair policy response');276
- return parsed;277
- }278
- async setHistoryRepairPolicy(policy: HistoryRepairPolicy): Promise<void> {279
- await this.client.call('set_history_repair_policy', { policy });280
- }281256
eventContext(room_id: string, event_id: string, limit = 20): Promise<EventContext> {282257
return this.contextCall('event_context', { room_id, event_id, limit });283258
}@@ -1,6 +1,5 @@
11
import type { EventContext, EventContextPage, MemberSummary, PackUsage, RoomSummary } from './types';22
import type { MentionKey, MentionsBoundary, MentionsPage, SavedMentionContext } from './mentions-model';3
-import type { HistoryRepairBudget, HistoryRepairPolicy, HistoryRepairReport } from './repair-model';43
54
export class ApiError extends Error {65
status: number;@@ -65,17 +64,12 @@ export type CommandRequest =
6564
| { op: 'clone_pack_image'; from_pack: string; shortcode: string; to: PackTarget; usage?: PackUsage[] }6665
| { op: 'clone_sticker'; room_id: string; event_id: string; to: PackTarget; shortcode: string; usage?: PackUsage[] }6766
| { op: 'clone_pack'; from_pack: string; to: PackTarget; display_name: string | null; usage?: PackUsage[] }68
- | HistoryRepairRequest67
+ | HistoryRequest6968
| MentionsRequest;7069
71
-export type HistoryRepairRequest =72
- | { op: 'history_repair'; room_id: string; policy: HistoryRepairPolicy | null; budget: HistoryRepairBudget }73
- | { op: 'continue_history_repair'; room_id: string; budget: HistoryRepairBudget }74
- | { op: 'cancel_history_repair'; room_id: string }70
+export type HistoryRequest =7571
| { op: 'start_recent_history'; room_id: string }76
- | { op: 'fill_history_gap'; room_id: string; gap_id: string }77
- | { op: 'history_repair_policy' }78
- | { op: 'set_history_repair_policy'; policy: HistoryRepairPolicy };72
+ | { op: 'fill_history_gap'; room_id: string; gap_id: string };7973
8074
export type MentionsRequest =8175
| { op: 'mentions_page'; cursor: string | null; limit: number }@@ -127,13 +121,8 @@ export interface Transport {
127121
setMentionsRead(keys: MentionKey[], read: boolean): Promise<void>;128122
markMentionsReadThrough(boundary: MentionsBoundary): Promise<void>;129123
recoverMentions(): Promise<void>;130
- historyRepair(room_id: string, policy?: HistoryRepairPolicy | null, budget?: HistoryRepairBudget): Promise<HistoryRepairReport>;131
- continueHistoryRepair(room_id: string, budget?: HistoryRepairBudget): Promise<HistoryRepairReport>;132
- cancelHistoryRepair(room_id: string): Promise<void>;133
- startRecentHistory(room_id: string): Promise<HistoryRepairReport>;124
+ startRecentHistory(room_id: string): Promise<void>;134125
fillHistoryGap(room_id: string, gap_id: string): Promise<void>;135
- historyRepairPolicy(): Promise<HistoryRepairPolicy>;136
- setHistoryRepairPolicy(policy: HistoryRepairPolicy): Promise<void>;137126
setTyping(room_id: string, typing: boolean): Promise<void>;138127
getAccountData(eventType: string): Promise<unknown | null>;139128
getUrlPreview(room_id: string, url: string): Promise<unknown | null>;