sqlite-hub 1.1.2 → 1.4.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 (157) hide show
  1. package/README.md +106 -73
  2. package/bin/sqlite-hub.js +176 -2
  3. package/docs/API.md +122 -0
  4. package/docs/CLI.md +227 -0
  5. package/docs/DESIGN_GUIDELINES.md +45 -0
  6. package/docs/changelog.md +141 -0
  7. package/docs/shortkeys.md +27 -0
  8. package/docs/todo.md +16 -0
  9. package/examples/api/generate-types.js +52 -0
  10. package/frontend/assets/mockups/backups_1_1920.webp +0 -0
  11. package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
  12. package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
  13. package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
  14. package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
  15. package/frontend/assets/mockups/charts_1_1920.webp +0 -0
  16. package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
  17. package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
  18. package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
  19. package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
  20. package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
  21. package/frontend/assets/mockups/connections_1_1920.webp +0 -0
  22. package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
  23. package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
  24. package/frontend/assets/mockups/data_1_1920.webp +0 -0
  25. package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
  26. package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
  27. package/frontend/assets/mockups/documents_1_1920.webp +0 -0
  28. package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
  29. package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
  30. package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
  31. package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
  32. package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
  33. package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
  34. package/frontend/assets/mockups/overview_1_1920.webp +0 -0
  35. package/frontend/assets/mockups/settings_1_1920.webp +0 -0
  36. package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
  37. package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
  38. package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
  39. package/frontend/assets/mockups/structure_1_1920.webp +0 -0
  40. package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
  41. package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
  42. package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
  43. package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
  44. package/frontend/js/api.js +18 -0
  45. package/frontend/js/app.js +209 -13
  46. package/frontend/js/components/connectionCard.js +1 -1
  47. package/frontend/js/components/emptyState.js +4 -4
  48. package/frontend/js/components/generateTypesDropdown.js +33 -0
  49. package/frontend/js/components/metricCard.js +1 -1
  50. package/frontend/js/components/modal.js +211 -18
  51. package/frontend/js/components/pageHeader.js +1 -1
  52. package/frontend/js/components/queryHistoryDetail.js +3 -3
  53. package/frontend/js/components/queryHistoryHeader.js +1 -1
  54. package/frontend/js/components/queryHistoryPanel.js +1 -1
  55. package/frontend/js/components/queryResults.js +1 -1
  56. package/frontend/js/components/rowEditorPanel.js +65 -16
  57. package/frontend/js/components/topNav.js +1 -4
  58. package/frontend/js/components/workspaceOpenDropdown.js +52 -0
  59. package/frontend/js/router.js +28 -6
  60. package/frontend/js/store.js +489 -9
  61. package/frontend/js/utils/emailPreview.js +28 -0
  62. package/frontend/js/utils/markdownDocuments.js +17 -1
  63. package/frontend/js/views/backups.js +556 -36
  64. package/frontend/js/views/charts.js +9 -9
  65. package/frontend/js/views/connections.js +2 -2
  66. package/frontend/js/views/data.js +43 -17
  67. package/frontend/js/views/documents.js +3 -3
  68. package/frontend/js/views/editor.js +6 -6
  69. package/frontend/js/views/mediaTagging.js +7 -5
  70. package/frontend/js/views/overview.js +3 -3
  71. package/frontend/js/views/settings.js +4 -3
  72. package/frontend/js/views/structure.js +27 -10
  73. package/frontend/js/views/tableDesigner.js +23 -0
  74. package/frontend/styles/base.css +1 -40
  75. package/frontend/styles/components.css +114 -232
  76. package/frontend/styles/structure-graph.css +32 -68
  77. package/frontend/styles/tailwind.css +8 -4
  78. package/frontend/styles/tailwind.generated.css +1 -1
  79. package/frontend/styles/tokens.css +29 -21
  80. package/frontend/styles/views.css +94 -261
  81. package/package.json +18 -2
  82. package/server/routes/backups.js +18 -2
  83. package/server/routes/externalApi.js +36 -0
  84. package/server/routes/structure.js +22 -0
  85. package/server/services/databaseCommandService.js +14 -0
  86. package/server/services/sqlite/backupDiff.js +914 -0
  87. package/server/services/sqlite/backupService.js +77 -1
  88. package/server/services/sqlite/structureService.js +7 -0
  89. package/server/services/typeGenerationService.js +663 -0
  90. package/tailwind.config.cjs +0 -1
  91. package/.github/funding.yml +0 -2
  92. package/.github/workflows/ci.yml +0 -36
  93. package/database.sqlite +0 -0
  94. package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
  95. package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
  96. package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
  97. package/frontend/assets/mockups/connections_1200.webp +0 -0
  98. package/frontend/assets/mockups/data_1_1200.webp +0 -0
  99. package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
  100. package/frontend/assets/mockups/documents_1200.webp +0 -0
  101. package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
  102. package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
  103. package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
  104. package/frontend/assets/mockups/overview_1200.webp +0 -0
  105. package/frontend/assets/mockups/settings_1200.webp +0 -0
  106. package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
  107. package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
  108. package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
  109. package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
  110. package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
  111. package/frontend/assets/mockups/structure_1_1200.webp +0 -0
  112. package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
  113. package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
  114. package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
  115. package/tests/api-token-auth.test.js +0 -279
  116. package/tests/backup-manager.test.js +0 -140
  117. package/tests/backups-view.test.js +0 -64
  118. package/tests/charts-height-preset-storage.test.js +0 -60
  119. package/tests/charts-route-state.test.js +0 -144
  120. package/tests/check-constraint-options.test.js +0 -90
  121. package/tests/cli-args.test.js +0 -113
  122. package/tests/cli-service-delegation.test.js +0 -140
  123. package/tests/connection-removal.test.js +0 -52
  124. package/tests/connections-file-dialog-route.test.js +0 -89
  125. package/tests/copy-column-modal.test.js +0 -131
  126. package/tests/database-command-service.test.js +0 -174
  127. package/tests/database-documents.test.js +0 -85
  128. package/tests/documents-view.test.js +0 -132
  129. package/tests/dropdown-button.test.js +0 -75
  130. package/tests/export-blob.test.js +0 -99
  131. package/tests/export-filenames.test.js +0 -38
  132. package/tests/file-path-preview.test.js +0 -165
  133. package/tests/form-controls.test.js +0 -34
  134. package/tests/json-preview.test.js +0 -49
  135. package/tests/local-request-security.test.js +0 -85
  136. package/tests/markdown-documents.test.js +0 -79
  137. package/tests/native-file-dialog.test.js +0 -105
  138. package/tests/query-editor.test.js +0 -28
  139. package/tests/query-history-detail.test.js +0 -37
  140. package/tests/query-history-header.test.js +0 -30
  141. package/tests/query-results-truncation.test.js +0 -20
  142. package/tests/risky-sql.test.js +0 -30
  143. package/tests/row-editor-json.test.js +0 -82
  144. package/tests/row-editor-null-values.test.js +0 -155
  145. package/tests/row-editor-timestamp-preview.test.js +0 -192
  146. package/tests/security-paths.test.js +0 -84
  147. package/tests/settings-api-tokens-route.test.js +0 -97
  148. package/tests/settings-metadata.test.js +0 -114
  149. package/tests/settings-view.test.js +0 -107
  150. package/tests/sql-formatter.test.js +0 -173
  151. package/tests/sql-highlight.test.js +0 -38
  152. package/tests/sql-identifier-safety.test.js +0 -171
  153. package/tests/sql-result-limit.test.js +0 -66
  154. package/tests/structure-view.test.js +0 -56
  155. package/tests/table-designer-v2-unique-constraints.test.js +0 -78
  156. package/tests/table-scroll-state.test.js +0 -70
  157. package/tests/text-cell-stats.test.js +0 -38
