sqlite-hub 1.0.0 → 1.1.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 (40) hide show
  1. package/README.md +47 -33
  2. package/bin/sqlite-hub.js +127 -47
  3. package/frontend/js/api.js +6 -0
  4. package/frontend/js/app.js +199 -34
  5. package/frontend/js/components/modal.js +17 -1
  6. package/frontend/js/components/queryChartRenderer.js +28 -3
  7. package/frontend/js/components/queryEditor.js +20 -24
  8. package/frontend/js/components/queryHistoryDetail.js +1 -1
  9. package/frontend/js/components/queryHistoryHeader.js +48 -0
  10. package/frontend/js/components/queryHistoryList.js +132 -0
  11. package/frontend/js/components/queryHistoryPanel.js +72 -136
  12. package/frontend/js/components/structureGraph.js +699 -89
  13. package/frontend/js/components/tableDesignerEditor.js +3 -5
  14. package/frontend/js/store.js +73 -7
  15. package/frontend/js/utils/exportFilenames.js +3 -1
  16. package/frontend/js/views/charts.js +320 -169
  17. package/frontend/js/views/connections.js +59 -64
  18. package/frontend/js/views/data.js +12 -20
  19. package/frontend/js/views/editor.js +0 -2
  20. package/frontend/js/views/settings.js +78 -0
  21. package/frontend/js/views/structure.js +27 -13
  22. package/frontend/styles/components.css +155 -0
  23. package/frontend/styles/structure-graph.css +140 -35
  24. package/frontend/styles/tailwind.generated.css +1 -1
  25. package/frontend/styles/views.css +12 -6
  26. package/package.json +3 -2
  27. package/server/routes/export.js +89 -22
  28. package/server/routes/externalApi.js +84 -2
  29. package/server/routes/settings.js +37 -28
  30. package/server/services/appInfoService.js +215 -0
  31. package/server/services/databaseCommandService.js +50 -6
  32. package/server/services/sqlite/exportService.js +307 -22
  33. package/tests/api-token-auth.test.js +110 -1
  34. package/tests/cli-args.test.js +16 -3
  35. package/tests/database-command-service.test.js +39 -2
  36. package/tests/export-blob.test.js +54 -1
  37. package/tests/export-filenames.test.js +4 -0
  38. package/tests/settings-api-tokens-route.test.js +22 -1
  39. package/tests/settings-metadata.test.js +99 -1
  40. package/tests/settings-view.test.js +28 -0
@@ -1,39 +1,36 @@
1
- import { renderConnectionCard } from "../components/connectionCard.js";
2
- import { renderPageHeader } from "../components/pageHeader.js";
3
- import { escapeHtml } from "../utils/format.js";
1
+ import { renderConnectionCard } from '../components/connectionCard.js';
2
+ import { renderPageHeader } from '../components/pageHeader.js';
3
+ import { escapeHtml } from '../utils/format.js';
4
4
 
5
5
  function renderConnectionsActionButton({
6
- label,
7
- icon,
8
- action = "open-modal",
9
- modal,
10
- tone = "secondary",
11
- disabled = false,
6
+ label,
7
+ icon,
8
+ action = 'open-modal',
9
+ modal,
10
+ tone = 'secondary',
11
+ disabled = false,
12
12
  }) {
13
- const toneClassName =
14
- tone === "primary"
15
- ? "signature-button"
16
- : "standard-button";
17
- const iconClassName = "";
18
- const modalAttribute = modal ? `data-modal="${modal}"` : "";
13
+ const toneClassName = tone === 'primary' ? 'signature-button' : 'standard-button';
14
+ const iconClassName = '';
15
+ const modalAttribute = modal ? `data-modal="${modal}"` : '';
19
16
 
20
- return `
17
+ return `
21
18
  <button
22
19
  class="${toneClassName}"
23
20
  data-action="${escapeHtml(action)}"
24
21
  ${modalAttribute}
25
22
  type="button"
26
- ${disabled ? "disabled" : ""}
23
+ ${disabled ? 'disabled' : ''}
27
24
  >
28
25
  <span>${label}</span>
29
- <span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` : ""}">${icon}</span>
26
+ <span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` : ''}">${icon}</span>
30
27
  </button>
