sqlite-hub 0.12.0 → 0.17.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 (64) hide show
  1. package/README.md +118 -23
  2. package/bin/sqlite-hub.js +1041 -224
  3. package/frontend/index.html +1 -0
  4. package/frontend/js/api.js +32 -2
  5. package/frontend/js/app.js +989 -13
  6. package/frontend/js/components/modal.js +644 -15
  7. package/frontend/js/components/pageHeader.js +14 -16
  8. package/frontend/js/components/queryEditor.js +9 -1
  9. package/frontend/js/components/queryHistoryPanel.js +126 -131
  10. package/frontend/js/components/queryResults.js +79 -17
  11. package/frontend/js/components/rowEditorPanel.js +204 -10
  12. package/frontend/js/components/sidebar.js +102 -18
  13. package/frontend/js/components/tableDesignerEditor.js +69 -11
  14. package/frontend/js/router.js +8 -0
  15. package/frontend/js/store.js +868 -9
  16. package/frontend/js/utils/copyColumnExport.js +117 -0
  17. package/frontend/js/utils/exportFilenames.js +32 -0
  18. package/frontend/js/utils/filePathPreview.js +315 -0
  19. package/frontend/js/utils/format.js +1 -1
  20. package/frontend/js/utils/markdownDocuments.js +248 -0
  21. package/frontend/js/utils/rowEditorJson.js +65 -0
  22. package/frontend/js/utils/sqlFormatter.js +691 -0
  23. package/frontend/js/utils/tableDesigner.js +178 -6
  24. package/frontend/js/utils/textCellStats.js +20 -0
  25. package/frontend/js/utils/timestampPreview.js +264 -0
  26. package/frontend/js/views/charts.js +3 -1
  27. package/frontend/js/views/data.js +34 -2
  28. package/frontend/js/views/documents.js +300 -0
  29. package/frontend/js/views/editor.js +48 -1
  30. package/frontend/js/views/settings.js +39 -6
  31. package/frontend/js/views/structure.js +154 -212
  32. package/frontend/styles/base.css +6 -0
  33. package/frontend/styles/components.css +476 -2
  34. package/frontend/styles/structure-graph.css +0 -3
  35. package/frontend/styles/tailwind.generated.css +1 -1
  36. package/frontend/styles/tokens.css +1 -1
  37. package/frontend/styles/views.css +422 -0
  38. package/package.json +3 -3
  39. package/server/routes/documents.js +163 -0
  40. package/server/routes/settings.js +22 -6
  41. package/server/server.js +6 -0
  42. package/server/services/sqlite/introspection.js +10 -0
  43. package/server/services/sqlite/sqlExecutor.js +29 -0
  44. package/server/services/sqlite/tableDesigner/changeAnalysis.js +25 -1
  45. package/server/services/sqlite/tableDesigner/schemaMapping.js +105 -10
  46. package/server/services/sqlite/tableDesigner/validation.js +60 -2
  47. package/server/services/sqlite/tableDesignerService.js +1 -1
  48. package/server/services/storage/appStateStore.js +313 -0
  49. package/tests/check-constraint-options.test.js +14 -0
  50. package/tests/cli-args.test.js +100 -0
  51. package/tests/copy-column-modal.test.js +83 -0
  52. package/tests/database-documents.test.js +85 -0
  53. package/tests/export-filenames.test.js +34 -0
  54. package/tests/file-path-preview.test.js +165 -0
  55. package/tests/markdown-documents.test.js +79 -0
  56. package/tests/row-editor-json.test.js +82 -0
  57. package/tests/row-editor-timestamp-preview.test.js +192 -0
  58. package/tests/settings-metadata.test.js +16 -0
  59. package/tests/settings-view.test.js +32 -0
  60. package/tests/sql-formatter.test.js +173 -0
  61. package/tests/sql-highlight.test.js +38 -0
  62. package/tests/table-designer-v2-unique-constraints.test.js +78 -0
  63. package/tests/text-cell-stats.test.js +38 -0
  64. package/fill.js +0 -526
@@ -1,33 +1,31 @@
1
- import { escapeHtml } from "../utils/format.js";
1
+ import { escapeHtml } from '../utils/format.js';
2
2
 
