sqlite-hub 0.9.13 → 0.10.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.
@@ -385,6 +385,13 @@ export function updateDataTableRow(tableName, payload) {
385
385
  });
386
386
  }
387
387
 
388
+ export function previewDataTableRowUpdate(tableName, payload) {
389
+ return request(`/api/data/${encodeURIComponent(tableName)}/rows/preview-update`, {
390
+ method: "POST",
391
+ body: payload,
392
+ });
393
+ }
394
+
388
395
  export function deleteDataTableRow(tableName, payload) {
389
396
  return request(`/api/data/${encodeURIComponent(tableName)}/rows`, {
390
397
  method: "DELETE",
@@ -49,6 +49,8 @@ import {
49
49
  openEditConnectionModal,
50
50
  openCreateQueryChartModal,
51
51
  openEditQueryChartModal,
52
+ openDataRowUpdatePreview,
53
+ openEditorRowUpdatePreview,
52
54
  refreshCurrentRoute,
53
55
  refreshMediaTaggingPreview,
54
56
  removeConnection,
@@ -66,6 +68,8 @@ import {
66
68
  selectStructureEntry,
67
69
  setTableDesignerSearchQuery,
68
70
  setTableDesignerSqlPreviewVisibility,
71
+ setDataTableSearchQuery,
72
+ setStructureTableSearchQuery,
69
73
  toggleStructureTablesPanel,
70
74
  setDataPage,
71
75
  setDataPageSize,
@@ -82,6 +86,7 @@ import {
82
86
  submitCreateMediaTaggingTagTable,
83
87
  submitCreateMediaTaggingMappingTable,
84
88
  submitDeleteQueryHistoryConfirmation,
89
+ submitRowUpdatePreviewConfirmation,
85
90
  setQueryHistoryPanelVisibility,
86
91
  sortDataTableByColumn,
87
92
  sortEditorResultsByColumn,
@@ -100,8 +105,6 @@ import {
100
105
  submitCreateConnection,
101
106
  createCurrentMediaTag,
102
107
  submitDeleteRowConfirmation,
103
- submitDataRowUpdate,
104
- submitEditorRowUpdate,
105
108
  submitEditConnection,
106
109
  submitImportSql,
107
110
  submitOpenConnection,
@@ -1775,6 +1778,16 @@ document.addEventListener('input', event => {
1775
1778
  return;
1776
1779
  }
1777
1780
 
1781
+ if (bindNode.dataset.bind === 'data-table-search') {
1782
+ setDataTableSearchQuery(bindNode.value);
1783
+ return;
1784
+ }
1785
+
1786
+ if (bindNode.dataset.bind === 'structure-table-search') {
1787
+ setStructureTableSearchQuery(bindNode.value);
1788
+ return;
1789
+ }
1790
+
1778
1791
  if (bindNode.dataset.bind === 'table-designer-search') {
1779
1792
  setTableDesignerSearchQuery(bindNode.value);
1780
1793
  return;
@@ -2055,6 +2068,9 @@ document.addEventListener('submit', async event => {
2055
2068
  case 'delete-query-history-confirm':
2056
2069
  await submitDeleteQueryHistoryConfirmation();
2057
2070
  return;
2071
+ case 'apply-row-update-preview':
2072
+ await submitRowUpdatePreviewConfirmation();
2073
+ return;
2058
2074
  case 'create-media-tagging-tag-table':
2059
2075
  await submitCreateMediaTaggingTagTable();
2060
2076
  return;
@@ -2083,7 +2099,7 @@ document.addEventListener('submit', async event => {
2083
2099
  }
2084
2100
  }
2085
2101
 
2086
- await submitDataRowUpdate(String(formData.get('rowIndex') ?? ''), values, rowIdentity);
2102
+ await openDataRowUpdatePreview(String(formData.get('rowIndex') ?? ''), values, rowIdentity);
2087
2103
  return;
2088
2104
  }
2089
2105
  case 'save-editor-row': {
@@ -2097,7 +2113,7 @@ document.addEventListener('submit', async event => {
2097
2113
  values[key.slice('field:'.length)] = String(value ?? '');
2098
2114
  }
2099
2115
 
2100
- await submitEditorRowUpdate(String(formData.get('rowIndex') ?? ''), values);
2116
+ await openEditorRowUpdatePreview(String(formData.get('rowIndex') ?? ''), values);
2101
2117
  return;
2102
2118
  }
2103
2119
  case 'save-query-history-title': {
@@ -425,6 +425,98 @@ function renderDeleteRowConfirmForm(modal) {
425
425
  ].join("");
426
426
  }
427
427
 
428
+ function renderRowUpdatePreviewForm(modal) {
429
+ const preview = modal.preview ?? {};
430
+ const changes = preview.changes ?? [];
431
+ const params = preview.params ?? [];
432
+ const warnings = preview.warnings ?? [];
433
+ const changesMarkup = changes.length
434
+ ? [
435
+ '<div class="overflow-hidden border border-outline-variant/10">',
436
+ '<table class="w-full text-left text-sm">',
437
+ '<thead class="bg-surface-container-highest text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">',
438
+ '<tr><th class="px-3 py-2">Column</th><th class="px-3 py-2">Old</th><th class="px-3 py-2">New</th></tr>',
439
+ '</thead><tbody class="divide-y divide-outline-variant/10">',
440
+ changes
441
+ .map(
442
+ change => `
443
+ <tr>
444
+ <td class="px-3 py-2 font-mono text-xs text-primary-container">${escapeHtml(change.column)}</td>
445
+ <td class="px-3 py-2 text-on-surface-variant/70">${escapeHtml(change.oldValue)}</td>
446
+ <td class="px-3 py-2 text-on-surface">${escapeHtml(change.newValue)}</td>
447
+ </tr>
448
+ `,
449
+ )
450
+ .join(''),
451
+ '</tbody></table></div>',
452
+ ].join('')
453
+ : '<div class="border border-outline-variant/10 bg-surface-container-low px-4 py-3 text-sm text-on-surface-variant/65">No changed values were detected.</div>';
454
+ const paramsMarkup = params.length
455
+ ? `
456
+ <div class="space-y-2">
457
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">Parameters</div>
458
+ <div class="space-y-2">
459
+ ${params
460
+ .map(
461
+ param => `
462
+ <div class="flex gap-3 border border-outline-variant/10 bg-surface-container-low px-3 py-2 text-xs">
463
+ <span class="font-mono text-primary-container">$${escapeHtml(param.index)}</span>
464
+ <span class="min-w-0 break-words text-on-surface">${escapeHtml(param.value)}</span>
465
+ </div>
466
+ `,
467
+ )
468
+ .join('')}
469
+ </div>
470
+ </div>
471
+ `
472
+ : '';
473
+ const warningsMarkup = warnings.length
474
+ ? `
475
+ <div class="space-y-2">
476
+ ${warnings
477
+ .map(
478
+ warning => `
479
+ <div class="border border-primary-container/20 bg-primary-container/10 px-4 py-3 text-sm text-on-surface">
480
+ ${escapeHtml(warning)}
481
+ </div>
482
+ `,
483
+ )
484
+ .join('')}
485
+ </div>
486
+ `
487
+ : '';
488
+
489
+ return `
490
+ <form class="space-y-5" data-form="apply-row-update-preview">
491
+ <div class="space-y-2">
492
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
493
+ ${escapeHtml(modal.tableName ?? 'Table Row')}
494
+ </div>
495
+ <p class="text-sm leading-7 text-on-surface-variant/70">
496
+ Review the SQL and changed values before applying this row update.
497
+ </p>
498
+ </div>
499
+ ${warningsMarkup}
500
+ <div class="space-y-2">
501
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">SQL Preview</div>
502
+ ${renderSqlPreviewField(preview.sql ?? '', 'sql-highlight-shell--compact')}
503
+ </div>
504
+ <div class="space-y-2">
505
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">Changed Values</div>
506
+ ${changesMarkup}
507
+ </div>
508
+ ${paramsMarkup}
509
+ ${renderError(modal.error)}
510
+ <div class="flex items-center justify-end gap-3 pt-2">
511
+ <button class="standard-button" data-action="close-modal" type="button">Cancel</button>
512
+ <button class="signature-button" type="submit">
513
+ ${modal.submitting ? 'Applying...' : 'Apply Changes'}
514
+ </button>
515
+ </div>
516
+ </form>
517
+ `;
518
+ }
519
+
428
520
  function renderChartColumnOptions(analysis, { allowEmpty = false, includeNumericHint = false } = {}) {
429
521
  const options = allowEmpty ? [{ value: "", label: "None" }] : [];
430
522
 
@@ -869,6 +961,11 @@ export function renderModal(state) {
869
961
  title: "Delete Row",
870
962
  body: renderDeleteRowConfirmForm(modal),
871
963
  },
964
+ "row-update-preview": {
965
+ eyebrow: "Mutation // Review row update",
966
+ title: "Review Update",
967
+ body: renderRowUpdatePreviewForm(modal),
968
+ },
872
969
  "chart-editor": {
873
970
  eyebrow: "Charts // Configure query-based ECharts panel",
874
971
  title: modal.draft?.mode === "edit" ? "Edit Chart" : "New Chart",
@@ -904,7 +1001,7 @@ export function renderModal(state) {
904
1001
 
905
1002
  return `
906
1003
  <div class="fixed inset-0 z-50 flex items-center justify-center bg-background/85 px-4 backdrop-blur-sm">
907
- <div class="w-full ${modal.kind === "chart-editor" ? "max-w-3xl" : "max-w-xl"} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
1004
+ <div class="w-full ${modal.kind === "chart-editor" || modal.kind === "row-update-preview" ? "max-w-3xl" : "max-w-xl"} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
908
1005
  <div class="flex items-start justify-between gap-4 border-b border-outline-variant/10 bg-surface-container-low px-6 py-5">
909
1006
  <div>
910
1007
  <div class="text-[10px] font-mono uppercase tracking-[0.26em] text-primary-container/70">
@@ -38,7 +38,6 @@ function renderEditorSurface({ query }) {
38
38
 
39
39
  export function renderQueryEditor({
40
40
  query,
41
- title,
42
41
  executing = false,
43
42
  exporting = false,
44
43
  historyLoading = false,
@@ -48,13 +47,7 @@ export function renderQueryEditor({
48
47
  }) {
49
48
  const secondaryButtonClass = "standard-button";
50
49
  const left = `
51
- <div class="flex items-center gap-2 bg-surface-container-lowest px-3 py-1">
52
- <span class="material-symbols-outlined text-xs text-[#FCE300]">database</span>
53
- <span class="text-[10px] font-mono uppercase tracking-widest text-on-surface-variant">${escapeHtml(
54
- title
55
- )}</span>
56
- </div>
57
- <div class="hidden items-center gap-2 text-[10px] font-mono uppercase tracking-widest text-on-surface-variant/40 md:flex">
50
+ <div class="flex items-center gap-2 text-[10px] font-mono uppercase tracking-widest text-on-surface-variant/40">
58
51
  <span class="material-symbols-outlined text-xs">history</span>
59
52
  ${historyLoading ? "Loading history..." : `${historyTotal} queries tracked`}
60
53
  </div>
@@ -38,6 +38,7 @@ const UI_PREFERENCE_STORAGE_KEYS = {
38
38
  sqlEditorHistoryVisible: 'sqlite_hub_sql_editor_history_visible',
39
39
  sqlEditorEditorVisible: 'sqlite_hub_sql_editor_editor_visible',
40
40
  sqlEditorActiveTab: 'sqlite_hub_sql_editor_active_tab',
41
+ sqlEditorQueryDraft: 'sqlite_hub_sql_editor_query_draft',
41
42
  dataTablesVisible: 'sqlite_hub_data_tables_visible',
42
43
  structureTablesVisible: 'sqlite_hub_structure_tables_visible',
43
44
  chartsHistoryVisible: 'sqlite_hub_charts_history_visible',
@@ -103,6 +104,23 @@ function storeBoolean(key, value) {
103
104
  }
104
105
  }
105
106
 
107
+ function readStoredString(key, fallback = '') {
108
+ try {
109
+ const value = globalThis.localStorage?.getItem(key);
110
+ return value === null || value === undefined ? fallback : value;
111
+ } catch {
112
+ return fallback;
113
+ }
114
+ }
115
+
116
+ function storeString(key, value) {
117
+ try {
118
+ globalThis.localStorage?.setItem(key, String(value ?? ''));
119
+ } catch {
120
+ // Ignore unavailable browser storage; the in-memory value still applies.
121
+ }
122
+ }
123
+
106
124
  function readStoredEditorActiveTab(fallback = 'messages') {
107
125
  try {
108
126
  const value = globalThis.localStorage?.getItem(UI_PREFERENCE_STORAGE_KEYS.sqlEditorActiveTab);
@@ -193,6 +211,7 @@ const state = {
193
211
  sortColumn: null,
194
212
  sortDirection: null,
195
213
  searchQuery: '',
214
+ tableSearchQuery: '',
196
215
  searchColumn: '',
197
216
  selectedRowIndex: null,
198
217
  selectedRow: null,
@@ -202,7 +221,7 @@ const state = {
202
221
  saveError: null,
203
222
  },
204
223
  editor: {
205
- sqlText: '',
224
+ sqlText: readStoredString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft),
206
225
  editorPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.sqlEditorEditorVisible, true),
207
226
  history: [],
208
227
  historyPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.sqlEditorHistoryVisible, true),
@@ -271,6 +290,7 @@ const state = {
271
290
  selectedName: null,
272
291
  detail: null,
273
292
  tablesVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, true),
293
+ tableSearchQuery: '',
274
294
  loading: false,
275
295
  detailLoading: false,
276
296
  error: null,
@@ -527,6 +547,10 @@ function resetDataBrowserSearch() {
527
547
  state.dataBrowser.searchColumn = '';
528
548
  }
529
549
 
550
+ function resetDataBrowserTableSearch() {
551
+ state.dataBrowser.tableSearchQuery = '';
552
+ }
553
+
530
554
  function resetDataBrowserSort() {
531
555
  state.dataBrowser.sortColumn = null;
532
556
  state.dataBrowser.sortDirection = null;
@@ -707,7 +731,11 @@ function clearDataBrowserRowSelectionState() {
707
731
  }
708
732
 
709
733
  function resolveDataBrowserRowSelection(rowIndex, identity = null) {
710
- const numericIndex = Number(rowIndex);
734
+ const hasRowIndex =
735
+ rowIndex !== null &&
736
+ rowIndex !== undefined &&
737
+ (typeof rowIndex !== 'string' || rowIndex.trim() !== '');
738
+ const numericIndex = hasRowIndex ? Number(rowIndex) : NaN;
711
739
 
712
740
  if (Number.isInteger(numericIndex) && numericIndex >= 0) {
713
741
  const indexedRow = state.dataBrowser.table?.rows?.[numericIndex] ?? null;
@@ -951,6 +979,7 @@ function setMissingDatabaseState() {
951
979
  state.dataBrowser.selectedTable = null;
952
980
  state.dataBrowser.table = null;
953
981
  state.dataBrowser.page = 1;
982
+ resetDataBrowserTableSearch();
954
983
  resetDataBrowserSort();
955
984
  resetDataBrowserSearch();
956
985
  clearDataBrowserRowSelectionState();
@@ -964,6 +993,7 @@ function setMissingDatabaseState() {
964
993
  state.structure.data = null;
965
994
  state.structure.detail = null;
966
995
  state.structure.tablesVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, true);
996
+ state.structure.tableSearchQuery = '';
967
997
  state.structure.error = error;
968
998
 
969
999
  state.tableDesigner.loading = false;
@@ -1919,6 +1949,7 @@ function invalidateDatabaseCaches() {
1919
1949
  state.dataBrowser.selectedTable = null;
1920
1950
  state.dataBrowser.table = null;
1921
1951
  state.dataBrowser.page = 1;
1952
+ resetDataBrowserTableSearch();
1922
1953
  resetDataBrowserSearch();
1923
1954
  clearDataBrowserRowSelectionState();
1924
1955
  state.dataBrowser.pendingOpenRow = null;
@@ -1939,6 +1970,7 @@ function invalidateDatabaseCaches() {
1939
1970
  state.structure.data = null;
1940
1971
  state.structure.detail = null;
1941
1972
  state.structure.tablesVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, true);
1973
+ state.structure.tableSearchQuery = '';
1942
1974
  state.mediaTagging.loading = false;
1943
1975
  state.mediaTagging.previewLoading = false;
1944
1976
  state.mediaTagging.saving = false;
@@ -2356,6 +2388,92 @@ export function closeModal() {
2356
2388
  closeModalInternal();
2357
2389
  }
2358
2390
 
2391
+ export async function openDataRowUpdatePreview(rowIndex, values, identity = null) {
2392
+ const tableName = state.dataBrowser.selectedTable;
2393
+ const selected = resolveDataBrowserRowSelection(rowIndex, identity);
2394
+
2395
+ if (!tableName || !selected.identity) {
2396
+ pushToast('The selected row could not be loaded.', 'alert');
2397
+ return null;
2398
+ }
2399
+
2400
+ state.dataBrowser.saving = true;
2401
+ state.dataBrowser.saveError = null;
2402
+ emitChange();
2403
+
2404
+ try {
2405
+ const response = await api.previewDataTableRowUpdate(tableName, {
2406
+ identity: selected.identity,
2407
+ values,
2408
+ });
2409
+
2410
+ state.modal = {
2411
+ kind: 'row-update-preview',
2412
+ target: 'data',
2413
+ tableName,
2414
+ rowIndex: selected.rowIndex,
2415
+ identity: selected.identity,
2416
+ values,
2417
+ preview: response.data,
2418
+ error: null,
2419
+ submitting: false,
2420
+ };
2421
+ emitChange();
2422
+ return response.data;
2423
+ } catch (error) {
2424
+ state.dataBrowser.saveError = normalizeError(error);
2425
+ emitChange();
2426
+ return null;
2427
+ } finally {
2428
+ state.dataBrowser.saving = false;
2429
+ emitChange();
2430
+ }
2431
+ }
2432
+
2433
+ export async function openEditorRowUpdatePreview(rowIndex, values) {
2434
+ const numericIndex = Number(rowIndex);
2435
+ const result = state.editor.result;
2436
+ const row = result?.rows?.[numericIndex];
2437
+ const tableName = result?.editing?.tableName ?? null;
2438
+
2439
+ if (!tableName || !row || !canEditQueryResult()) {
2440
+ pushToast('The selected query result row could not be loaded.', 'alert');
2441
+ return null;
2442
+ }
2443
+
2444
+ state.editor.saving = true;
2445
+ state.editor.saveError = null;
2446
+ emitChange();
2447
+
2448
+ try {
2449
+ const response = await api.previewDataTableRowUpdate(tableName, {
2450
+ identity: row.__identity,
2451
+ values,
2452
+ });
2453
+
2454
+ state.modal = {
2455
+ kind: 'row-update-preview',
2456
+ target: 'editor',
2457
+ tableName,
2458
+ rowIndex: numericIndex,
2459
+ identity: row.__identity,
2460
+ values,
2461
+ preview: response.data,
2462
+ error: null,
2463
+ submitting: false,
2464
+ };
2465
+ emitChange();
2466
+ return response.data;
2467
+ } catch (error) {
2468
+ state.editor.saveError = normalizeError(error);
2469
+ emitChange();
2470
+ return null;
2471
+ } finally {
2472
+ state.editor.saving = false;
2473
+ emitChange();
2474
+ }
2475
+ }
2476
+
2359
2477
  export function toggleChartsSqlPanel() {
2360
2478
  state.charts.sqlExpanded = !state.charts.sqlExpanded;
2361
2479
  emitChange();
@@ -2623,6 +2741,7 @@ export function setCurrentQuery(query) {
2623
2741
  const nextLineCount = Math.max(1, nextQuery.split('\n').length);
2624
2742
 
2625
2743
  state.editor.sqlText = nextQuery;
2744
+ storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, nextQuery);
2626
2745
 
2627
2746
  if (previousLineCount !== nextLineCount) {
2628
2747
  emitChange();
@@ -2631,6 +2750,7 @@ export function setCurrentQuery(query) {
2631
2750
 
2632
2751
  export function clearCurrentQuery() {
2633
2752
  state.editor.sqlText = '';
2753
+ storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, '');
2634
2754
  state.editor.result = null;
2635
2755
  state.editor.lastExecutedSql = '';
2636
2756
  resetEditorResultSort();
@@ -2696,6 +2816,8 @@ export async function executeCurrentQuery() {
2696
2816
  return true;
2697
2817
  } catch (error) {
2698
2818
  state.editor.error = normalizeError(error);
2819
+ state.editor.activeTab = 'messages';
2820
+ storeEditorActiveTab('messages');
2699
2821
  await refreshQueryHistoryState();
2700
2822
  return false;
2701
2823
  } finally {
@@ -2819,6 +2941,7 @@ export function openQueryHistoryInEditor(historyId, options = {}) {
2819
2941
  setActiveQueryHistoryItem(historyId);
2820
2942
  clearQueryHistoryDetailState();
2821
2943
  state.editor.sqlText = options.append ? [state.editor.sqlText.trim(), rawSql].filter(Boolean).join('\n\n') : rawSql;
2944
+ storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, state.editor.sqlText);
2822
2945
  emitChange();
2823
2946
  return true;
2824
2947
  }
@@ -2926,6 +3049,16 @@ export function setTableDesignerSearchQuery(query) {
2926
3049
  emitChange();
2927
3050
  }
2928
3051
 
3052
+ export function setDataTableSearchQuery(query) {
3053
+ state.dataBrowser.tableSearchQuery = String(query ?? '');
3054
+ emitChange();
3055
+ }
3056
+
3057
+ export function setStructureTableSearchQuery(query) {
3058
+ state.structure.tableSearchQuery = String(query ?? '');
3059
+ emitChange();
3060
+ }
3061
+
2929
3062
  export function setTableDesignerSqlPreviewVisibility(visible) {
2930
3063
  const nextValue = typeof visible === 'boolean' ? visible : !Boolean(state.tableDesigner.sqlPreviewVisible);
2931
3064
 
@@ -3577,9 +3710,10 @@ export async function setDataPageSize(pageSize) {
3577
3710
  }
3578
3711
  }
3579
3712
 
3580
- export async function submitDataRowUpdate(rowIndex, values, identity = null) {
3713
+ export async function submitDataRowUpdate(rowIndex, values, identity = null, options = {}) {
3581
3714
  const tableName = state.dataBrowser.selectedTable;
3582
3715
  const selected = resolveDataBrowserRowSelection(rowIndex, identity);
3716
+ const reportErrorToModal = Boolean(options.reportErrorToModal);
3583
3717
 
3584
3718
  if (!tableName || !selected.identity) {
3585
3719
  pushToast('The selected row could not be loaded.', 'alert');
@@ -3601,8 +3735,12 @@ export async function submitDataRowUpdate(rowIndex, values, identity = null) {
3601
3735
  clearDataBrowserRowSelectionState();
3602
3736
  return response.data;
3603
3737
  } catch (error) {
3604
- state.dataBrowser.saveError = normalizeError(error);
3605
- emitChange();
3738
+ if (reportErrorToModal) {
3739
+ withModalError(error);
3740
+ } else {
3741
+ state.dataBrowser.saveError = normalizeError(error);
3742
+ emitChange();
3743
+ }
3606
3744
  return null;
3607
3745
  } finally {
3608
3746
  state.dataBrowser.saving = false;
@@ -3654,11 +3792,12 @@ export async function submitDataRowDelete(rowIndex, options = {}) {
3654
3792
  }
3655
3793
  }
3656
3794
 
3657
- export async function submitEditorRowUpdate(rowIndex, values) {
3795
+ export async function submitEditorRowUpdate(rowIndex, values, options = {}) {
3658
3796
  const numericIndex = Number(rowIndex);
3659
3797
  const result = state.editor.result;
3660
3798
  const row = result?.rows?.[numericIndex];
3661
3799
  const tableName = result?.editing?.tableName ?? null;
3800
+ const reportErrorToModal = Boolean(options.reportErrorToModal);
3662
3801
 
3663
3802
  if (!tableName || !row || !canEditQueryResult()) {
3664
3803
  pushToast('The selected query result row could not be loaded.', 'alert');
@@ -3688,8 +3827,12 @@ export async function submitEditorRowUpdate(rowIndex, values) {
3688
3827
  emitChange();
3689
3828
  return response.data;
3690
3829
  } catch (error) {
3691
- state.editor.saveError = normalizeError(error);
3692
- emitChange();
3830
+ if (reportErrorToModal) {
3831
+ withModalError(error);
3832
+ } else {
3833
+ state.editor.saveError = normalizeError(error);
3834
+ emitChange();
3835
+ }
3693
3836
  return null;
3694
3837
  } finally {
3695
3838
  state.editor.saving = false;
@@ -3767,6 +3910,29 @@ export async function submitDeleteRowConfirmation() {
3767
3910
  return result;
3768
3911
  }
3769
3912
 
3913
+ export async function submitRowUpdatePreviewConfirmation() {
3914
+ const modal = state.modal;
3915
+
3916
+ if (modal?.kind !== 'row-update-preview') {
3917
+ return null;
3918
+ }
3919
+
3920
+ startModalSubmission();
3921
+
3922
+ const result =
3923
+ modal.target === 'editor'
3924
+ ? await submitEditorRowUpdate(modal.rowIndex, modal.values, { reportErrorToModal: true })
3925
+ : await submitDataRowUpdate(modal.rowIndex, modal.values, modal.identity, {
3926
+ reportErrorToModal: true,
3927
+ });
3928
+
3929
+ if (result) {
3930
+ closeModalInternal();
3931
+ }
3932
+
3933
+ return result;
3934
+ }
3935
+
3770
3936
  export async function saveCurrentQueryChartDraft() {
3771
3937
  if (state.modal?.kind !== 'chart-editor' || !state.modal.draft) {
3772
3938
  return null;
@@ -3988,12 +4154,12 @@ export function getQueryMessages(snapshot = state) {
3988
4154
 
3989
4155
  if (snapshot.editor.error) {
3990
4156
  return [
3991
- ...queryMessages,
3992
4157
  {
3993
4158
  tone: 'alert',
3994
4159
  label: snapshot.editor.error.code,
3995
4160
  value: snapshot.editor.error.message,
3996
4161
  },
4162
+ ...queryMessages,
3997
4163
  ];
3998
4164
  }
3999
4165
 
@@ -4008,7 +4174,6 @@ export function getQueryMessages(snapshot = state) {
4008
4174
  }
4009
4175
 
4010
4176
  return [
4011
- ...queryMessages,
4012
4177
  ...snapshot.editor.result.statements.map(statement => ({
4013
4178
  tone: statement.kind === 'resultSet' ? 'success' : inferStatusTone(statement.keyword),
4014
4179
  label: `${statement.keyword} #${statement.index + 1}`,
@@ -4017,6 +4182,7 @@ export function getQueryMessages(snapshot = state) {
4017
4182
  ? `${statement.rowCount} row(s) returned.`
4018
4183
  : `${statement.changes} row(s) affected.`,
4019
4184
  })),
4185
+ ...queryMessages,
4020
4186
  ];
4021
4187
  }
4022
4188
 
@@ -16,8 +16,37 @@ function getSelectedRow(state) {
16
16
  return state.dataBrowser.table?.rows?.[rowIndex] ?? null;
17
17
  }
18
18
 
19
+ function getFilteredTables(tables, searchQuery) {
20
+ const normalizedSearch = String(searchQuery ?? '')
21
+ .trim()
22
+ .toLowerCase();
23
+
24
+ if (!normalizedSearch) {
25
+ return tables;
26
+ }
27
+
28
+ return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
29
+ }
30
+
31
+ function renderDataTableSearch(state) {
32
+ return `
33
+ <label class="table-designer-sidebar__search">
34
+ <span class="material-symbols-outlined text-sm text-on-surface-variant/55">search</span>
35
+ <input
36
+ class="table-designer-sidebar__search-input"
37
+ data-bind="data-table-search"
38
+ placeholder="Search tables..."
39
+ spellcheck="false"
40
+ type="search"
41
+ value="${escapeHtml(state.dataBrowser.tableSearchQuery ?? '')}"
42
+ />
43
+ </label>
44
+ `;
45
+ }
46
+
19
47
  function renderTableList(state) {
20
48
  const tables = state.dataBrowser.tables ?? [];
49
+ const filteredTables = getFilteredTables(tables, state.dataBrowser.tableSearchQuery);
21
50
  const activeName = state.dataBrowser.selectedTable;
22
51
 
23
52
  if (state.dataBrowser.loading && !tables.length) {
@@ -39,10 +68,18 @@ function renderTableList(state) {
39
68
  `;
40
69
  }
41
70
 
71
+ if (!filteredTables.length) {
72
+ return `
73
+ <div class="px-6 py-6 text-sm text-on-surface-variant/55">
74
+ No tables match the current search.
75
+ </div>
76
+ `;
77
+ }
78
+
42
79
  return `
43
80
  <div class="custom-scrollbar flex-1 overflow-auto px-4 py-4">
44
81
  <div class="space-y-2">
45
- ${tables
82
+ ${filteredTables
46
83
  .map(
47
84
  table => `
48
85
  <button
@@ -556,6 +593,7 @@ export function renderDataView(state) {
556
593
  total ${escapeHtml(formatNumber(state.dataBrowser.tables.length))}
557
594
  </div>
558
595
  </div>
596
+ ${renderDataTableSearch(state)}
559
597
  ${renderTableList(state)}
560
598
  </aside>
561
599
  `
@@ -4,7 +4,7 @@ import { renderQueryHistoryDetail } from '../components/queryHistoryDetail.js';
4
4
  import { renderQueryHistoryPanel } from '../components/queryHistoryPanel.js';
5
5
  import { renderRowEditorPanel } from '../components/rowEditorPanel.js';
6
6
  import { renderQueryResultsPane } from '../components/queryResults.js';
7
- import { getCurrentConnection, getQueryMessages, getQueryPerformance } from '../store.js';
7
+ import { getQueryMessages, getQueryPerformance } from '../store.js';
8
8
  import { escapeHtml, formatBytes, formatCellValue, formatExecutionTimeMs, formatNumber, isBlobPreview } from '../utils/format.js';
9
9
 
10
10
  function renderMissingDatabase() {
@@ -244,7 +244,6 @@ function renderResultsSurface(state, isResultsRoute) {
244
244
  }
245
245
 
246
246
  export function renderEditorView(state, { isResultsRoute = false } = {}) {
247
- const connection = getCurrentConnection(state);
248
247
  const editorSectionClass = state.editor.editorPanelVisible ? 'min-h-[27.5%] max-h-[60%]' : 'flex-none';
249
248
  const resultsSectionClass = 'flex-1';
250
249
 
@@ -261,7 +260,6 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
261
260
  historyTotal: state.editor.historyTotal,
262
261
  editorVisible: state.editor.editorPanelVisible,
263
262
  historyVisible: state.editor.historyPanelVisible,
264
- title: connection?.label ?? 'SQLite Query Workspace',
265
263
  })}
266
264
  </section>
267
265
  <section class="${resultsSectionClass} flex min-h-0 min-w-0 flex-col overflow-hidden">