31
28
  `;
32
29
  }
33
30
 
34
31
  function renderConnectionsBody(state) {
35
- if (state.connections.loading && !state.connections.recent.length) {
36
- return `
32
+ if (state.connections.loading && !state.connections.recent.length) {
33
+ return `
37
34
  <div class="flex min-h-[280px] items-center justify-center border border-outline-variant/10 bg-surface-container-low">
38
35
  <div class="text-center text-on-surface-variant/40">
39
36
  <span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
@@ -41,10 +38,10 @@ function renderConnectionsBody(state) {
41
38
  </div>
42
39
  </div>
43
40
  `;
44
- }
41
+ }
45
42
 
46
- if (state.connections.error) {
47
- return `
43
+ if (state.connections.error) {
44
+ return `
48
45
  <div class="border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
49
46
  <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
50
47
  ${escapeHtml(state.connections.error.code)}
@@ -52,10 +49,10 @@ function renderConnectionsBody(state) {
52
49
  <div class="mt-2">${escapeHtml(state.connections.error.message)}</div>
53
50
  </div>
54
51
  `;
55
- }
52
+ }
56
53
 
57
- if (!state.connections.recent.length) {
58
- return `
54
+ if (!state.connections.recent.length) {
55
+ return `
59
56
  <div class="border border-dashed border-outline-variant/20 bg-surface-container-low px-8 py-10 text-center">
60
57
  <span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
61
58
  <p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
@@ -66,71 +63,69 @@ function renderConnectionsBody(state) {
66
63
  </p>
67
64
  <div class="mt-6 flex flex-wrap items-center justify-center gap-3">
68
65
  ${renderConnectionsActionButton({
69
- label: "Open Database",
70
- icon: "folder_open",
71
- modal: "open-connection",
72
- tone: "primary",
66
+ label: 'Create Database',
67
+ icon: 'note_add',
68
+ modal: 'create-connection',
73
69
  })}
74
70
  ${renderConnectionsActionButton({
75
- label: "Create Database",
76
- icon: "note_add",
77
- modal: "create-connection",
71
+ label: 'Open Database',
72
+ icon: 'folder_open',
73
+ modal: 'open-connection',
74
+ tone: 'primary',
78
75
  })}
79
76
  </div>
80
77
  </div>
81
78
  `;
82
- }
79
+ }
83
80
 
84
- return `
81
+ return `
85
82
  <div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
86
83
  ${state.connections.recent
87
- .map((connection) =>
88
- renderConnectionCard(connection, state.connections.active?.id)
89
- )
90
- .join("")}
84
+ .map(connection => renderConnectionCard(connection, state.connections.active?.id))
85
+ .join('')}
91
86
  </div>
92
87
  `;
93
88
  }
94
89
 
95
90
  export function renderConnectionsView(state) {
96
- const actions = `
91
+ const actions = `
92
+ ${
93
+ state.connections.active
94
+ ? renderConnectionsActionButton({
95
+ label: state.connections.backupLoading ? 'Creating Backup...' : 'Create Backup',
96
+ icon: 'inventory_2',
97
+ action: 'create-backup',
98
+ disabled: state.connections.backupLoading,
99
+ })
100
+ : ''
101
+ }
97
102
  ${renderConnectionsActionButton({
98
- label: "Open Database",
99
- icon: "folder_open",
100
- modal: "open-connection",
101
- tone: "primary",
103
+ label: 'Create Database',
104
+ icon: 'note_add',
105
+ modal: 'create-connection',
102
106
  })}
103
107
  ${renderConnectionsActionButton({
104
- label: "Create Database",
105
- icon: "note_add",
106
- modal: "create-connection",
108
+ label: 'Open Database',
109
+ icon: 'folder_open',
110
+ modal: 'open-connection',
111
+ tone: 'primary',
107
112
  })}
108
- ${
109
- state.connections.active
110
- ? renderConnectionsActionButton({
111
- label: state.connections.backupLoading ? "Creating Backup..." : "Create Backup",
112
- icon: "inventory_2",
113
- action: "create-backup",
114
- disabled: state.connections.backupLoading,
115
- })
116
- : ""
117
- }
118
113
  `;
119
114
 