3
- export function renderPageHeader({ eyebrow = "", title, subtitle = "", actions = "" }) {
4
- return `
3
+ export function renderPageHeader({ eyebrow = '', title, subtitle = '', actions = '' }) {
4
+ return `
5
5
  <div class="mb-10 flex flex-wrap items-end justify-between gap-6">
6
6
  <div>
7
7
  ${
8
- eyebrow
9
- ? `
8
+ eyebrow
9
+ ? `
10
10
  <div class="page-eyebrow">
11
11
  <span class="text-[#FCE300] text-xs font-mono font-bold tracking-widest uppercase">${escapeHtml(
12
- eyebrow
12
+ eyebrow,
13
13
  )}</span>
14
14
  <div class="page-eyebrow-line"></div>
15
15
  </div>
16
16
  `
17
- : ""
17
+ : ''
18
18
  }
19
- <h1 class="text-5xl font-headline font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(
20
- title
21
- )}</h1>
19
+ <h1 class="text-5xl font-headline font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(title)}</h1>
22
20
  ${
23
- subtitle
24
- ? `<p class="text-xs font-mono text-on-surface/40 mt-1 uppercase tracking-widest">${escapeHtml(
25
- subtitle
26
- )}</p>`
27
- : ""
21
+ subtitle
22
+ ? `<p class="text-xs font-mono text-on-surface/40 mt-1 uppercase tracking-widest">${escapeHtml(
23
+ subtitle,
24
+ )}</p>`
25
+ : ''
28
26
  }
29
27
  </div>
30
- ${actions ? `<div class="flex gap-3">${actions}</div>` : ""}
28
+ ${actions ? `<div class="flex gap-3">${actions}</div>` : ''}
31
29
  </div>
32
30
  `;
33
31
  }
@@ -69,9 +69,17 @@ export function renderQueryEditor({
69
69
  data-next-value="${historyVisible ? "false" : "true"}"
70
70
  type="button"
71
71
  >
72
- <span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "history"}</span>
72
+ <span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "visibility"}</span>
73
73
  ${historyVisible ? "Hide History" : "Show History"}
74
74
  </button>
75
+ <button
76
+ class="${secondaryButtonClass}"
77
+ data-action="format-current-query"
78
+ type="button"
79
+ >
80
+ <span class="material-symbols-outlined text-sm">format_align_left</span>
81
+ Format
82
+ </button>
75
83
  <button
76
84
  class="${secondaryButtonClass}"
77
85
  data-action="clear-query"
@@ -1,133 +1,130 @@
1
- import {
2
- escapeHtml,
3
- formatNumber,
4
- } from "../utils/format.js";
5
- import { renderStatusBadge } from "./badges.js";
1
+ import { escapeHtml, formatNumber } from '../utils/format.js';
2
+ import { renderStatusBadge } from './badges.js';
6
3
 
7
4
  function getQueryTypeTone(queryType) {
8
- if (queryType === "select" || queryType === "update") {
9
- return "success";
10
- }
5
+ if (queryType === 'select' || queryType === 'update') {
6
+ return 'success';
7
+ }
11
8
 
12
- if (queryType === "pragma") {
13
- return "primary";
14
- }
9
+ if (queryType === 'pragma') {
10
+ return 'primary';
11
+ }
15
12
 
16
- return "muted";
13
+ return 'muted';
17
14
  }
18
15
 
19
16
  export function renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId) {
20
- const isActive = Number(activeHistoryId) === Number(item.id);
21
- const isSelected = Number(selectedHistoryId) === Number(item.id);
22
- const visibleTables = (item.tablesDetected ?? []).slice(0, 3);
23
- const itemClasses = [
24
- "query-history-item",
25
- isActive ? "is-active" : "",
26
- item.lastRun?.status === "error" ? "is-error" : "",
27
- ]
28
- .filter(Boolean)
29
- .join(" ");
30
- const tableMarkup = visibleTables
31
- .map((tableName) =>
32
- [
33
- '<span class="border border-outline-variant/20 bg-surface-highest px-2 py-1">',
34
- escapeHtml(tableName),
35
- "</span>",
36
- ].join("")
37
- )
38
- .join("");
17
+ const isActive = Number(activeHistoryId) === Number(item.id);
18
+ const isSelected = Number(selectedHistoryId) === Number(item.id);
19
+ const visibleTables = (item.tablesDetected ?? []).slice(0, 3);
20
+ const itemClasses = [
21
+ 'query-history-item',
22
+ isActive ? 'is-active' : '',
23
+ item.lastRun?.status === 'error' ? 'is-error' : '',
24
+ ]
25
+ .filter(Boolean)
26
+ .join(' ');
27
+ const tableMarkup = visibleTables
28
+ .map(tableName =>
29
+ [
30
+ '<span class="border border-outline-variant/20 bg-surface-highest px-2 py-1">',
31
+ escapeHtml(tableName),
32
+ '</span>',
33
+ ].join(''),
34
+ )
35
+ .join('');
39
36
 
40
- return [
41
- '<article class="',
42
- itemClasses,
43
- '"><button class="query-history-item-hit ',
44
- isActive ? "is-active" : "",
45
- '" data-action="select-query-history-item" data-history-id="',
46
- escapeHtml(item.id),
47
- '" type="button">',
48
- '<div class="flex flex-wrap items-center gap-2"><span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface">',
49
- escapeHtml(item.displayTitle),
50
- "</span>",
51
- renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
52
- item.isSaved ? renderStatusBadge("saved", "primary") : "",
53
- item.isDestructive ? renderStatusBadge("destructive", "warning") : "",
54
- "</div>",
55
- '<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">',
56
- escapeHtml(item.previewSql),
57
- "</p></button>",
58
- '<div class="flex items-center justify-between gap-3 border-t border-outline-variant/10 px-3 pb-3 pt-2">',
59
- '<div class="min-w-0 flex flex-wrap gap-2 text-[10px] font-mono uppercase tracking-[0.14em] text-on-surface-variant/55">',
60
- tableMarkup,
61
- '</div><div class="flex items-center gap-1">',
62
- '<button class="query-history-icon-button" data-action="open-query-history" data-history-id="',
63
- escapeHtml(item.id),
64
- '" title="Open in editor" type="button"><span class="material-symbols-outlined text-[18px]">edit_note</span></button>',
65
- '<button class="query-history-icon-button" data-action="run-query-history" data-history-id="',
66
- escapeHtml(item.id),
67
- '" title="Run query" type="button"><span class="material-symbols-outlined text-[18px]">play_arrow</span></button>',
68
- '<button class="query-history-icon-button ',
69
- item.isSaved ? "is-active" : "",
70
- '" data-action="toggle-query-history-saved" data-history-id="',
71
- escapeHtml(item.id),
72
- '" data-next-value="',
73
- item.isSaved ? "false" : "true",
74
- '" title="',
75
- item.isSaved ? "Remove from saved" : "Save query",
76
- '" type="button"><span class="material-symbols-outlined text-[18px]">',
77
- item.isSaved ? "bookmark" : "bookmark_add",
78
- "</span></button>",
79
- '<button class="query-history-icon-button ',
80
- isSelected ? "is-active" : "",
81
- '" data-action="select-query-history-item" data-history-id="',
82
- escapeHtml(item.id),
83
- '" title="Open query detail" type="button"><span class="material-symbols-outlined text-[18px]">info</span></button>',
84
- "</div></div></article>",
85
- ].join("");
37
+ return [
38
+ '<article class="',
39
+ itemClasses,
40
+ '"><button class="query-history-item-hit ',
41
+ isActive ? 'is-active' : '',
42
+ '" data-action="select-query-history-item" data-history-id="',
43
+ escapeHtml(item.id),
44
+ '" type="button">',
45
+ '<div class="flex flex-wrap items-center gap-2"><span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface w-full">',
46
+ escapeHtml(item.displayTitle),
47
+ '</span>',
48
+ renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
49
+ item.isSaved ? renderStatusBadge('saved', 'primary') : '',
50
+ item.isDestructive ? renderStatusBadge('destructive', 'warning') : '',
51
+ '</div>',
52
+ '<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">',
53
+ escapeHtml(item.previewSql),
54
+ '</p></button>',
55
+ '<div class="flex items-center justify-between gap-3 border-t border-outline-variant/10 px-3 pb-3 pt-2">',
56
+ '<div class="min-w-0 flex flex-wrap gap-2 text-[10px] font-mono uppercase tracking-[0.14em] text-on-surface-variant/55">',
57
+ tableMarkup,
58
+ '</div><div class="flex items-center gap-1">',
59
+ '<button class="query-history-icon-button" data-action="open-query-history" data-history-id="',
60
+ escapeHtml(item.id),
61
+ '" title="Open in editor" type="button"><span class="material-symbols-outlined text-[18px]">edit_note</span></button>',
62
+ '<button class="query-history-icon-button" data-action="run-query-history" data-history-id="',
63
+ escapeHtml(item.id),
64
+ '" title="Run query" type="button"><span class="material-symbols-outlined text-[18px]">play_arrow</span></button>',
65
+ '<button class="query-history-icon-button ',
66
+ item.isSaved ? 'is-active' : '',
67
+ '" data-action="toggle-query-history-saved" data-history-id="',
68
+ escapeHtml(item.id),
69
+ '" data-next-value="',
70
+ item.isSaved ? 'false' : 'true',
71
+ '" title="',
72
+ item.isSaved ? 'Remove from saved' : 'Save query',
73
+ '" type="button"><span class="material-symbols-outlined text-[18px]">',
74
+ item.isSaved ? 'bookmark' : 'bookmark_add',
75
+ '</span></button>',
76
+ '<button class="query-history-icon-button ',
77
+ isSelected ? 'is-active' : '',
78
+ '" data-action="select-query-history-item" data-history-id="',
79
+ escapeHtml(item.id),
80
+ '" title="Open query detail" type="button"><span class="material-symbols-outlined text-[18px]">info</span></button>',
81
+ '</div></div></article>',
82
+ ].join('');
86
83
  }
