@bojackduy/opencode-telescope 0.1.15 → 0.1.16

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@bojackduy/opencode-telescope",
4
- "version": "0.1.15",
4
+ "version": "0.1.16",
5
5
  "description": "Fuzzy search across all OpenCode conversations — grep session and chat history, find code snippets, and jump to any chat instantly",
6
6
  "type": "module",
7
7
  "license": "MIT",
package/search.ts CHANGED
@@ -11,7 +11,6 @@ export type SearchResult = {
11
11
  sessionTitle: string
12
12
  directory: string
13
13
  role: "user" | "assistant"
14
- partType: "text" | "reasoning" | "tool" | "patch"
15
14
  timeCreated: number
16
15
  snippet: string
17
16
  matchStart: number
@@ -68,15 +67,10 @@ type Row = {
68
67
  session_title: string | null
69
68
  directory: string
70
69
  role: SearchRole
71
- part_type?: SearchResult["partType"]
72
70
  time_created: number
73
71
  text: string
74
72
  }
75
73
 
76
- type SourceRow = Omit<Row, "text"> & {
77
- data: string
78
- }
79
-
80
74
  type ConversationRow = {
81
75
  id: string
82
76
  message_id: string
@@ -324,7 +318,6 @@ export function rowToSearchResult(row: Row, query: string): SearchResult | undef
324
318
  sessionTitle: row.session_title || "Untitled session",
325
319
  directory: row.directory,
326
320
  role: row.role,
327
- partType: row.part_type ?? "text",
328
321
  timeCreated: row.time_created,
329
322
  snippet: makeSnippet(text, query),
330
323
  matchStart: match.start,
@@ -344,7 +337,7 @@ export function rowToSearchResult(row: Row, query: string): SearchResult | undef
344
337
 
345
338
  export function extractSearchText(data: string) {
346
339
  try {
347
- return searchablePartText(JSON.parse(data)).replace(/\s+/g, " ").trim()
340
+ return extractFromValue(JSON.parse(data)).replace(/\s+/g, " ").trim()
348
341
  } catch {
349
342
  return data.replace(/\s+/g, " ").trim()
350
343
  }
@@ -576,7 +569,7 @@ function indexedTextRows(db: Database, dbPath: string, limit: number, query: str
576
569
  debug.time("query:fts:exec")
577
570
  const rows = index.query<Row, (string | number)[]>(`
578
571
  SELECT id, message_id, session_id, session_title, directory, role,
579
- part_type, CAST(time_created AS INTEGER) AS time_created, text
572
+ CAST(time_created AS INTEGER) AS time_created, text
580
573
  FROM document_fts
581
574
  WHERE ${conditions.join(" AND ")}
582
575
  ORDER BY bm25(document_fts), CAST(time_created AS INTEGER) DESC
@@ -593,7 +586,7 @@ function indexedTextRows(db: Database, dbPath: string, limit: number, query: str
593
586
  function visibleTextRows(db: Database, limit: number, query?: string, directory?: string, offset?: number, role?: SearchRole) {
594
587
  const offsetClause = offset ? "OFFSET ?" : ""
595
588
  const conditions: string[] = [
596
- "json_extract(p.data, '$.type') IN ('text', 'reasoning', 'tool', 'patch')",
589
+ "json_extract(p.data, '$.type') = 'text'",
597
590
  role ? "json_extract(m.data, '$.role') = ?" : "json_extract(m.data, '$.role') IN ('user', 'assistant')",
598
591
  ]
599
592
  const params: (string | number)[] = []
@@ -607,7 +600,7 @@ function visibleTextRows(db: Database, limit: number, query?: string, directory?
607
600
 
608
601
  const tokens = query ? query.trim().split(/\s+/).filter(Boolean) : []
609
602
  for (const token of tokens) {
610
- conditions.push("p.data LIKE ?")
603
+ conditions.push("json_extract(p.data, '$.text') LIKE ?")
611
604
  params.push(`%${token}%`)
612
605
  }
613
606
 
@@ -617,9 +610,8 @@ function visibleTextRows(db: Database, limit: number, query?: string, directory?
617
610
  const sql = `
618
611
  SELECT p.id, p.message_id, p.session_id, s.title AS session_title, s.directory,
619
612
  json_extract(m.data, '$.role') AS role,
620
- json_extract(p.data, '$.type') AS part_type,
621
613
  p.time_created,
622
- p.data
614
+ json_extract(p.data, '$.text') AS text
623
615
  FROM part p
624
616
  JOIN message m ON m.id = p.message_id
625
617
  JOIN session s ON s.id = p.session_id
@@ -628,9 +620,9 @@ function visibleTextRows(db: Database, limit: number, query?: string, directory?
628
620
  LIMIT ? ${offsetClause}
629
621
  `
630
622
  debug.time("query:sql:exec")
631
- const rows = db.query<SourceRow, (string | number)[]>(sql).all(...params as any[])
623
+ const rows = db.query<Row, (string | number)[]>(sql).all(...params as any[])
632
624
  debug.timeEnd("query:sql:exec")
633
- return rows.flatMap(sourceRowToSearchRow)
625
+ return rows
634
626
  }
635
627
 
636
628
  function ensureSearchIndex(source: Database, sourcePath: string) {
@@ -646,15 +638,12 @@ function ensureSearchIndex(source: Database, sourcePath: string) {
646
638
  const currentDataVersion = getMeta(_indexDb, "source_data_version")
647
639
  const currentMtimeMs = getMeta(_indexDb, "source_mtime_ms")
648
640
  const currentPath = getMeta(_indexDb, "source_path")
649
- const currentIndexVersion = getMeta(_indexDb, "index_version")
650
- if (currentPath !== sourcePath || currentDataVersion !== String(state.dataVersion) || currentMtimeMs !== String(state.mtimeMs) || currentIndexVersion !== SEARCH_INDEX_VERSION) {
641
+ if (currentPath !== sourcePath || currentDataVersion !== String(state.dataVersion) || currentMtimeMs !== String(state.mtimeMs)) {
651
642
  rebuildSearchIndex(source, _indexDb, sourcePath, state)
652
643
  }
653
644
  return _indexDb
654
645
  }
655
646
 
656
- const SEARCH_INDEX_VERSION = "3"
657
-
658
647
  function searchIndexPath(sourcePath: string) {
659
648
  const parsed = path.parse(sourcePath)
660
649
  return path.join(parsed.dir, `${parsed.name}-search.db`)
@@ -673,31 +662,11 @@ function migrateSearchIndex(db: Database) {
673
662
  session_title,
674
663
  directory UNINDEXED,
675
664
  role UNINDEXED,
676
- part_type UNINDEXED,
677
665
  time_created UNINDEXED,
678
666
  text,
679
667
  tokenize='unicode61'
680
668
  );
681
669
  `)
682
-
683
- const hasPartType = db.query<{ name: string }, []>("PRAGMA table_info(document_fts)").all().some((column) => column.name === "part_type")
684
- if (!hasPartType) {
685
- db.exec("DROP TABLE document_fts")
686
- db.exec(`
687
- CREATE VIRTUAL TABLE document_fts USING fts5(
688
- id UNINDEXED,
689
- message_id UNINDEXED,
690
- session_id UNINDEXED,
691
- session_title,
692
- directory UNINDEXED,
693
- role UNINDEXED,
694
- part_type UNINDEXED,
695
- time_created UNINDEXED,
696
- text,
697
- tokenize='unicode61'
698
- );
699
- `)
700
- }
701
670
  }
702
671
 
703
672
  function sourceState(db: Database, sourcePath: string) {
@@ -708,27 +677,27 @@ function sourceState(db: Database, sourcePath: string) {
708
677
 
709
678
  function rebuildSearchIndex(source: Database, index: Database, sourcePath: string, state: { dataVersion: number; mtimeMs: number }) {
710
679
  debug.time("fts:rebuild")
711
- const rows = source.query<SourceRow, []>(`
680
+ const rows = source.query<Row, []>(`
712
681
  SELECT p.id, p.message_id, p.session_id, s.title AS session_title, s.directory,
713
- json_extract(m.data, '$.role') AS role,
714
- json_extract(p.data, '$.type') AS part_type,
715
- p.time_created,
716
- p.data
682
+ json_extract(m.data, '$.role') AS role,
683
+ p.time_created,
684
+ json_extract(p.data, '$.text') AS text
717
685
  FROM part p
718
686
  JOIN message m ON m.id = p.message_id
719
687
  JOIN session s ON s.id = p.session_id
720
- WHERE json_extract(p.data, '$.type') IN ('text', 'reasoning', 'tool', 'patch')
688
+ WHERE json_extract(p.data, '$.type') = 'text'
721
689
  AND json_extract(m.data, '$.role') IN ('user', 'assistant')
690
+ AND json_extract(p.data, '$.text') IS NOT NULL
722
691
  ORDER BY p.time_created DESC
723
692
  `).all()
724
- const insert = index.query<Row, [string, string, string, string, string, string, SearchResult["partType"], number, string]>(`
725
- INSERT INTO document_fts(id, message_id, session_id, session_title, directory, role, part_type, time_created, text)
726
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
693
+ const insert = index.query<Row, [string, string, string, string, string, string, number, string]>(`
694
+ INSERT INTO document_fts(id, message_id, session_id, session_title, directory, role, time_created, text)
695
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
727
696
  `)
728
697
  index.exec("BEGIN IMMEDIATE")
729
698
  try {
730
699
  index.exec("DELETE FROM document_fts")
731
- for (const row of rows.flatMap(sourceRowToSearchRow)) {
700
+ for (const row of rows) {
732
701
  insert.run(
733
702
  row.id,
734
703
  row.message_id,
@@ -736,7 +705,6 @@ function rebuildSearchIndex(source: Database, index: Database, sourcePath: strin
736
705
  row.session_title ?? "Untitled session",
737
706
  row.directory,
738
707
  row.role,
739
- row.part_type ?? "text",
740
708
  row.time_created,
741
709
  row.text,
742
710
  )
@@ -744,7 +712,6 @@ function rebuildSearchIndex(source: Database, index: Database, sourcePath: strin
744
712
  setMeta(index, "source_path", sourcePath)
745
713
  setMeta(index, "source_data_version", String(state.dataVersion))
746
714
  setMeta(index, "source_mtime_ms", String(state.mtimeMs))
747
- setMeta(index, "index_version", SEARCH_INDEX_VERSION)
748
715
  index.exec("COMMIT")
749
716
  } catch (err) {
750
717
  index.exec("ROLLBACK")
@@ -816,29 +783,6 @@ function extractFromValue(value: unknown): string {
816
783
  .join("\n")
817
784
  }
818
785
 
819
- function sourceRowToSearchRow(row: SourceRow): Row[] {
820
- const text = extractSearchText(row.data)
821
- if (!text) return []
822
- return [{ ...row, text }]
823
- }
824
-
825
- function searchablePartText(value: unknown): string {
826
- if (!value || typeof value !== "object" || Array.isArray(value)) return extractFromValue(value)
827
- const record = value as Record<string, unknown>
828
- const type = record.type
829
-
830
- if (type === "text" || type === "reasoning") return typeof record.text === "string" ? record.text : extractFromValue(record)
831
- if (type === "patch") return extractFromValue(record.files)
832
-
833
- if (type === "tool") {
834
- const state = record.state && typeof record.state === "object" && !Array.isArray(record.state) ? record.state as Record<string, unknown> : undefined
835
- if (record.tool !== "apply_patch") return ""
836
- return extractFromValue(state?.input)
837
- }
838
-
839
- return extractFromValue(record)
840
- }
841
-
842
786
  function truncate(value: string, length: number) {
843
787
  if (value.length <= length) return value
844
788
  return `${value.slice(0, length - 3)}...`
package/telescope.tsx CHANGED
@@ -46,14 +46,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
46
46
  const [loadingPreviewMore, setLoadingPreviewMore] = createSignal(false)
47
47
  const [prefetchingPreviewBefore, setPrefetchingPreviewBefore] = createSignal(false)
48
48
  const [prefetchingPreviewAfter, setPrefetchingPreviewAfter] = createSignal(false)
49
- const [previewEdgeLoadingReady, setPreviewEdgeLoadingReady] = createSignal(false)
50
- const RESULT_BATCH_SIZE = 50
51
- const RESULT_PREFETCH_AHEAD_ROWS = 25
49
+ const MIN_SEARCH_BATCH_SIZE = 25
50
+ const MIN_RECENT_BATCH_SIZE = 15
52
51
  const RESULT_OVERSCAN_MULTIPLIER = 2
52
+ const RESULT_BATCH_VIEWPORTS = 4
53
+ const RESULT_PREFETCH_VIEWPORTS = 10
53
54
  const RESULT_CACHE_BEHIND_VIEWPORTS = 6
54
- const INITIAL_PREVIEW_BEFORE = 6
55
- const INITIAL_PREVIEW_AFTER = 12
56
- const INITIAL_PREVIEW_DELAY_MS = 75
55
+ const INITIAL_PREVIEW_BEFORE = 20
56
+ const INITIAL_PREVIEW_AFTER = 30
57
57
  const PREVIEW_PAGE_SIZE = 20
58
58
  const PREVIEW_PREFETCH_VIEWPORTS = 0.5
59
59
  let input: InputRenderable | undefined
@@ -91,19 +91,18 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
91
91
  let resultPreviousTimer: ReturnType<typeof setTimeout> | undefined
92
92
  let previewBeforeTimer: ReturnType<typeof setTimeout> | undefined
93
93
  let previewAfterTimer: ReturnType<typeof setTimeout> | undefined
94
- let pendingPreviewBefore: { itemId: string; previousContentHeight: number; preserveScroll: boolean; visibleLoad: boolean } | undefined
95
- let pendingPreviewAfter: { itemId: string; visibleLoad: boolean } | undefined
94
+ let pendingPreviewBefore: { previousContentHeight: number; preserveScroll: boolean; visibleLoad: boolean } | undefined
95
+ let pendingPreviewAfterVisible = false
96
96
  const cancelPreviewPrefetch = () => {
97
97
  if (previewBeforeTimer) clearTimeout(previewBeforeTimer)
98
98
  if (previewAfterTimer) clearTimeout(previewAfterTimer)
99
99
  previewBeforeTimer = undefined
100
100
  previewAfterTimer = undefined
101
101
  pendingPreviewBefore = undefined
102
- pendingPreviewAfter = undefined
102
+ pendingPreviewAfterVisible = false
103
103
  setPrefetchingPreviewBefore(false)
104
104
  setPrefetchingPreviewAfter(false)
105
105
  setLoadingPreviewMore(false)
106
- setPreviewEdgeLoadingReady(false)
107
106
  }
108
107
  onCleanup(() => {
109
108
  if (resultPrefetchTimer) clearTimeout(resultPrefetchTimer)
@@ -116,9 +115,9 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
116
115
  const viewportHeight = resultScroll?.height || Math.max(8, height() - 7)
117
116
  return Math.max(5, Math.floor(viewportHeight / resultRowHeight()))
118
117
  }
119
- const searchBatchSize = () => RESULT_BATCH_SIZE
120
- const recentBatchSize = () => RESULT_BATCH_SIZE
121
- const resultPrefetchThreshold = () => RESULT_PREFETCH_AHEAD_ROWS
118
+ const searchBatchSize = () => Math.max(MIN_SEARCH_BATCH_SIZE, visibleResultRows() * RESULT_BATCH_VIEWPORTS)
119
+ const recentBatchSize = () => Math.max(MIN_RECENT_BATCH_SIZE, visibleResultRows() * RESULT_BATCH_VIEWPORTS)
120
+ const resultPrefetchThreshold = () => visibleResultRows() * RESULT_PREFETCH_VIEWPORTS
122
121
  const resultPrefetchState = () => {
123
122
  const cachedEnd = resultBaseOffset() + results().length
124
123
  const rowsAhead = Math.max(0, cachedEnd - selected() - 1)
@@ -438,20 +437,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
438
437
  return lastChild ? lastChild.y + lastChild.height : 0
439
438
  }
440
439
 
441
- const resetPreviewScroll = () => {
442
- if (!previewScroll || previewScroll.y === 0) return
443
- previewScroll.scrollBy(-previewScroll.y)
444
- }
445
-
446
- const loadPreviewBefore = (itemId: string, previousContentHeight = previewContentHeight(), preserveScroll = true, visibleLoad = false) => {
440
+ const loadPreviewBefore = (previousContentHeight = previewContentHeight(), preserveScroll = true, visibleLoad = false) => {
447
441
  const item = selectedResult()
448
442
  const first = previewParts()[0]
449
- if (!item || item.id !== itemId || !first || loadingPreviewMore() || prefetchingPreviewBefore()) return
443
+ if (!item || !first || loadingPreviewMore() || prefetchingPreviewBefore()) return
450
444
  visibleLoad ? setLoadingPreviewMore(true) : setPrefetchingPreviewBefore(true)
451
445
  debug.time("preview:load-before")
452
446
  try {
453
447
  const page = loadConversationBefore(item, { id: first.id, timeCreated: first.timeCreated }, { limit: PREVIEW_PAGE_SIZE, dbPath: dbPath() })
454
- if (selectedResult()?.id !== itemId) return
455
448
  debug.log("preview:load-before", {
456
449
  item: item.id,
457
450
  added: page.parts.length,
@@ -465,7 +458,6 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
465
458
  setPreviewParts((prev) => [...page.parts, ...prev])
466
459
  if (preserveScroll) {
467
460
  setTimeout(() => {
468
- if (selectedResult()?.id !== itemId) return
469
461
  const delta = previewContentHeight() - previousContentHeight
470
462
  if (delta > 0) previewScroll?.scrollBy(delta)
471
463
  }, 1)
@@ -480,15 +472,14 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
480
472
  }
481
473
  }
482
474
 
483
- const loadPreviewAfter = (itemId: string, visibleLoad = false) => {
475
+ const loadPreviewAfter = (visibleLoad = false) => {
484
476
  const item = selectedResult()
485
477
  const last = previewParts().at(-1)
486
- if (!item || item.id !== itemId || !last || loadingPreviewMore() || prefetchingPreviewAfter()) return
478
+ if (!item || !last || loadingPreviewMore() || prefetchingPreviewAfter()) return
487
479
  visibleLoad ? setLoadingPreviewMore(true) : setPrefetchingPreviewAfter(true)
488
480
  debug.time("preview:load-after")
489
481
  try {
490
482
  const page = loadConversationAfter(item, { id: last.id, timeCreated: last.timeCreated }, { limit: PREVIEW_PAGE_SIZE, dbPath: dbPath() })
491
- if (selectedResult()?.id !== itemId) return
492
483
  debug.log("preview:load-after", {
493
484
  item: item.id,
494
485
  added: page.parts.length,
@@ -508,8 +499,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
508
499
  }
509
500
 
510
501
  const schedulePreviewBefore = (previousContentHeight = previewContentHeight(), preserveScroll = true, visibleLoad = false) => {
511
- const item = selectedResult()
512
- if (!item || !hasMorePreviewBefore() || loadingPreviewMore() || prefetchingPreviewBefore()) return
502
+ if (!hasMorePreviewBefore() || loadingPreviewMore() || prefetchingPreviewBefore()) return
513
503
  if (previewBeforeTimer) {
514
504
  if (pendingPreviewBefore) {
515
505
  pendingPreviewBefore.preserveScroll = pendingPreviewBefore.preserveScroll && preserveScroll
@@ -517,27 +507,26 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
517
507
  }
518
508
  return
519
509
  }
520
- pendingPreviewBefore = { itemId: item.id, previousContentHeight, preserveScroll, visibleLoad }
521
- debug.log("preview:prefetch-before-scheduled", { item: item.id, preserveScroll, visibleLoad })
510
+ pendingPreviewBefore = { previousContentHeight, preserveScroll, visibleLoad }
511
+ debug.log("preview:prefetch-before-scheduled", { preserveScroll, visibleLoad })
522
512
  previewBeforeTimer = setTimeout(() => {
523
513
  const pending = pendingPreviewBefore
524
514
  previewBeforeTimer = undefined
525
515
  pendingPreviewBefore = undefined
526
- if (pending && selectedResult()?.id === pending.itemId) loadPreviewBefore(pending.itemId, pending.previousContentHeight, pending.preserveScroll, pending.visibleLoad)
516
+ if (pending) loadPreviewBefore(pending.previousContentHeight, pending.preserveScroll, pending.visibleLoad)
527
517
  }, 1)
528
518
  }
529
519
 
530
520
  const schedulePreviewAfter = (visibleLoad = false) => {
531
- const item = selectedResult()
532
- if (!item || !hasMorePreviewAfter() || loadingPreviewMore() || prefetchingPreviewAfter()) return
533
- pendingPreviewAfter = { itemId: item.id, visibleLoad: (pendingPreviewAfter?.visibleLoad || visibleLoad) }
521
+ if (!hasMorePreviewAfter() || loadingPreviewMore() || prefetchingPreviewAfter()) return
522
+ pendingPreviewAfterVisible = pendingPreviewAfterVisible || visibleLoad
534
523
  if (previewAfterTimer) return
535
- debug.log("preview:prefetch-after-scheduled", { item: item.id, visibleLoad })
524
+ debug.log("preview:prefetch-after-scheduled", { visibleLoad })
536
525
  previewAfterTimer = setTimeout(() => {
537
- const pending = pendingPreviewAfter
526
+ const pendingVisibleLoad = pendingPreviewAfterVisible
538
527
  previewAfterTimer = undefined
539
- pendingPreviewAfter = undefined
540
- if (pending && selectedResult()?.id === pending.itemId) loadPreviewAfter(pending.itemId, pending.visibleLoad)
528
+ pendingPreviewAfterVisible = false
529
+ loadPreviewAfter(pendingVisibleLoad)
541
530
  }, 1)
542
531
  }
543
532
 
@@ -554,62 +543,33 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
554
543
  if (item.id === lastPreviewItemId) return
555
544
  lastPreviewItemId = item.id
556
545
  cancelPreviewPrefetch()
557
- resetPreviewScroll()
558
- solidBatch(() => {
559
- setPreviewParts([])
560
- setHasMorePreviewBefore(false)
561
- setHasMorePreviewAfter(false)
562
- setPreviewEdgeLoadingReady(false)
563
- })
564
546
  debug.log("preview:new-item", item.sessionTitle?.slice(0, 40) ?? item.id.slice(-8))
565
547
  const db = dbPath()
566
- const itemId = item.id
567
- debug.log("preview:schedule", { item: item.id, delayMs: INITIAL_PREVIEW_DELAY_MS, before: INITIAL_PREVIEW_BEFORE, after: INITIAL_PREVIEW_AFTER })
548
+ debug.time("preview:load")
549
+ try {
550
+ const page = loadConversationAround(item, { before: INITIAL_PREVIEW_BEFORE, after: INITIAL_PREVIEW_AFTER, dbPath: db })
551
+ debug.log("preview:init", {
552
+ item: item.id,
553
+ session: item.sessionID,
554
+ parts: page.parts.length,
555
+ hasMoreBefore: page.hasMoreBefore,
556
+ hasMoreAfter: page.hasMoreAfter,
557
+ first: page.parts[0]?.id,
558
+ last: page.parts.at(-1)?.id,
559
+ })
560
+ setPreviewParts(page.parts)
561
+ setHasMorePreviewBefore(page.hasMoreBefore)
562
+ setHasMorePreviewAfter(page.hasMoreAfter)
563
+ } catch {}
568
564
  debug.timeEnd("nav:total")
569
- const timer = setTimeout(() => {
570
- if (selectedResult()?.id !== itemId) {
571
- debug.log("preview:load:cancelled", { item: itemId })
572
- return
573
- }
574
- debug.time("preview:load")
575
- try {
576
- const page = loadConversationAround(item, { before: INITIAL_PREVIEW_BEFORE, after: INITIAL_PREVIEW_AFTER, dbPath: db })
577
- if (selectedResult()?.id !== itemId) {
578
- debug.log("preview:load:cancelled", { item: itemId, afterLoad: true })
579
- return
580
- }
581
- debug.log("preview:init", {
582
- item: item.id,
583
- session: item.sessionID,
584
- parts: page.parts.length,
585
- hasMoreBefore: page.hasMoreBefore,
586
- hasMoreAfter: page.hasMoreAfter,
587
- first: page.parts[0]?.id,
588
- last: page.parts.at(-1)?.id,
589
- })
590
- solidBatch(() => {
591
- setPreviewParts(page.parts)
592
- setHasMorePreviewBefore(page.hasMoreBefore)
593
- setHasMorePreviewAfter(page.hasMoreAfter)
594
- })
595
- } catch {
596
- } finally {
597
- debug.timeEnd("preview:load")
598
- }
599
- }, INITIAL_PREVIEW_DELAY_MS)
600
- onCleanup(() => {
601
- clearTimeout(timer)
602
- if (selectedResult()?.id !== itemId) {
603
- debug.log("preview:load:cancelled", { item: itemId, cleanup: true })
604
- }
605
- })
565
+ debug.timeEnd("preview:load")
606
566
  })
607
567
 
608
568
  createEffect(() => {
609
569
  const item = selectedResult()
610
570
  if (!item) return
611
571
  const interval = setInterval(() => {
612
- if (!previewEdgeLoadingReady() || loadingPreviewMore() || prefetchingPreviewBefore() || prefetchingPreviewAfter()) return
572
+ if (loadingPreviewMore() || prefetchingPreviewBefore() || prefetchingPreviewAfter()) return
613
573
  const scroll = previewScroll
614
574
  const children = scroll?.getChildren()
615
575
  if (!scroll || !children || children.length === 0) return
@@ -650,19 +610,8 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
650
610
  if (!item) return
651
611
  if (item.id === scrolledItem) return
652
612
  scrolledItem = item.id
653
- const itemId = item.id
654
- let readyTimer: ReturnType<typeof setTimeout> | undefined
655
- const timer = setTimeout(() => {
656
- if (selectedResult()?.id !== itemId) return
657
- scrollPreviewToTarget(previewScroll, messageTargetID(item))
658
- readyTimer = setTimeout(() => {
659
- if (selectedResult()?.id === itemId) setPreviewEdgeLoadingReady(true)
660
- }, 100)
661
- }, 1)
662
- onCleanup(() => {
663
- clearTimeout(timer)
664
- if (readyTimer) clearTimeout(readyTimer)
665
- })
613
+ const timer = setTimeout(() => scrollPreviewToTarget(previewScroll, messageTargetID(item)), 1)
614
+ onCleanup(() => clearTimeout(timer))
666
615
  })
667
616
 
668
617
  const open = () => {
@@ -6,8 +6,6 @@ export function previewScrollAmount(scroll: ScrollBoxRenderable | undefined) {
6
6
  }
7
7
 
8
8
  export function messageTargetID(item: SearchResult) {
9
- if (item.partType === "tool") return `tool-inline-${item.messageID}-${item.id}`
10
- if (item.partType === "reasoning") return `text-${item.messageID}-${item.id}`
11
9
  if (item.role === "assistant") return `text-${item.messageID}-${item.id}`
12
10
  return item.messageID
13
11
  }