120
- return {
121
- main: `
115
+ return {
116
+ main: `
122
117
  <section class="view-surface relative min-h-full overflow-hidden">
123
118
  <div class="data-grid-texture pointer-events-none absolute inset-0"></div>
124
119
  <div class="view-frame relative z-10">
125
120
  ${renderPageHeader({
126
- title: "Connections",
127
- subtitle: `Registry // Recent_Targets: ${String(state.connections.recent.length).padStart(2, "0")}`,
128
- actions,
121
+ title: 'Connections',
122
+ subtitle: `Registry // Recent_Targets: ${String(state.connections.recent.length).padStart(2, '0')}`,
123
+ actions,
129
124
  })}
130
125
  ${renderConnectionsBody(state)}
131
126
  </div>
132
127
  </section>
133
128
  `,
134
- panel: "",
135
- };
129
+ panel: '',
130
+ };
136
131
  }
@@ -112,25 +112,8 @@ function renderWorkspaceHeader(state) {
112
112
  const tablesVisible = state.dataBrowser.tablesVisible !== false;
113
113
 
114
114
  return `
115
- <header class="border-b border-outline-variant/10 bg-surface-container px-6 py-5">
116
- <div class="flex flex-wrap items-end justify-between gap-4">
117
- <div class="data-headline-container">
118
- <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
119
- Data Browser
120
- </div>
121
- <h1 class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
122
- ${escapeHtml(table?.name ?? 'Table Data')}
123
- </h1>
124
- <div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
125
- ${
126
- table
127
- ? `rows ${escapeHtml(formatNumber(table.rowCount ?? 0))} // columns ${escapeHtml(
128
- formatNumber(table.columns?.length ?? 0),
129
- )}`
130
- : `tables ${escapeHtml(formatNumber(state.dataBrowser.tables.length))}`
131
- }
132
- </div>
133
- </div>
115
+ <header class="border-b border-outline-variant/10 bg-surface-container px-6 py-3">
116
+ <div class="flex flex-wrap items-center justify-between gap-4">
134
117
  <div class="flex items-center gap-3">
135
118
  ${
136
119
  table
@@ -145,7 +128,14 @@ function renderWorkspaceHeader(state) {
145
128
  : '<span class="material-symbols-outlined text-sm">visibility</span> Show Tables'
146
129
  }
147
130
  </button>
148
- <button
131
+ `
132
+ : ''
133
+ }
134
+ </div>
135
+ <div class="flex flex-wrap items-center justify-end gap-3">
136
+ ${
137
+ table
138
+ ? `<button
149
139
  class="standard-button"
150
140
  data-action="open-data-export-modal"
151
141
  type="button"
@@ -476,6 +466,8 @@ function renderTableSurface(state) {
476
466
  escapeHtml(formatNumber(totalRows)),
477
467
  filteredRowsText,
478
468
  ' rows',
469
+ ' // columns ',
470
+ escapeHtml(formatNumber(table.columns?.length ?? 0)),
479
471
  '</div>',
480
472
  '<div class="flex flex-wrap items-center gap-4"><div class="flex items-center gap-2">',
481
473
  '<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">rows</span>',
@@ -307,8 +307,6 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
307
307
  query: state.editor.sqlText,
308
308
  executing: state.editor.executing,
309
309
  exporting: state.editor.exportLoading,
310
- historyLoading: state.editor.historyLoading,
311
- historyTotal: state.editor.historyTotal,
312
310
  editorVisible: state.editor.editorPanelVisible,
313
311
  historyVisible: state.editor.historyPanelVisible,
314
312
  })}