87
84
 
88
85
  function renderQueryHistoryTabs(activeTab, historyTotal) {
89
- const tabs = [
90
- { id: "recent", label: "Recent" },
91
- { id: "saved", label: "Saved" },
92
- { id: "unsaved", label: "Unsaved" },
93
- { id: "failed", label: "Failed" },
94
- ];
86
+ const tabs = [
87
+ { id: 'recent', label: 'Recent' },
88
+ { id: 'saved', label: 'Saved' },
89
+ { id: 'unsaved', label: 'Unsaved' },
90
+ { id: 'failed', label: 'Failed' },
91
+ ];
95
92
 
96
- return [
97
- '<div class="flex items-center gap-2">',
98
- tabs
99
- .map((tab) =>
100
- [
101
- '<button class="query-history-tab ',
102
- activeTab === tab.id ? "is-active" : "",
103
- '" data-action="set-query-history-tab" data-tab="',
104
- tab.id,
105
- '" type="button">',
106
- escapeHtml(tab.label),
107
- "</button>",
108
- ].join("")
109
- )
110
- .join(""),
111
- '<span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">',
112
- escapeHtml(formatNumber(historyTotal)),
113
- "</span></div>",
114
- ].join("");
93
+ return [
94
+ '<div class="flex items-center gap-2">',
95
+ tabs
96
+ .map(tab =>
97
+ [
98
+ '<button class="query-history-tab ',
99
+ activeTab === tab.id ? 'is-active' : '',
100
+ '" data-action="set-query-history-tab" data-tab="',
101
+ tab.id,
102
+ '" type="button">',
103
+ escapeHtml(tab.label),
104
+ '</button>',
105
+ ].join(''),
106
+ )
107
+ .join(''),
108
+ '<span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">',
109
+ escapeHtml(formatNumber(historyTotal)),
110
+ '</span></div>',
111
+ ].join('');
115
112
  }