@@ -153,6 +153,17 @@ export function getBackup(backupId) {
153
153
  return request(`/api/backups/${encodeURIComponent(backupId)}`);
154
154
  }
155
155
 
156
+ export function getBackupDiff(backupId, options = {}) {
157
+ const params = new URLSearchParams();
158
+
159
+ if (options.sampleLimit !== undefined) {
160
+ params.set("sampleLimit", String(options.sampleLimit));
161
+ }
162
+
163
+ const query = params.toString();
164
+ return request(`/api/backups/${encodeURIComponent(backupId)}/diff${query ? `?${query}` : ""}`);
165
+ }
166
+
156
167
  export function createBackup(payload = {}) {
157
168
  return request("/api/backups", {
158
169
  method: "POST",
@@ -340,6 +351,13 @@ export function getStructureDetail(tableName) {
340
351
  return request(`/api/structure/${encodeURIComponent(tableName)}`);
341
352
  }
342
353
 
354
+ export function generateStructureTypes(tableName, payload = {}) {
355
+ return request(`/api/structure/${encodeURIComponent(tableName)}/types`, {
356
+ method: "POST",
357
+ body: payload,
358
+ });
359
+ }
360
+
343
361
  export function getTableDesignerOverview() {
344
362
  return request("/api/table-designer");
345
363
  }
@@ -46,6 +46,7 @@ import {
46
46
  exportCurrentQueryFormat,
47
47
  getState,
48
48
  initializeApp,
49
+ insertMarkdownIntoLastOpenDocument,
49
50
  loadMoreQueryHistory,
50
51
  openModal,
51
52
  openOverviewInFinder,
@@ -55,7 +56,10 @@ import {
55
56
  openDeleteQueryHistoryModal,
56
57
  openDeleteSettingsApiTokenModal,
57
58
  openCreateBackupModal,
58
- openEditBackupNotesModal,
59
+ openEditBackupModal,
60
+ openBackupDiffDrawer,
61
+ closeBackupDiffDrawer,
62
+ openGenerateTypesModal,
59
63
  openDeleteBackupModal,
60
64
  openRestoreBackupModal,
61
65
  openDeleteQueryChartModal,
@@ -121,7 +125,7 @@ import {
121
125
  submitDeleteSettingsApiTokenConfirmation,
122
126
  submitBackupSafetyChoice,
123
127
  submitCreateBackupConfirmation,
124
- submitEditBackupNotesConfirmation,
128
+ submitEditBackupConfirmation,
125
129
  submitRowUpdatePreviewConfirmation,
126
130
  setQueryHistoryPanelVisibility,
127
131
  sortDataTableByColumn,
@@ -131,6 +135,7 @@ import {
131
135
  setCopyColumnModalError,
132
136
  setCopyColumnModalEditedText,
133
137
  setCopyColumnModalSubmitting,
138
+ setBackupDiffTab,
134
139
  setRoute,
135
140
  setSettingsSection,
136
141
  saveQueryHistoryNotes,
@@ -165,6 +170,7 @@ import {
165
170
  updateCurrentTableDesignerColumnField,
166
171
  updateCurrentTableDesignerConstraintField,
167
172
  updateCurrentTableDesignerField,
173
+ updateGenerateTypesModal,
168
174
  addCurrentTableDesignerColumn,
169
175
  applyCurrentMediaTaggingSelection,
170
176
  removeCurrentTableDesignerColumn,
@@ -239,6 +245,7 @@ let lastRenderedPanelMarkup = '';
239
245
  let lastRenderedModalMarkup = '';
240
246
  let lastRenderedToastMarkup = '';
241
247
  let lastRenderedChartsHistorySignature = '';
248
+ let lastRenderedChartsDetailSignature = '';
242
249
  let lastRenderedChartsCardSignature = '';
243
250
  let lastRenderedPanelOpen = false;
244
251
  let lastRenderedLockedRoute = false;
@@ -786,6 +793,27 @@ function buildChartsCardSignature(state) {
786
793
  });
787
794
  }
788
795
 
796
+ function buildChartsDetailSignature(state) {
797
+ if (state.route.name !== 'charts') {
798
+ return '';
799
+ }
800
+
801
+ const detail = state.charts.detail;
802
+ const historyVisible = state.charts.historyPanelVisible !== false || !state.charts.selectedHistoryId;
803
+
804
+ return JSON.stringify({
805
+ selectedHistoryId: state.charts.selectedHistoryId ?? null,
806
+ historyVisible,
807
+ detailLoading: Boolean(state.charts.detailLoading),
808
+ detailError: state.charts.detailError
809
+ ? [state.charts.detailError.code ?? '', state.charts.detailError.message ?? '']
810
+ : null,
811
+ hasDetailItem: Boolean(detail?.item),
812
+ displayTitle: detail?.item?.displayTitle ?? '',
813
+ cards: buildChartsCardSignature(state),
814
+ });
815
+ }
816
+
789
817
  function syncChartsHistorySelectionUi(state) {
790
818
  const selectedHistoryId = String(state.charts.selectedHistoryId ?? '');
791
819
  const historyButtons = shellRefs.view.querySelectorAll(
@@ -1042,11 +1070,11 @@ function renderNotFoundView() {
1042
1070
  <p class="font-mono text-[10px] uppercase tracking-[0.3em] text-primary-container/40">
1043
1071
  ROUTE_LOST // HASH_NOT_RECOGNIZED
1044
1072
  </p>
1045
- <h1 class="mt-4 font-headline text-6xl font-black uppercase tracking-tight text-primary-container">
1073
+ <h1 class="mt-4 font-body text-6xl font-black uppercase tracking-tight text-primary-container">
1046
1074
  404_SIGNAL
1047
1075
  </h1>
1048
1076
  <button
1049
- class="standard-button mt-8 px-6 font-headline text-sm"
1077
+ class="standard-button mt-8 px-6 font-body text-sm"
1050
1078
  data-action="navigate"
1051
1079
  data-to="/"
1052
1080
  type="button"
@@ -1368,6 +1396,7 @@ function renderApp(state) {
1368
1396
  const modalMarkup = renderModal(state);
1369
1397
  const toastMarkup = renderToasts(state.toasts);
1370
1398
  const chartsHistorySignature = buildChartsHistorySignature(state);
1399
+ const chartsDetailSignature = buildChartsDetailSignature(state);
1371
1400
  const chartsCardSignature = buildChartsCardSignature(state);
1372
1401
  const isLockedRoute = [
1373
1402
  'editor',
@@ -1388,6 +1417,7 @@ function renderApp(state) {
1388
1417
  const panelChanged = panel !== lastRenderedPanelMarkup;
1389
1418
  const modalChanged = modalMarkup !== lastRenderedModalMarkup;
1390
1419
  const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
1420
+ const chartsDetailChanged = chartsDetailSignature !== lastRenderedChartsDetailSignature;
1391
1421
  const chartsCardsChanged = chartsCardSignature !== lastRenderedChartsCardSignature;
1392
1422
  const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
1393
1423
  const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
@@ -1436,12 +1466,17 @@ function renderApp(state) {
1436
1466
  const historyPatched = !chartsHistoryChanged || patchChartsHistoryUi(state);
1437
1467
 
1438
1468
  if (historyPatched) {
1439
- if (chartsCardsChanged) {
1440
- teardownQueryChartRenderer();
1441
- }
1469
+ if (chartsDetailChanged) {
1470
+ if (chartsCardsChanged) {
1471
+ teardownQueryChartRenderer();
1472
+ }
1442
1473
 
1443
- preservedChartsDom = !chartsCardsChanged;
1444
- mainPatched = patchChartsDetailUi(state, { preserveCharts: preservedChartsDom });
1474
+ preservedChartsDom = !chartsCardsChanged;
1475
+ mainPatched = patchChartsDetailUi(state, { preserveCharts: preservedChartsDom });
1476
+ } else {
1477
+ preservedChartsDom = true;
1478
+ mainPatched = syncChartsHistorySelectionUi(state);
1479
+ }
1445
1480
  }
1446
1481
 
1447
1482
  if (!mainPatched) {
@@ -1542,6 +1577,7 @@ function renderApp(state) {
1542
1577
  lastRenderedModalMarkup = modalMarkup;
1543
1578
  lastRenderedToastMarkup = toastMarkup;
1544
1579
  lastRenderedChartsHistorySignature = chartsHistorySignature;
1580
+ lastRenderedChartsDetailSignature = chartsDetailSignature;
1545
1581
  lastRenderedChartsCardSignature = chartsCardSignature;
1546
1582
  lastRenderedPanelOpen = panelOpen;
1547
1583
  lastRenderedLockedRoute = isLockedRoute;
@@ -1567,6 +1603,22 @@ async function executeEditorQueryAndNavigate() {
1567
1603
  router.navigate(success && activeTab === 'results' ? '/editor/results' : '/editor');
1568
1604
  }
1569
1605
 
1606
+ function quoteSqlIdentifier(identifier) {
1607
+ return `"${String(identifier ?? '').replace(/"/g, '""')}"`;
1608
+ }
1609
+
1610
+ async function openTableInSqlEditor(tableName) {
1611
+ const normalizedTableName = String(tableName ?? '').trim();
1612
+
1613
+ if (!normalizedTableName) {
1614
+ showToast('No table selected for SQL Editor.', 'alert');
1615
+ return;
1616
+ }
1617
+
1618
+ setCurrentQuery(`SELECT * FROM ${quoteSqlIdentifier(normalizedTableName)};`);
1619
+ router.navigate('/editor');
1620
+ }
1621
+
1570
1622
  function formatCurrentQuery() {
1571
1623
  const currentQuery = getState().editor.sqlText ?? '';
1572
1624
  const formattedQuery = formatSqlQuery(currentQuery);
@@ -1956,6 +2008,68 @@ function downloadTextFile({ text, filename, mimeType }) {
1956
2008
  URL.revokeObjectURL(url);
1957
2009
  }
1958
2010
 
2011
+ async function copyGeneratedTypes() {
2012
+ const modal = getState().modal;
2013
+ const code = modal?.kind === 'generate-types' ? String(modal.result?.code ?? '') : '';
2014
+
2015
+ if (!code) {
2016
+ showToast('No generated code to copy.', 'alert');
2017
+ return;
2018
+ }
2019
+
2020
+ if (!navigator.clipboard?.writeText) {
2021
+ showToast('Clipboard API is not available.', 'alert');
2022
+ return;
2023
+ }
2024
+
2025
+ try {
2026
+ await navigator.clipboard.writeText(code);
2027
+ showToast(
2028
+ modal.result?.files?.length ? 'Type definitions copied to clipboard.' : 'Type definition copied to clipboard.',
2029
+ 'success',
2030
+ );
2031
+ } catch (error) {
2032
+ showToast(
2033
+ modal.result?.files?.length
2034
+ ? 'Type definitions could not be copied.'
2035
+ : 'Type definition could not be copied.',
2036
+ 'alert',
2037
+ );
2038
+ }
2039
+ }
2040
+
2041
+ function downloadGeneratedTypes() {
2042
+ const modal = getState().modal;
2043
+ const result = modal?.kind === 'generate-types' ? modal.result : null;
2044
+ const files = result?.files ?? [];
2045
+
2046
+ if (files.length) {
2047
+ files.forEach((file, index) => {
2048
+ window.setTimeout(() => {
2049
+ downloadTextFile({
2050
+ text: file.code,
2051
+ filename: file.fileName || `${file.tableName || 'types'}.txt`,
2052
+ mimeType: 'text/plain;charset=utf-8',
2053
+ });
2054
+ }, index * 120);
2055
+ });
2056
+ showToast(`${files.length} type definition downloads started.`, 'success');
2057
+ return;
2058
+ }
2059
+
2060
+ if (!result?.code) {
2061
+ showToast('No generated code to download.', 'alert');
2062
+ return;
2063
+ }
2064
+
2065
+ downloadTextFile({
2066
+ text: result.code,
2067
+ filename: result.fileName || 'types.txt',
2068
+ mimeType: 'text/plain;charset=utf-8',
2069
+ });
2070
+ showToast('Type definition download started.', 'success');
2071
+ }
2072
+
1959
2073
  function getSelectedDataBrowserRowForJson(state) {
1960
2074
  if (state.dataBrowser.selectedRow) {
1961
2075
  return state.dataBrowser.selectedRow;
@@ -2103,6 +2217,39 @@ function exportRowEditorJson() {
2103
2217
  showToast(`Row from "${payload.label}" exported as JSON.`, 'success');
2104
2218
  }
2105
2219
 
2220
+ async function insertRowEditorJsonIntoDocument() {
2221
+ const payload = buildRowEditorJsonPayload(getState());
2222
+
2223
+ if (!payload) {
2224
+ showToast('No row is selected.', 'alert');
2225
+ return;
2226
+ }
2227
+
2228
+ const markdown = ['```json', payload.text, '```'].join('\n');
2229
+
2230
+ try {
2231
+ const result = await insertMarkdownIntoLastOpenDocument(markdown);
2232
+
2233
+ if (!result.documentId) {
2234
+ showToast('Open a document before inserting row JSON.', 'alert');
2235
+ return;
2236
+ }
2237
+
2238
+ if (!result.inserted) {
2239
+ showToast('Row JSON could not be inserted.', 'alert');
2240
+ return;
2241
+ }
2242
+
2243
+ if (!result.saved) {
2244
+ scheduleDocumentAutosave(result.documentId);
2245
+ }
2246
+
2247
+ showToast(`Row from "${payload.label}" inserted into document.`, 'success');
2248
+ } catch (error) {
2249
+ showToast('Open a document before inserting row JSON.', 'alert');
2250
+ }
2251
+ }
2252
+
2106
2253
  function exportCurrentDocumentMarkdown() {
2107
2254
  const documents = getState().documents;
2108
2255
 
@@ -2319,6 +2466,9 @@ async function handleAction(actionNode) {
2319
2466
  case 'export-row-editor-json':
2320
2467
  exportRowEditorJson();
2321
2468
  return;
2469
+ case 'insert-row-editor-json-into-document':
2470
+ await insertRowEditorJsonIntoDocument();
2471
+ return;
2322
2472
  case 'open-modal':
2323
2473
  openModal(actionNode.dataset.modal, {
2324
2474
  columnId: actionNode.dataset.columnId,
@@ -2496,11 +2646,32 @@ async function handleAction(actionNode) {
2496
2646
  case 'refresh-backups':
2497
2647
  await refreshBackups();
2498
2648
  return;
2649
+ case 'open-table-in-sql-editor':
2650
+ await openTableInSqlEditor(actionNode.dataset.tableName);
2651
+ return;
2652
+ case 'open-generate-types-modal':
2653
+ await openGenerateTypesModal(actionNode.dataset.tableName, actionNode.dataset.typeTarget, actionNode.dataset.typeScope);
2654
+ return;
2655
+ case 'copy-generated-types':
2656
+ await copyGeneratedTypes();
2657
+ return;
2658
+ case 'download-generated-types':
2659
+ downloadGeneratedTypes();
2660
+ return;
2499
2661
  case 'open-restore-backup-modal':
2500
2662
  openRestoreBackupModal(actionNode.dataset.backupId);
2501
2663
  return;
2502
- case 'open-edit-backup-notes-modal':
2503
- openEditBackupNotesModal(actionNode.dataset.backupId);
2664
+ case 'open-compare-backup-drawer':
2665
+ await openBackupDiffDrawer(actionNode.dataset.backupId);
2666
+ return;
2667
+ case 'set-backup-diff-tab':
2668
+ setBackupDiffTab(actionNode.dataset.tab);
2669
+ return;
2670
+ case 'close-backup-diff-drawer':
2671
+ closeBackupDiffDrawer();
2672
+ return;
2673
+ case 'open-edit-backup-modal':
2674
+ openEditBackupModal(actionNode.dataset.backupId);
2504
2675
  return;
2505
2676
  case 'open-delete-backup-modal':
2506
2677
  openDeleteBackupModal(actionNode.dataset.backupId);
@@ -3095,6 +3266,12 @@ document.addEventListener('keydown', event => {
3095
3266
  return;
3096
3267
  }
3097
3268
 
3269
+ if (state.route.name === 'backups' && state.backups.diff?.visible) {
3270
+ event.preventDefault();
3271
+ closeBackupDiffDrawer();
3272
+ return;
3273
+ }
3274
+
3098
3275
  if (state.editor.historySelectedId !== null || state.editor.historyDetail) {
3099
3276
  event.preventDefault();
3100
3277
  clearQueryHistorySelection();
@@ -3141,6 +3318,15 @@ document.addEventListener('input', event => {
3141
3318
  return;
3142
3319
  }
3143
3320
 
3321
+ if (bindNode.dataset.bind === 'type-generation-field') {
3322
+ if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
3323
+ return;
3324
+ }
3325
+
3326
+ void updateGenerateTypesModal(bindNode.dataset.typeGenerationField, bindNode.value);
3327
+ return;
3328
+ }
3329
+
3144
3330
  if (bindNode.dataset.bind === 'copy-column-format-field') {
3145
3331
  updateCopyColumnModalFormatField(
3146
3332
  bindNode.dataset.field,
@@ -3312,6 +3498,13 @@ document.addEventListener('change', event => {
3312
3498
  return;
3313
3499
  }
3314
3500
 
3501
+ if (bindNode.dataset.bind === 'type-generation-field') {
3502
+ const nextValue =
3503
+ bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox' ? bindNode.checked : bindNode.value;
3504
+ void updateGenerateTypesModal(bindNode.dataset.typeGenerationField, nextValue);
3505
+ return;
3506
+ }
3507
+
3315
3508
  if (bindNode.dataset.bind === 'data-search-column') {
3316
3509
  void setDataSearchColumn(bindNode.value);
3317
3510
  return;
@@ -3537,8 +3730,11 @@ document.addEventListener('submit', async event => {
3537
3730
  type: String(formData.get('type') ?? 'manual'),
3538
3731
  });
3539
3732
  return;
3540
- case 'edit-backup-notes':
3541
- await submitEditBackupNotesConfirmation(String(formData.get('notes') ?? ''));
3733
+ case 'edit-backup':
3734
+ await submitEditBackupConfirmation({
3735
+ name: String(formData.get('name') ?? ''),
3736
+ notes: String(formData.get('notes') ?? ''),
3737
+ });
3542
3738
  return;
3543
3739
  case 'delete-backup-confirm':
3544
3740
  await submitDeleteBackupConfirmation();
@@ -36,7 +36,7 @@ export function renderConnectionCard(connection, activeConnectionId) {
36
36
  renderStatusBadge(isActive ? "ACTIVE" : "RECENT", isActive ? "primary" : "muted"),
37
37
  connection.readOnly ? renderStatusBadge("READ_ONLY", "alert") : "",
38
38
  "</div></div>",
39
- '<h3 class="mb-1 font-headline text-xl font-bold uppercase ',
39
+ '<h3 class="mb-1 font-body text-xl font-bold uppercase ',
40
40
  isActive ? "text-[#FCE300]" : "text-on-surface",
41
41
  '">',
42
42
  escapeHtml(connection.label),
@@ -61,7 +61,7 @@ function renderActiveConnection(activeConnection) {
61
61
  <p class="text-[10px] font-mono uppercase tracking-[0.24em] text-primary-container/70">
62
62
  ACTIVE_DATABASE
63
63
  </p>
64
- <h2 class="mt-2 font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
64
+ <h2 class="mt-2 font-body text-2xl font-black uppercase tracking-tight text-primary-container">
65
65
  ${escapeHtml(activeConnection.label)}
66
66
  </h2>
67
67
  <p class="mt-2 font-mono text-[10px] text-on-surface-variant/55">${escapeHtml(
@@ -93,7 +93,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
93
93
  SYSTEM_READY // ${hasActive ? 'ACTIVE_CONTEXT' : 'IDLE_STATE'}
94
94
  </span>
95
95
  </div>
96
- <h1 class="mb-4 font-headline text-7xl font-black tracking-tighter text-primary-container opacity-90 md:text-9xl">
96
+ <h1 class="mb-4 font-body text-7xl font-black tracking-tighter text-primary-container opacity-90 md:text-9xl">
97
97
  SQLite Hub
98
98
  </h1>
99
99
  <p class="font-mono text-primary-container/40">The only fucking SQLite Manager you'll ever need.</p>
@@ -104,7 +104,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
104
104
  </div>
105
105
  <div class="mx-auto grid w-full max-w-3xl grid-cols-1 gap-4 px-6 md:grid-cols-2">
106
106
  <button
107
- class="landing-primary-action clipped-btn group flex items-center justify-between bg-primary-container px-8 py-6 font-headline text-lg font-bold transition-all duration-300 hover:shadow-[0_0_20px_rgba(252,227,0,0.3)]"
107
+ class="landing-primary-action clipped-btn group flex items-center justify-between bg-primary-container px-8 py-6 font-body text-lg font-bold transition-all duration-300 hover:shadow-[0_0_20px_rgba(252,227,0,0.3)]"
108
108
  data-action="open-modal"
109
109
  data-modal="open-connection"
110
110
  style="--clip-path: polygon(0 0, 90% 0, 100% 25%, 100% 100%, 0 100%);"
@@ -114,7 +114,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
114
114
  <span class="material-symbols-outlined transition-transform group-hover:translate-x-1">add_circle</span>
115
115
  </button>
116
116
  <button
117
- class="flex items-center justify-between border-l-2 border-primary-container bg-surface-container-highest px-8 py-6 font-headline text-lg font-bold text-primary-container transition-colors duration-150 hover:bg-surface-bright"
117
+ class="flex items-center justify-between border-l-2 border-primary-container bg-surface-container-highest px-8 py-6 font-body text-lg font-bold text-primary-container transition-colors duration-150 hover:bg-surface-bright"
118
118
  data-action="open-modal"
119
119
  data-modal="create-connection"
120
120
  type="button"
@@ -0,0 +1,33 @@
1
+ import { renderDropdownButton } from './dropdownButton.js';
2
+
3
+ export function renderGenerateTypesDropdown({ selectedTableName = '', hasTables = false } = {}) {
4
+ const hasSelectedTable = Boolean(String(selectedTableName ?? '').trim());
5
+
6
+ return renderDropdownButton({
7
+ align: 'left',
8
+ icon: 'code',
9
+ label: 'Generate Types',
10
+ title: 'Generate types',
11
+ items: [
12
+ {
13
+ action: 'open-generate-types-modal',
14
+ disabled: !hasSelectedTable,
15
+ icon: 'select_check_box',
16
+ label: 'Selected table',
17
+ dataAttributes: {
18
+ tableName: selectedTableName,
19
+ typeScope: 'selected',
20
+ },
21
+ },
22
+ {
23
+ action: 'open-generate-types-modal',
24
+ disabled: !hasTables,
25
+ icon: 'library_books',
26
+ label: 'All tables',
27
+ dataAttributes: {
28
+ typeScope: 'all',
29
+ },
30
+ },
31
+ ],
32
+ });
33
+ }
@@ -10,7 +10,7 @@ export function renderMetricCard({
10
10
  return `
11
11
  <div class="metric-card ${accent ? "metric-card--accent" : ""}">
12
12
  <span class="text-[10px] font-mono text-on-surface/40 uppercase">${escapeHtml(label)}</span>
13
- <span class="text-3xl font-headline font-bold text-on-surface">${escapeHtml(value)}</span>
13
+ <span class="text-3xl font-body font-bold text-on-surface">${escapeHtml(value)}</span>
14
14
  ${
15
15
  progress
16
16
  ? `