@@ -30,6 +30,66 @@ function renderSettingsNavigation(activeSection) {
30
30
  `;
31
31
  }
32
32
 
33
+ function renderVersionCheckStatus(settings) {
34
+ const result = settings.versionCheck;
35
+ const error = settings.versionCheckError;
36
+
37
+ if (error) {
38
+ return `
39
+ <div class="border border-error/20 bg-error-container/10 px-4 py-3 text-sm text-on-surface">
40
+ <div class="font-mono text-[10px] font-bold uppercase tracking-[0.18em] text-error">
41
+ ${escapeHtml(error.code ?? 'VERSION_CHECK_FAILED')}
42
+ </div>
43
+ <div class="mt-1 text-on-surface-variant">${escapeHtml(error.message ?? 'Version check failed.')}</div>
44
+ </div>
45
+ `;
46
+ }
47
+
48
+ if (!result) {
49
+ return `
50
+ <div class="border border-outline-variant/10 bg-surface-container-high px-4 py-3 text-sm text-on-surface-variant">
51
+ No version check has been run yet.
52
+ </div>
53
+ `;
54
+ }
55
+
56
+ const updateAvailable = Boolean(result.updateAvailable);
57
+ const releaseLink =
58
+ updateAvailable && result.releaseUrl
59
+ ? `
60
+ <a
61
+ class="standard-button"
62
+ href="${escapeHtml(result.releaseUrl)}"
63
+ rel="noreferrer"
64
+ target="_blank"
65
+ >
66
+ <span class="material-symbols-outlined text-sm">open_in_new</span>
67
+ Open Release
68
+ </a>
69
+ `
70
+ : '';
71
+
72
+ return `
73
+ <div class="border ${
74
+ updateAvailable ? 'border-primary-container/30 bg-primary-container/5' : 'border-outline-variant/10 bg-surface-container-high'
75
+ } px-4 py-3">
76
+ <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
77
+ <div class="min-w-0">
78
+ <div class="font-mono text-[10px] font-bold uppercase tracking-[0.18em] ${
79
+ updateAvailable ? 'text-primary-container' : 'text-on-surface-variant/70'
80
+ }">
81
+ ${updateAvailable ? 'New Version Available' : 'Up To Date'}
82
+ </div>
83
+ <div class="mt-1 text-sm text-on-surface-variant">
84
+ Current v${escapeHtml(result.currentVersion ?? '0.0.0')} · Latest v${escapeHtml(result.latestVersion ?? 'unknown')}
85
+ </div>
86
+ </div>
87
+ ${releaseLink}
88
+ </div>
89
+ </div>
90
+ `;
91
+ }
92
+
33
93
  function renderSettingsContent(state) {
34
94
  if (state.settings.loading && !state.settings.appVersion) {
35
95
  return `