116
113
 
117
114
  export function renderQueryHistoryPanel({
118
- items = [],
119
- loading = false,
120
- loadingMore = false,
121
- error = null,
122
- activeTab = "recent",
123
- search = "",
124
- committedSearch = "",
125
- total = 0,
126
- hasMore = false,
127
- activeHistoryId = null,
128
- selectedHistoryId = null,
115
+ items = [],
116
+ loading = false,
117
+ loadingMore = false,
118
+ error = null,
119
+ activeTab = 'recent',
120
+ search = '',
121
+ committedSearch = '',
122
+ total = 0,
123
+ hasMore = false,
124
+ activeHistoryId = null,
125
+ selectedHistoryId = null,
129
126
  }) {
130
- return `
127
+ return `
131
128
  <aside class="query-history-panel border-l border-outline-variant/10 bg-surface-container-lowest">
132
129
  <div class="border-b border-outline-variant/10 px-4 py-4">
133
130
  <div class="flex items-center justify-between gap-3">
@@ -162,23 +159,23 @@ export function renderQueryHistoryPanel({
162
159
  <div
163
160
  class="custom-scrollbar min-h-0 flex-1 overflow-auto px-3 py-3"
164
161
  data-query-history-committed-search="${escapeHtml(committedSearch)}"
165
- data-query-history-loading-more="${loadingMore ? "true" : "false"}"
162
+ data-query-history-loading-more="${loadingMore ? 'true' : 'false'}"
166
163
  data-query-history-search="${escapeHtml(search)}"
167
164
  data-query-history-scroll
168
165
  data-query-history-tab="${escapeHtml(activeTab)}"
169
166
  >
170
167
  ${
171
- error
172
- ? `
168
+ error
169
+ ? `
173
170
  <div class="border border-error/30 bg-error-container/20 px-4 py-3 text-sm text-error">
174
171
  ${escapeHtml(error.message)}
175
172
  </div>
176
173
  `
177
- : ""
174
+ : ''
178
175
  }
179
176
  ${
180
- !loading && !items.length
181
- ? `
177
+ !loading && !items.length
178
+ ? `
182
179
  <div class="flex h-full min-h-[240px] flex-col items-center justify-center px-6 text-center">
183
180
  <span class="material-symbols-outlined text-4xl text-on-surface-variant/25">manage_search</span>
184
181
  <p class="mt-4 font-headline text-lg font-bold uppercase tracking-tight text-on-surface">
@@ -189,36 +186,34 @@ export function renderQueryHistoryPanel({
189
186
  </p>
190
187
  </div>
191
188
  `
192
- : ""
189
+ : ''
193
190
  }
194
191
  <div class="space-y-3">
195
- ${items
196
- .map((item) => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId))
197
- .join("")}
192
+ ${items.map(item => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId)).join('')}
198
193
  </div>
199
194
  ${
200
- loading
201
- ? `
195
+ loading
196
+ ? `
202
197
  <div class="mt-4 text-center text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/50">
203
198
  Loading query history...
204
199
  </div>
205
200
  `
206
- : ""
201
+ : ''
207
202
  }
208
203
  ${
209
- hasMore
210
- ? `
204
+ hasMore
205
+ ? `
211
206
  <div class="mt-4 flex justify-center">
212
207
  <button
213
208
  class="standard-button"
214
209
  data-action="load-more-query-history"
215
210
  type="button"
216
211
  >
217
- ${loadingMore ? "Loading..." : "Load More"}
212
+ ${loadingMore ? 'Loading...' : 'Load More'}
218
213
  </button>
219
214
  </div>
220
215
  `
221
- : ""
216
+ : ''
222
217
  }
223
218
  </div>
224
219
  </aside>
@@ -9,31 +9,93 @@ function getSortIcon(columnName, sortColumn, sortDirection) {
9
9
  return sortDirection === "desc" ? "south" : "north";
10
10
  }
11
11
 
12
- function renderSortableHeader(columnName, sortColumn, sortDirection) {
12
+ const COPY_COLUMN_ACTIONS = [
13
+ { mode: "column", label: "Copy column" },
14
+ { mode: "column-with-header", label: "Copy column with header" },
15
+ { mode: "first-10", label: "Copy first 10" },
16
+ { mode: "markdown-todo", label: "Export as Markdown Todo" },
17
+ ];
18
+
19
+ function renderColumnActionMenu(columnName, resultScope) {
20
+ return `
21
+ <details class="query-result-column-menu" data-copy-column-menu>
22
+ <summary
23
+ aria-label="Column actions for ${escapeHtml(columnName)}"
24
+ class="query-result-column-menu__toggle"
25
+ title="Column actions"
26
+ >
27
+ <span class="material-symbols-outlined" aria-hidden="true">more_vert</span>
28
+ </summary>
29
+ <div class="query-result-column-menu__panel" role="menu">
30
+ ${COPY_COLUMN_ACTIONS.map(
31
+ (item) => `
32
+ <button
33
+ class="query-result-column-menu__item"
34
+ data-action="open-copy-column-modal"
35
+ data-column-name="${escapeHtml(columnName)}"
36
+ data-copy-mode="${escapeHtml(item.mode)}"
37
+ data-result-scope="${escapeHtml(resultScope)}"
38
+ role="menuitem"
39
+ type="button"
40
+ >
41
+ ${escapeHtml(item.label)}
42
+ </button>
43
+ `
44
+ ).join("")}
45
+ </div>
46
+ </details>
47
+ `;
48
+ }
49
+
50
+ function renderSortableHeader(columnName, sortColumn, sortDirection, { resultScope, sortAction }) {
13
51
  const isActive = columnName === sortColumn;
52
+ const labelMarkup = `<span class="query-result-column-label truncate" title="${escapeHtml(columnName)}">${escapeHtml(
53
+ columnName
54
+ )}</span>`;
14
55
 
15
56
  return `
16
- <button
17
- class="flex w-full items-center justify-between gap-2 text-left transition-colors ${
18
- isActive ? "text-primary-container" : "text-on-surface-variant hover:text-primary-container"
19
- }"
20
- data-action="sort-editor-results-column"
57
+ <div
58
+ class="query-result-column-header"
21
59
  data-column-name="${escapeHtml(columnName)}"
22
- type="button"
60
+ data-result-column-header
61
+ data-result-scope="${escapeHtml(resultScope)}"
23
62
  >
24
- <span class="truncate">${escapeHtml(columnName)}</span>
25
- <span class="material-symbols-outlined text-sm leading-none">${getSortIcon(
26
- columnName,
27
- sortColumn,
28
- sortDirection
29
- )}</span>
30
- </button>
63
+ ${
64
+ sortAction
65
+ ? `
66
+ <button
67
+ class="query-result-column-sort ${
68
+ isActive ? "text-primary-container" : "text-on-surface-variant hover:text-primary-container"
69
+ }"
70
+ data-action="${escapeHtml(sortAction)}"
71
+ data-column-name="${escapeHtml(columnName)}"
72
+ type="button"
73
+ >
74
+ ${labelMarkup}
75
+ <span class="material-symbols-outlined text-sm leading-none">${getSortIcon(
76
+ columnName,
77
+ sortColumn,
78
+ sortDirection
79
+ )}</span>
80
+ </button>
81
+ `
82
+ : `<span class="query-result-column-static text-on-surface-variant">${labelMarkup}</span>`
83
+ }
84
+ ${renderColumnActionMenu(columnName, resultScope)}
85
+ </div>
31
86
  `;
32
87
  }
33
88
 
34
89
  export function renderQueryResultsPane(
35
90
  result,
36
- { selectedRowIndex = null, editable = false, sortColumn = null, sortDirection = null } = {}
91
+ {
92
+ selectedRowIndex = null,
93
+ editable = false,
94
+ sortColumn = null,
95
+ sortDirection = null,
96
+ resultScope = "editor",
97
+ sortAction = "sort-editor-results-column",
98
+ } = {}
37
99
  ) {
38
100
  if (!result) {
39
101
  return `
@@ -48,8 +110,8 @@ export function renderQueryResultsPane(
48
110
 
49
111
  const columns = (result.columns ?? []).map((columnName) => ({
50
112
  headerClassName:
51
- "border-b-2 border-primary-container px-4 py-3 text-[10px] font-bold uppercase tracking-widest",
52
- renderHeader: () => renderSortableHeader(columnName, sortColumn, sortDirection),
113
+ "query-result-header-cell border-b-2 border-primary-container px-4 py-3 text-[10px] font-bold uppercase tracking-widest",
114
+ renderHeader: () => renderSortableHeader(columnName, sortColumn, sortDirection, { resultScope, sortAction }),
53
115
  cellClassName: "px-4 py-3 align-top text-on-surface",
54
116
  render: (row) => {
55
117
  const value = formatCellValue(row[columnName]);