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
@@ -16,7 +16,7 @@ function renderMissingDatabase() {
16
16
  <section class="flex flex-1 items-center justify-center px-8 text-center">
17
17
  <div class="max-w-xl">
18
18
  <span class="material-symbols-outlined mb-4 text-5xl text-on-surface-variant/20">database_off</span>
19
- <h1 class="font-headline text-3xl font-black uppercase tracking-tight text-primary-container">
19
+ <h1 class="font-body text-3xl font-black uppercase tracking-tight text-primary-container">
20
20
  No Active SQLite Database
21
21
  </h1>
22
22
  <p class="mt-3 text-sm leading-7 text-on-surface-variant/65">
@@ -154,7 +154,7 @@ function renderChartsList(state) {
154
154
  <span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">${
155
155
  hasSearch ? 'manage_search' : isSavedTab ? 'bookmark' : isUnsavedTab ? 'bookmark_border' : 'query_stats'
156
156
  }</span>
157
- <p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
157
+ <p class="font-body text-lg font-black uppercase tracking-tight text-on-surface">
158
158
  ${
159
159
  hasSearch
160
160
  ? 'No Matching Queries'
@@ -220,7 +220,7 @@ function renderEmptyChartDetail() {
220
220
  <div class="flex flex-1 items-center justify-center px-8 py-10 text-center">
221
221
  <div class="max-w-lg">
222
222
  <span class="material-symbols-outlined mb-4 text-5xl text-on-surface-variant/20">bar_chart</span>
223
- <h2 class="font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
223
+ <h2 class="font-body text-2xl font-black uppercase tracking-tight text-primary-container">
224
224
  Select A Query
225
225
  </h2>
226
226
  <p class="mt-3 text-sm leading-7 text-on-surface-variant/65">
@@ -339,7 +339,7 @@ export function renderChartsDetailDrawer(state) {
339
339
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
340
340
  <div class="border-b border-outline-variant/10 px-5 py-4">
341
341
  <div class="flex items-center justify-between gap-3">
342
- <span class="font-headline text-sm font-black uppercase tracking-[0.18em] text-primary-container">
342
+ <span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
343
343
  Chart Detail
344
344
  </span>
345
345
  <button class="query-history-icon-button" data-action="close-charts-query-detail" type="button">
@@ -359,7 +359,7 @@ export function renderChartsDetailDrawer(state) {
359
359
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
360
360
  <div class="border-b border-outline-variant/10 px-5 py-4">
361
361
  <div class="flex items-center justify-between gap-3">
362
- <span class="font-headline text-sm font-black uppercase tracking-[0.18em] text-primary-container">
362
+ <span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
363
363
  Chart Detail
364
364
  </span>
365
365
  <button class="query-history-icon-button" data-action="close-charts-query-detail" type="button">
@@ -415,7 +415,7 @@ export function renderChartsDetailDrawer(state) {
415
415
  <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">
416
416
  Chart Detail
417
417
  </div>
418
- <h2 class="mt-1 truncate font-headline text-lg font-black uppercase tracking-tight text-on-surface">
418
+ <h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
419
419
  ${escapeHtml(detail.item.displayTitle)}
420
420
  </h2>
421
421
  </div>
@@ -547,7 +547,7 @@ function renderChartCard(chart, state, analysis) {
547
547
  sizeClass,
548
548
  '"><header class="query-chart-card__header"><div class="min-w-0">',
549
549
  '<div class="flex flex-wrap items-center gap-2">',
550
- '<h3 class="truncate font-headline text-xl font-black uppercase tracking-tight text-on-surface">',
550
+ '<h3 class="truncate font-body text-xl font-black uppercase tracking-tight text-on-surface">',
551
551
  escapeHtml(chart.name),
552
552
  '</h3>',
553
553
  renderStatusBadge(getQueryChartTypeLabel(chart.chartType), 'primary'),
@@ -609,7 +609,7 @@ export function renderChartsDetail(state) {
609
609
  <div class="charts-detail-shell">
610
610
  <header class="charts-detail-shell__header" data-charts-detail-header>
611
611
  <div class="charts-detail-shell__title">
612
- <h1 class="mt-2 truncate font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
612
+ <h1 class="mt-2 truncate font-body text-4xl font-black uppercase tracking-tight text-primary-container">
613
613
  ${escapeHtml(detail.item.displayTitle)}
614
614
  </h1>
615
615
  </div>
@@ -659,7 +659,7 @@ export function renderChartsDetail(state) {
659
659
  <div class="flex min-h-[240px] items-center justify-center border border-dashed border-outline-variant/20 bg-surface-container-low px-8 text-center">
660
660
  <div>
661
661
  <span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">add_chart</span>
662
- <p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
662
+ <p class="font-body text-lg font-black uppercase tracking-tight text-on-surface">
663
663
  No Charts Yet
664
664
  </p>
665
665
  <p class="mt-2 max-w-md text-sm leading-6 text-on-surface-variant/60">
@@ -43,7 +43,7 @@ function renderConnectionsBody(state) {
43
43
  if (state.connections.error) {
44
44
  return `
45
45
  <div class="border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
46
- <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
46
+ <div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
47
47
  ${escapeHtml(state.connections.error.code)}
48
48
  </div>
49
49
  <div class="mt-2">${escapeHtml(state.connections.error.message)}</div>
@@ -55,7 +55,7 @@ function renderConnectionsBody(state) {
55
55
  return `
56
56
  <div class="border border-dashed border-outline-variant/20 bg-surface-container-low px-8 py-10 text-center">
57
57
  <span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
58
- <p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
58
+ <p class="font-body text-xl font-black uppercase tracking-tight text-primary-container">
59
59
  No Recorded SQLite Connections
60
60
  </p>
61
61
  <p class="mx-auto mt-3 max-w-xl text-sm leading-7 text-on-surface-variant/65">
@@ -1,6 +1,8 @@
1
1
  import { renderDataGrid } from '../components/dataGrid.js';
2
2
  import { renderRowEditorPanel } from '../components/rowEditorPanel.js';
3
+ import { renderWorkspaceOpenDropdown } from '../components/workspaceOpenDropdown.js';
3
4
  import { escapeHtml, formatCellValue, formatNumber, isBlobPreview, truncateMiddle } from '../utils/format.js';
5
+ import { detectEmailValue } from '../utils/emailPreview.js';
4
6
  import { compactPathForDisplay, detectFilePathValue } from '../utils/filePathPreview.js';
5
7
 
6
8
  function getSelectedRow(state) {
@@ -135,6 +137,30 @@ function renderWorkspaceHeader(state) {
135
137
  }
136
138
  </div>
137
139
  <div class="flex flex-wrap items-center justify-end gap-3">
140
+ ${
141
+ table
142
+ ? renderWorkspaceOpenDropdown({
143
+ tableName: table.name,
144
+ destinations: [
145
+ {
146
+ icon: 'account_tree',
147
+ key: 'structure',
148
+ label: 'Structure',
149
+ target: tableName => `/structure/${encodeURIComponent(tableName)}`,
150
+ },
151
+ {
152
+ icon: 'table_chart',
153
+ key: 'table-designer',
154
+ label: 'Table Designer',
155
+ target: tableName => `/table-designer/${encodeURIComponent(tableName)}`,
156
+ },
157
+ {
158
+ key: 'sql-editor',
159
+ },
160
+ ],
161
+ })
162
+ : ''
163
+ }
138
164
  ${
139
165
  table
140
166
  ? `<button
@@ -154,21 +180,6 @@ function renderWorkspaceHeader(state) {
154
180
  >
155
181
  Reload Data
156
182
  </button>
157
- ${
158
- table
159
- ? `
160
- <button
161
- class="standard-button"
162
- data-action="navigate"
163
- data-to="/structure/${encodeURIComponent(table.name)}"
164
- type="button"
165
- >
166
- <span class="material-symbols-outlined">account_tree</span>
167
- Open Structure
168
- </button>
169
- `
170
- : ''
171
- }
172
183
  </div>
173
184
  </div>
174
185
  </header>
@@ -182,7 +193,7 @@ function renderWorkspaceError(state) {
182
193
 
183
194
  return `
184
195
  <div class="border-b border-error/20 bg-error-container/10 px-6 py-4 text-sm text-on-surface">
185
- <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
196
+ <div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
186
197
  ${escapeHtml(state.dataBrowser.error.code)}
187
198
  </div>
188
199
  <div class="mt-2">${escapeHtml(state.dataBrowser.error.message)}</div>
@@ -370,11 +381,26 @@ function renderTableSurface(state) {
370
381
  cellClassName: 'px-4 py-2 align-top text-[11px] text-on-surface',
371
382
  render: row => {
372
383
  const rawValue = row[columnName];
384
+ const email = detectEmailValue(rawValue);
373
385
  const filePath = detectFilePathValue(rawValue, columnName, tableMeta);
374
386
  const value = formatCellValue(rawValue);
375
387
  const isNull = value === 'NULL';
376
388
  const widthClass = getCellWidthClass(columnName);
377
389
 
390
+ if (email) {
391
+ return `
392
+ <span
393
+ class="inline-flex ${widthClass} items-center gap-2 overflow-hidden whitespace-nowrap text-on-surface"
394
+ title="${escapeHtml(email.value)}"
395
+ >
396
+ <span class="material-symbols-outlined text-sm text-primary-container/75">alternate_email</span>
397
+ <span class="min-w-0 overflow-hidden text-ellipsis">${escapeHtml(
398
+ truncateMiddle(email.value, 48),
399
+ )}</span>
400
+ </span>
401
+ `;
402
+ }
403
+
378
404
  if (filePath) {
379
405
  return `
380
406
  <span
@@ -406,7 +432,7 @@ function renderTableSurface(state) {
406
432
  const gridMarkup = renderDataGrid({
407
433
  columns,
408
434
  rows: indexedRows.map(({ row }) => row),
409
- tableClass: 'min-w-full border-collapse text-left font-mono text-xs',
435
+ tableClass: 'data-table min-w-full border-collapse text-left font-mono text-xs',
410
436
  theadClass: 'sticky top-0 z-10 bg-surface-container-highest',
411
437
  tbodyClass: 'divide-y divide-outline-variant/5',
412
438
  getRowClass: (_, rowIndexOnPage) => {
@@ -8,7 +8,7 @@ function renderMissingDatabase() {
8
8
  <div class="view-frame flex min-h-full items-center justify-center">
9
9
  <div class="text-center">
10
10
  <span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
11
- <p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
11
+ <p class="font-body text-xl font-black uppercase tracking-tight text-primary-container">
12
12
  No Active SQLite Database
13
13
  </p>
14
14
  <p class="mt-3 max-w-xl text-sm leading-7 text-on-surface-variant/65">
@@ -294,7 +294,7 @@ function renderEmptyDocumentsState(documents) {
294
294
  return `
295
295
  <div class="documents-empty-state">
296
296
  <span class="material-symbols-outlined">description</span>
297
- <p class="font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
297
+ <p class="font-body text-2xl font-black uppercase tracking-tight text-primary-container">
298
298
  No Documents
299
299
  </p>
300
300
  ${renderNewDocumentDropdown(documents, 'mt-4')}
@@ -309,7 +309,7 @@ function renderDocumentDetail(documents) {
309
309
  ${renderDocumentsTitlebar(documents, { showFilename: false })}
310
310
  <div class="documents-empty-state">
311
311
  <span class="material-symbols-outlined">sync</span>
312
- <p class="font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
312
+ <p class="font-body text-2xl font-black uppercase tracking-tight text-primary-container">
313
313
  Loading Documents
314
314
  </p>
315
315
  </div>
@@ -11,7 +11,7 @@ function renderMissingDatabase() {
11
11
  return `
12
12
  <div class="flex flex-1 flex-col items-center justify-center bg-surface-container-lowest px-8 text-center">
13
13
  <span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
14
- <p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
14
+ <p class="font-body text-xl font-black uppercase tracking-tight text-primary-container">
15
15
  No Active SQLite Database
16
16
  </p>
17
17
  <p class="mt-3 max-w-xl text-sm leading-7 text-on-surface-variant/65">
@@ -60,35 +60,35 @@ function renderPerformancePane(state) {
60
60
  <div class="performance-metrics-grid grid flex-1 gap-4 bg-surface-container-lowest p-6">
61
61
  <div class="metric-card">
62
62
  <span class="text-[10px] font-mono uppercase text-on-surface/40">Exec_Time</span>
63
- <span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
63
+ <span class="font-body text-3xl font-bold text-on-surface">${escapeHtml(
64
64
  formatExecutionTimeMs(metrics.timingMs ?? 0),
65
65
  )}</span>
66
66
  <span class="text-[10px] text-primary-container">Measured backend execution time</span>
67
67
  </div>
68
68
  <div class="metric-card">
69
69
  <span class="text-[10px] font-mono uppercase text-on-surface/40">Statements</span>
70
- <span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
70
+ <span class="font-body text-3xl font-bold text-on-surface">${escapeHtml(
71
71
  formatNumber(metrics.statementCount),
72
72
  )}</span>
73
73
  <span class="text-[10px] text-on-surface/40">Split and executed by SQLite</span>
74
74
  </div>
75
75
  <div class="metric-card">
76
76
  <span class="text-[10px] font-mono uppercase text-on-surface/40">Rows_Returned</span>
77
- <span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
77
+ <span class="font-body text-3xl font-bold text-on-surface">${escapeHtml(
78
78
  formatNumber(metrics.rowCount),
79
79
  )}</span>
80
80
  <span class="text-[10px] text-on-surface/40">Visible result set size</span>
81
81
  </div>
82
82
  <div class="metric-card">
83
83
  <span class="text-[10px] font-mono uppercase text-on-surface/40">Memory_Size</span>
84
- <span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
84
+ <span class="font-body text-3xl font-bold text-on-surface">${escapeHtml(
85
85
  formatBytes(metrics.memoryBytes),
86
86
  )}</span>
87
87
  <span class="text-[10px] text-on-surface/40">Serialized result payload</span>
88
88
  </div>
89
89
  <div class="metric-card metric-card--accent">
90
90
  <span class="text-[10px] font-mono uppercase text-on-surface/40">Rows_Affected</span>
91
- <span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
91
+ <span class="font-body text-3xl font-bold text-on-surface">${escapeHtml(
92
92
  formatNumber(metrics.affectedRowCount),
93
93
  )}</span>
94
94
  <span class="text-[10px] text-primary-container">INSERT / UPDATE / DELETE impact</span>
@@ -301,9 +301,11 @@ function renderCreatedTagList(tags = [], { canRemove = false, removingTagKey = n
301
301
  escapeHtml(tag.key),
302
302
  '" type="button" title="Remove tag" ',
303
303
  canRemove && removingTagKey !== tag.key ? '' : 'disabled',
304
- '><span class="material-symbols-outlined text-[18px]">',
305
- removingTagKey === tag.key ? 'hourglass_top' : 'delete',
306
- '</span></button></article>',
304
+ '><span class="material-symbols-outlined text-sm">',
305
+ 'delete',
306
+ '</span>',
307
+ removingTagKey === tag.key ? 'Removing...' : 'Delete',
308
+ '</button></article>',
307
309
  ].join('');
308
310
  })
309
311
  .join('')}
@@ -319,7 +321,7 @@ function renderPreviewMedia(
319
321
  return `
320
322
  <div class="media-tagging-preview__empty">
321
323
  <span class="material-symbols-outlined text-4xl">photo_size_select_large</span>
322
- <div class="mt-3 font-headline text-lg uppercase tracking-[0.06em] text-primary-container">
324
+ <div class="mt-3 font-body text-lg uppercase tracking-[0.06em] text-primary-container">
323
325
  Queue Empty
324
326
  </div>
325
327
  <div class="mt-2 text-sm text-on-surface-variant/55">
@@ -715,7 +717,7 @@ function renderMappingSection(state) {
715
717
  </div>
716
718
  <div class="media-tagging-card__body media-tagging-card__body--mapping">
717
719
  <article class="media-tagging-mapping-card ${mappingCandidate ? 'is-selected' : ''}">
718
- <div class="font-headline text-sm uppercase tracking-[0.08em] text-primary-container">
720
+ <div class="font-body text-sm uppercase tracking-[0.08em] text-primary-container">
719
721
  ${escapeHtml(MEDIA_TAGGING_DEFAULT_MAPPING_TABLE)}
720
722
  </div>
721
723
  ${
@@ -7,7 +7,7 @@ function renderMissingDatabase() {
7
7
  return `
8
8
  <div class="border border-dashed border-outline-variant/20 bg-surface-container-low px-8 py-12 text-center">
9
9
  <span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
10
- <p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
10
+ <p class="font-body text-xl font-black uppercase tracking-tight text-primary-container">
11
11
  No Active SQLite Database
12
12
  </p>
13
13
  <p class="mx-auto mt-3 max-w-xl text-sm leading-7 text-on-surface-variant/65">
@@ -210,7 +210,7 @@ function renderSchemaMapPreview(overview) {
210
210
  <div class="space-y-5 p-4">
211
211
  <div class="flex flex-col gap-5 xl:flex-row xl:items-end xl:justify-between">
212
212
  <div>
213
- <div class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
213
+ <div class="font-body text-xl font-black uppercase tracking-tight text-primary-container">
214
214
  ${escapeHtml(narrative.title)}
215
215
  </div>
216
216
  <p class="mt-2 max-w-3xl text-sm leading-6 text-on-surface-variant/70">
@@ -237,7 +237,7 @@ function renderSchemaMapPreview(overview) {
237
237
  <div class="text-[9px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
238
238
  ${escapeHtml(stat.label)}
239
239
  </div>
240
- <div class="mt-2 font-headline text-2xl font-black uppercase tracking-tight text-on-surface">
240
+ <div class="mt-2 font-body text-2xl font-black uppercase tracking-tight text-on-surface">
241
241
  ${escapeHtml(stat.value)}
242
242
  </div>
243
243
  </div>
@@ -105,7 +105,7 @@ function renderSettingsContent(state) {
105
105
  if (state.settings.error) {
106
106
  return `
107
107
  <div class="border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
108
- <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
108
+ <div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
109
109
  ${escapeHtml(state.settings.error.code)}
110
110
  </div>
111
111
  <div class="mt-2">${escapeHtml(state.settings.error.message)}</div>
@@ -139,6 +139,7 @@ function renderSettingsContent(state) {
139
139
  type="button"
140
140
  ${tokenSaving ? 'disabled' : ''}
141
141
  >
142
+ <span class="material-symbols-outlined text-sm">delete</span>
142
143
  Delete
143
144
  </button>
144
145
  </div>
@@ -216,7 +217,7 @@ function renderSettingsContent(state) {
216
217
  <div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
217
218
  Current_Version
218
219
  </div>
219
- <div class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
220
+ <div class="mt-2 font-body text-4xl font-black uppercase tracking-tight text-primary-container">
220
221
  v${appVersion}
221
222
  </div>
222
223
  </div>
@@ -224,7 +225,7 @@ function renderSettingsContent(state) {
224
225
  <div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
225
226
  SQLite_Runtime
226
227
  </div>
227
- <div class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
228
+ <div class="mt-2 font-body text-4xl font-black uppercase tracking-tight text-primary-container">
228
229
  ${sqliteVersion}
229
230
  </div>
230
231
  </div>
@@ -1,5 +1,7 @@
1
1
  import { clearInspector, renderDdlSection, renderInspector } from '../components/structureGraph.js';
2
2
  import { renderDropdownButton } from '../components/dropdownButton.js';
3
+ import { renderGenerateTypesDropdown } from '../components/generateTypesDropdown.js';
4
+ import { renderWorkspaceOpenDropdown } from '../components/workspaceOpenDropdown.js';
3
5
  import { escapeHtml, formatNumber } from '../utils/format.js';
4
6
 
5
7
  function renderEntryGroup(title, entries, activeName, options = {}) {
@@ -231,6 +233,30 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
231
233
  </button>
232
234
  </div>
233
235
  <div class="structure-graph__toolbar-actions">
236
+ ${renderWorkspaceOpenDropdown({
237
+ tableName: selectedGraphTable?.name ?? '',
238
+ destinations: [
239
+ {
240
+ icon: 'table_rows',
241
+ key: 'data',
242
+ label: 'Data',
243
+ target: tableName => `/data/${encodeURIComponent(tableName)}`,
244
+ },
245
+ {
246
+ icon: 'table_chart',
247
+ key: 'table-designer',
248
+ label: 'Table Designer',
249
+ target: tableName => `/table-designer/${encodeURIComponent(tableName)}`,
250
+ },
251
+ {
252
+ key: 'sql-editor',
253
+ },
254
+ ],
255
+ })}
256
+ ${renderGenerateTypesDropdown({
257
+ selectedTableName: selectedGraphTable?.name ?? '',
258
+ hasTables: Boolean(graph.tables?.length),
259
+ })}
234
260
  ${renderDropdownButton({
235
261
  icon: 'format_shapes',
236
262
  label: 'Format',
@@ -256,15 +282,6 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
256
282
  },
257
283
  ],
258
284
  })}
259
- <button
260
- class="${toolbarButtonClass}"
261
- data-structure-graph-action="open-data"
262
- disabled
263
- type="button"
264
- >
265
- <span class="material-symbols-outlined text-sm">table_rows</span>
266
- Open Data
267
- </button>
268
285
  </div>
269
286
  <div class="structure-graph__toolbar-inspector">
270
287
  <button
@@ -382,7 +399,7 @@ export function renderStructureView(state) {
382
399
  : state.structure.error
383
400
  ? `
384
401
  <div class="min-h-0 flex-1 border-t border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
385
- <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
402
+ <div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
386
403
  ${escapeHtml(state.structure.error.code)}
387
404
  </div>
388
405
  <div class="mt-2">${escapeHtml(state.structure.error.message)}</div>
@@ -1,6 +1,7 @@
1
1
  import { renderTableDesignerEditor } from "../components/tableDesignerEditor.js";
2
2
  import { renderTableDesignerSidebar } from "../components/tableDesignerSidebar.js";
3
3
  import { renderTableDesignerSqlPreview } from "../components/tableDesignerSqlPreview.js";
4
+ import { renderWorkspaceOpenDropdown } from "../components/workspaceOpenDropdown.js";
4
5
  import { escapeHtml } from "../utils/format.js";
5
6
 
6
7
  function renderRouteError(error) {
@@ -18,6 +19,7 @@ function renderRouteError(error) {
18
19
 
19
20
  function renderWorkspaceToolbar(state) {
20
21
  const tablesVisible = state.tableDesigner.tablesVisible !== false;
22
+ const tableName = state.tableDesigner.selectedTableName ?? state.tableDesigner.draft?.tableName ?? "";
21
23
 
22
24
  return `
23
25
  <div class="table-designer-workspace__toolbar workspace-header">
@@ -33,6 +35,27 @@ function renderWorkspaceToolbar(state) {
33
35
  </button>
34
36
  </div>
35
37
  <div class="table-designer-workspace__toolbar-right">
38
+ ${renderWorkspaceOpenDropdown({
39
+ tableName,
40
+ disabled: !state.tableDesigner.selectedTableName,
41
+ destinations: [
42
+ {
43
+ icon: "table_rows",
44
+ key: "data",
45
+ label: "Data",
46
+ target: name => `/data/${encodeURIComponent(name)}`,
47
+ },
48
+ {
49
+ icon: "account_tree",
50
+ key: "structure",
51
+ label: "Structure",
52
+ target: name => `/structure/${encodeURIComponent(name)}`,
53
+ },
54
+ {
55
+ key: "sql-editor",
56
+ },
57
+ ],
58
+ })}
36
59
  <button
37
60
  class="standard-button"
38
61
  data-action="import-table-designer-csv"
@@ -129,11 +129,6 @@ img {
129
129
  display: none !important;
130
130
  }
131
131
 
132
- .font-headline {
133
- font-family: var(--font-family-headline);
134
- word-break: break-all;
135
- }
136
-
137
132
  .font-body {
138
133
  font-family: var(--font-family-body);
139
134
  }
@@ -159,17 +154,6 @@ img {
159
154
  vertical-align: middle;
160
155
  }
161
156
 
162
- .no-scrollbar {
163
- -ms-overflow-style: none;
164
- scrollbar-width: none;
165
- }
166
-
167
- .no-scrollbar::-webkit-scrollbar {
168
- display: none;
169
- height: 0;
170
- width: 0;
171
- }
172
-
173
157
  .custom-scrollbar {
174
158
  scrollbar-color: var(--color-surface-highest) var(--color-surface-lowest);
175
159
  scrollbar-width: thin;
@@ -193,10 +177,7 @@ img {
193
177
  }
194
178
 
195
179
  .clipped-btn,
196
- .clipped-corner,
197
- .custom-clip,
198
- .clip-button,
199
- .clip-corner {
180
+ .clipped-corner {
200
181
  clip-path: var(--clip-path);
201
182
  }
202
183
 
@@ -274,28 +255,12 @@ img {
274
255
  color: transparent;
275
256
  }
276
257
 
277
- .cursor-blink {
278
- animation: cursor-blink var(--duration-cursor-blink) step-end infinite;
279
- border-right: 2px solid var(--color-primary-container);
280
- }
281
-
282
258
  .text-xs {
283
259
  white-space: nowrap;
284
260
  overflow: hidden;
285
261
  text-overflow: ellipsis;
286
262
  }
287
263
 
288
- @keyframes cursor-blink {
289
- from,
290
- to {
291
- border-color: transparent;
292
- }
293
-
294
- 50% {
295
- border-color: var(--color-primary-container);
296
- }
297
- }
298
-
299
264
  @media (prefers-reduced-motion: reduce) {
300
265
  *,
301
266
  *::before,
@@ -306,8 +271,4 @@ img {
306
271
  transition-duration: 0.001ms;
307
272
  }
308
273
 
309
- .cursor-blink {
310
- animation: none;
311
- border-right-color: var(--color-primary-container);
312
- }
313
274
  }