@@ -56,6 +116,7 @@ function renderSettingsContent(state) {
56
116
  const activeSection = state.settings.section === 'api-tokens' ? 'api-tokens' : 'information';
57
117
  const appVersion = escapeHtml(state.settings.appVersion ?? '0.0.0');
58
118
  const sqliteVersion = escapeHtml(state.settings.sqliteVersion ?? 'unknown');
119
+ const versionCheckLoading = Boolean(state.settings.versionCheckLoading);
59
120
  const tokenDatabase = state.settings.tokenDatabase;
60
121
  const apiTokens = state.settings.apiTokens ?? [];
61
122
  const createdApiToken = state.settings.createdApiToken;
@@ -171,6 +232,23 @@ function renderSettingsContent(state) {
171
232
  <div class="border-t border-outline-variant/10 py-4 text-sm leading-6 text-on-surface-variant">
172
233
  SQL functions and syntax are evaluated with this SQLite runtime.
173
234
  </div>
235
+ <div class="border-t border-outline-variant/10 pt-4">
236
+ <div class="mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
237
+ <div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
238
+ Version_Check
239
+ </div>
240
+ <button
241
+ class="standard-button"
242
+ data-action="check-app-version"
243
+ type="button"
244
+ ${versionCheckLoading ? 'disabled aria-disabled="true"' : ''}
245
+ >
246
+ <span class="material-symbols-outlined text-sm">${versionCheckLoading ? 'progress_activity' : 'sync'}</span>
247
+ ${versionCheckLoading ? 'Checking...' : 'Check Updates'}
248
+ </button>
249
+ </div>
250
+ ${renderVersionCheckStatus(state.settings)}
251
+ </div>
174
252
  </div>
175
253
  </section>
176
254
 
@@ -115,20 +115,27 @@ function renderObjectInspector(detail) {
115
115
  return `
116
116
  <div class="structure-graph__panel">
117
117
  <div class="space-y-3">
118
- <div class="structure-graph__eyebrow">Object Inspector</div>
119
- <div class="structure-graph__title">${escapeHtml(detail.name)}</div>
120
- <div class="flex flex-wrap items-center gap-3">
121
- <div class="structure-graph__subtitle">${escapeHtml(detail.type ?? 'object')}</div>
122
- ${
123
- detail.tableName
124
- ? `
125
- <div class="structure-graph__subtitle">
126
- TABLE ${escapeHtml(detail.tableName)}
127
- </div>
128
- `
129
- : ''
130
- }
118
+ <div class="structure-graph__panel-heading">
119
+ <div class="structure-graph__eyebrow">Object Inspector</div>
120
+ <button
121
+ class="query-history-icon-button"
122
+ data-structure-graph-action="toggle-inspector"
123
+ type="button"
124
+ aria-label="Hide inspector"
125
+ >
126
+ <span class="material-symbols-outlined text-[18px]">close</span>
127
+ </button>
131
128
  </div>
129
+ <div class="structure-graph__title">${escapeHtml(detail.name)}</div>
130
+ ${
131
+ detail.tableName
132
+ ? `
133
+ <div class="structure-graph__subtitle">
134
+ TABLE ${escapeHtml(detail.tableName)}
135
+ </div>
136
+ `
137
+ : ''
138
+ }
132
139
  </div>
133
140
 
134
141
  <div class="structure-graph__summary">
@@ -255,6 +262,8 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
255
262
  <span class="material-symbols-outlined text-sm">table_rows</span>
256
263
  Open Data
257
264
  </button>
265
+ </div>
266
+ <div class="structure-graph__toolbar-inspector">
258
267
  <button
259
268
  class="${toolbarButtonClass}"
260
269
  data-structure-graph-action="toggle-inspector"
@@ -280,6 +289,11 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
280
289
  The active database does not expose table metadata for the schema graph.
281
290
  </p>
282
291
  </div>
292
+ <div
293
+ class="structure-graph__edge-readout"
294
+ data-structure-graph-edge-readout
295
+ hidden
296
+ ></div>
283
297
  </div>
284
298
 
285
299
  <aside
@@ -624,13 +624,35 @@
624
624
  }
625
625
 
626
626
  .status-badge {
627
+ align-items: center;
627
628
  border: 1px solid transparent;
629
+ box-sizing: border-box;
628
630
  display: inline-flex;
629
631
  font-size: var(--font-size-micro);
630
632
  font-weight: 700;
633
+ justify-content: center;
631
634
  letter-spacing: 0.12em;
635
+ line-height: 1;
632
636
  padding: 0.125rem 0.5rem;
633
637
  text-transform: uppercase;
638
+ white-space: nowrap;
639
+ }
640
+
641
+ .query-history-badge-row {
642
+ align-items: center;
643
+ display: flex;
644
+ flex-wrap: wrap;
645
+ gap: var(--spacing-2);
646
+ width: 100%;
647
+ }
648
+
649
+ .query-history-badge-row .status-badge {
650
+ min-height: var(--button-height);
651
+ min-width: 8.5rem;
652
+ }
653
+
654
+ .query-history-badge-row--compact .status-badge {
655
+ min-width: 6.75rem;
634
656
  }
635
657
 
636
658
  .status-badge--primary {
@@ -746,6 +768,76 @@
746
768
  width: clamp(20rem, 26vw, 22.5rem);
747
769
  }
748
770
 
771
+ .query-history-panel__header {
772
+ border-bottom: 1px solid var(--border-medium);
773
+ padding: var(--spacing-4);
774
+ }
775
+
776
+ .query-history-tabs {
777
+ align-items: center;
778
+ display: flex;
779
+ gap: var(--spacing-2);
780
+ margin-top: var(--spacing-4);
781
+ }
782
+
783
+ .query-history-tabs__count {
784
+ color: var(--text-variant-subtle);
785
+ font-family: var(--font-family-mono);
786
+ font-size: 10px;
787
+ letter-spacing: 0.16em;
788
+ margin-left: auto;
789
+ text-transform: uppercase;
790
+ }
791
+
792
+ .query-history-list-scroll {
793
+ flex: 1;
794
+ min-height: 0;
795
+ overflow: auto;
796
+ padding: var(--spacing-3);
797
+ }
798
+
799
+ .query-history-list {
800
+ display: flex;
801
+ flex-direction: column;
802
+ gap: var(--spacing-3);
803
+ }
804
+
805
+ .query-editor-toolbar {
806
+ align-items: center;
807
+ display: grid;
808
+ gap: var(--spacing-4);
809
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
810
+ }
811
+
812
+ .query-editor-toolbar__left,
813
+ .query-editor-toolbar__center,
814
+ .query-editor-toolbar__right {
815
+ align-items: center;
816
+ display: flex;
817
+ gap: var(--spacing-3);
818
+ }
819
+
820
+ .query-editor-toolbar__center {
821
+ flex-wrap: wrap;
822
+ justify-content: center;
823
+ }
824
+
825
+ .query-editor-toolbar__right {
826
+ justify-content: flex-end;
827
+ }
828
+
829
+ @media (max-width: 980px) {
830
+ .query-editor-toolbar {
831
+ grid-template-columns: 1fr;
832
+ }
833
+
834
+ .query-editor-toolbar__left,
835
+ .query-editor-toolbar__center,
836
+ .query-editor-toolbar__right {
837
+ justify-content: flex-start;
838
+ }
839
+ }
840
+
749
841
  .table-designer-sidebar {
750
842
  background: var(--color-surface-low);
751
843
  border-right: 1px solid var(--border-medium);
@@ -1478,6 +1570,63 @@ input.table-designer-main__name[type='text'] {
1478
1570
  background: var(--primary-alpha-08);
1479
1571
  }
1480
1572
 
1573
+ .query-history-item-main {
1574
+ display: flex;
1575
+ flex-direction: column;
1576
+ gap: var(--spacing-2);
1577
+ text-align: left;
1578
+ }
1579
+
1580
+ .query-history-item-title {
1581
+ color: var(--color-on-surface);
1582
+ display: block;
1583
+ font-family: var(--font-family-headline);
1584
+ font-size: 0.875rem;
1585
+ font-weight: 700;
1586
+ letter-spacing: 0;
1587
+ line-height: 1.15;
1588
+ min-width: 0;
1589
+ overflow: hidden;
1590
+ text-overflow: ellipsis;
1591
+ text-transform: uppercase;
1592
+ white-space: nowrap;
1593
+ width: 100%;
1594
+ }
1595
+
1596
+ .query-history-item.is-active .query-history-item-title,
1597
+ .query-history-item-hit.is-active .query-history-item-title {
1598
+ color: var(--color-primary-container);
1599
+ }
1600
+
1601
+ .query-history-item-footer {
1602
+ align-items: center;
1603
+ border-top: 1px solid var(--border-subtle);
1604
+ display: flex;
1605
+ gap: var(--spacing-3);
1606
+ justify-content: space-between;
1607
+ padding: var(--spacing-2) var(--spacing-3) var(--spacing-3);
1608
+ }
1609
+
1610
+ .query-history-item-meta {
1611
+ color: var(--text-variant-subtle);
1612
+ display: flex;
1613
+ flex-wrap: wrap;
1614
+ font-family: var(--font-family-mono);
1615
+ font-size: 10px;
1616
+ gap: var(--spacing-2);
1617
+ letter-spacing: 0.14em;
1618
+ min-width: 0;
1619
+ text-transform: uppercase;
1620
+ }
1621
+
1622
+ .query-history-item-actions {
1623
+ align-items: center;
1624
+ display: flex;
1625
+ flex-shrink: 0;
1626
+ gap: var(--spacing-1);
1627
+ margin-left: auto;
1628
+ }
1629
+
1481
1630
  .query-history-icon-button {
1482
1631
  background: transparent;
1483
1632
  border: 0;
@@ -1522,10 +1671,16 @@ input.table-designer-main__name[type='text'] {
1522
1671
  }
1523
1672
 
1524
1673
  .query-history-sql-preview {
1674
+ color: rgb(var(--rgb-on-surface-variant) / 0.75);
1525
1675
  display: -webkit-box;
1526
1676
  -webkit-box-orient: vertical;
1527
1677
  -webkit-line-clamp: 2;
1678
+ font-family: var(--font-family-mono);
1679
+ font-size: 0.75rem;
1680
+ line-height: 1.25rem;
1681
+ margin-top: var(--spacing-2);
1528
1682
  overflow: hidden;
1683
+ text-align: left;
1529
1684
  }
1530
1685
 
1531
1686
  .query-history-detail-sql {