sqlite-hub 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +15 -10
  2. package/docs/MCP.md +36 -4
  3. package/docs/changelog.md +11 -0
  4. package/docs/guidelines/DESIGN.md +64 -0
  5. package/frontend/assets/mockups/backups_1_1920.webp +0 -0
  6. package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
  7. package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
  8. package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
  9. package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
  10. package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
  11. package/frontend/assets/mockups/charts_1_1920.webp +0 -0
  12. package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
  13. package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
  14. package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
  15. package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
  16. package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
  17. package/frontend/assets/mockups/connections_1_1920.webp +0 -0
  18. package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
  19. package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
  20. package/frontend/assets/mockups/data_1_1920.webp +0 -0
  21. package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
  22. package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
  23. package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
  24. package/frontend/assets/mockups/documents_1_1920.webp +0 -0
  25. package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
  26. package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
  27. package/frontend/assets/mockups/logs_1_1920.webp +0 -0
  28. package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
  29. package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
  30. package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
  31. package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
  32. package/frontend/assets/mockups/overview_1_1920.webp +0 -0
  33. package/frontend/assets/mockups/settings_1_1920.webp +0 -0
  34. package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
  35. package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
  36. package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
  37. package/frontend/assets/mockups/structure_1_1920.webp +0 -0
  38. package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
  39. package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
  40. package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
  41. package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
  42. package/frontend/js/api.js +11 -0
  43. package/frontend/js/app.js +238 -8
  44. package/frontend/js/components/badges.js +16 -0
  45. package/frontend/js/components/connectionCard.js +35 -0
  46. package/frontend/js/components/modal.js +116 -245
  47. package/frontend/js/components/queryHistoryDetail.js +25 -45
  48. package/frontend/js/components/rowEditorPanel.js +7 -60
  49. package/frontend/js/components/structureGraph.js +292 -13
  50. package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
  51. package/frontend/js/components/tableDesignerEditor.js +51 -16
  52. package/frontend/js/components/tableDesignerSidebar.js +9 -5
  53. package/frontend/js/store.js +551 -7
  54. package/frontend/js/utils/connectionRegistry.js +76 -0
  55. package/frontend/js/utils/tableDesigner.js +431 -62
  56. package/frontend/js/views/backups.js +2 -5
  57. package/frontend/js/views/charts.js +20 -10
  58. package/frontend/js/views/connections.js +103 -3
  59. package/frontend/js/views/data.js +19 -5
  60. package/frontend/js/views/settings.js +2 -2
  61. package/frontend/js/views/structure.js +28 -5
  62. package/frontend/js/views/tableAdvisor.js +14 -6
  63. package/frontend/js/views/tableDesigner.js +44 -9
  64. package/frontend/styles/components.css +676 -79
  65. package/frontend/styles/structure-graph.css +49 -0
  66. package/frontend/styles/tailwind.generated.css +1 -1
  67. package/package.json +1 -1
  68. package/server/mcp/httpRouter.js +95 -0
  69. package/server/routes/connections.js +12 -0
  70. package/server/routes/data.js +1 -1
  71. package/server/routes/settings.js +29 -7
  72. package/server/routes/tableDesigner.js +15 -1
  73. package/server/server.js +15 -0
  74. package/server/services/mcpToolService.js +28 -0
  75. package/server/services/sqlite/connectionManager.js +23 -2
  76. package/server/services/sqlite/dataBrowserService.js +21 -0
  77. package/server/services/sqlite/introspection.js +39 -1
  78. package/server/services/sqlite/structureService.js +66 -11
  79. package/server/services/sqlite/tableAdvisor.js +6 -0
  80. package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
  81. package/server/services/sqlite/tableDesigner/sql.js +31 -2
  82. package/server/services/sqlite/tableDesigner/validation.js +69 -9
  83. package/server/services/sqlite/tableDesignerService.js +110 -5
  84. package/server/services/storage/appStateStore.js +303 -4
  85. package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
  86. package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
  87. package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
  88. package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
  89. package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
@@ -18,6 +18,11 @@ import {
18
18
  MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL,
19
19
  } from '../lib/mediaTaggingDefaults.js';
20
20
  import { SYNTHETIC_GENERATOR_TYPES, getSyntheticGeneratorLabel } from '../utils/syntheticData.js';
21
+ import {
22
+ MAX_CONNECTION_TAG_NAME_LENGTH,
23
+ normalizeConnectionTagKey,
24
+ normalizeConnectionTagName,
25
+ } from '../utils/connectionRegistry.js';
21
26
 
22
27
  function renderField({ label, name, type = 'text', placeholder = '', value = '' }) {
23
28
  return `
@@ -192,7 +197,114 @@ export function renderOpenConnectionForm(modal) {
192
197
  `;
193
198
  }
194
199
 
195
- function renderEditConnectionForm(modal) {
200
+ function renderEditConnectionTagEditor(modal, state) {
201
+ const assignedTags = Array.isArray(modal.assignedTags) ? modal.assignedTags : [];
202
+ const assignedKeys = new Set(assignedTags.map(tag => normalizeConnectionTagKey(tag.name)));
203
+ const tagQuery = String(modal.tagQuery ?? '');
204
+ const normalizedQuery = normalizeConnectionTagName(tagQuery);
205
+ const queryKey = normalizeConnectionTagKey(normalizedQuery);
206
+ const matchingTags = (state.connections.tags ?? [])
207
+ .filter(tag => !assignedKeys.has(normalizeConnectionTagKey(tag.name)))
208
+ .filter(tag => {
209
+ if (!queryKey) {
210
+ return true;
211
+ }
212
+
213
+ return normalizeConnectionTagKey(tag.name).includes(queryKey);
214
+ })
215
+ .slice(0, 6);
216
+ const exactMatch = (state.connections.tags ?? []).some(tag => normalizeConnectionTagKey(tag.name) === queryKey);
217
+ const canCreate = Boolean(normalizedQuery && !exactMatch);
218
+ const assignedMarkup = assignedTags.length
219
+ ? assignedTags
220
+ .map(
221
+ tag => `
222
+ <span class="connection-edit-tag-chip">
223
+ <span class="connection-edit-tag-chip__label">${escapeHtml(tag.name)}</span>
224
+ <button
225
+ aria-label="Remove ${escapeHtml(tag.name)} tag"
226
+ class="connection-edit-tag-chip__remove"
227
+ data-action="remove-edit-connection-tag"
228
+ data-tag-name="${escapeHtml(tag.name)}"
229
+ type="button"
230
+ title="Remove tag"
231
+ >
232
+ <span aria-hidden="true">×</span>
233
+ </button>
234
+ <input name="tags" type="hidden" value="${escapeHtml(tag.name)}" />
235
+ </span>
236
+ `,
237
+ )
238
+ .join('')
239
+ : '<div class="connection-edit-tags__empty">NO_TAGS_ASSIGNED</div>';
240
+ const matchMarkup = matchingTags
241
+ .map(
242
+ tag => `
243
+ <button
244
+ class="connection-edit-tag-option"
245
+ data-action="add-edit-connection-tag"
246
+ data-tag-name="${escapeHtml(tag.name)}"
247
+ type="button"
248
+ >
249
+ <span>${escapeHtml(tag.name)}</span>
250
+ <span>${escapeHtml(String(tag.connectionCount ?? 0))}</span>
251
+ </button>
252
+ `,
253
+ )
254
+ .join('');
255
+
256
+ return `
257
+ <div class="connection-edit-tags">
258
+ <div class="connection-edit-tags__label">Tags</div>
259
+ <div class="connection-edit-tags__assigned">
260
+ ${assignedMarkup}
261
+ </div>
262
+ <label class="connection-edit-tags__input-shell">
263
+ <span class="sr-only">Add or create tag</span>
264
+ <input
265
+ autocomplete="off"
266
+ class="control-input connection-edit-tags__input"
267
+ data-bind="edit-connection-tag-query"
268
+ maxlength="${MAX_CONNECTION_TAG_NAME_LENGTH}"
269
+ placeholder="ADD_OR_CREATE_TAG..."
270
+ spellcheck="false"
271
+ type="text"
272
+ value="${escapeHtml(tagQuery)}"
273
+ />
274
+ </label>
275
+ ${
276
+ modal.tagError
277
+ ? `<div class="connection-edit-tags__error">${escapeHtml(modal.tagError)}</div>`
278
+ : ''
279
+ }
280
+ <div class="connection-edit-tag-options">
281
+ ${
282
+ matchMarkup
283
+ ? `<div class="connection-edit-tag-options__section">MATCHING_TAGS</div>${matchMarkup}`
284
+ : ''
285
+ }
286
+ ${
287
+ canCreate
288
+ ? `
289
+ <div class="connection-edit-tag-options__section">CREATE_NEW</div>
290
+ <button
291
+ class="connection-edit-tag-option connection-edit-tag-option--create"
292
+ data-action="create-edit-connection-tag"
293
+ data-edit-connection-tag-primary
294
+ data-tag-name="${escapeHtml(normalizedQuery)}"
295
+ type="button"
296
+ >
297
+ <span>CREATE "${escapeHtml(normalizedQuery)}"</span>
298
+ </button>
299
+ `
300
+ : ''
301
+ }
302
+ </div>
303
+ </div>
304
+ `;
305
+ }
306
+
307
+ function renderEditConnectionForm(modal, state) {
196
308
  const connection = modal.connection ?? {};
197
309
 
198
310
  return `
@@ -246,6 +358,7 @@ function renderEditConnectionForm(modal) {
246
358
  checked: Boolean(connection.readOnly),
247
359
  text: 'Open read-only',
248
360
  })}
361
+ ${renderEditConnectionTagEditor(modal, state)}
249
362
  ${renderError(modal.error)}
250
363
  <div class="flex items-center justify-between gap-3 pt-2">
251
364
  <button
@@ -1926,242 +2039,6 @@ export function renderCopyColumnModal(modal, state) {
1926
2039
  `;
1927
2040
  }
1928
2041
 
1929
- function getTableDesignerUniqueConstraintTypeLabel(constraint) {
1930
- if (constraint.partial) {
1931
- return 'Partial unique index';
1932
- }
1933
-
1934
- if ((constraint.columns?.length ?? 0) > 1) {
1935
- return 'Multi-column unique';
1936
- }
1937
-
1938
- return 'Unique constraint';
1939
- }
1940
-
1941
- function renderTableDesignerUniqueConstraintExpression(constraint) {
1942
- const expression = String(constraint.expression || constraint.sql || '').trim();
1943
-
1944
- if (expression) {
1945
- return expression;
1946
- }
1947
-
1948
- const columns = (constraint.columns ?? []).map(column => column.name).filter(Boolean);
1949
- return columns.length ? `UNIQUE (${columns.join(', ')})` : 'UNIQUE constraint';
1950
- }
1951
-
1952
- function renderTableDesignerCheckConstraintExpression(constraint) {
1953
- return String(constraint.expression || '').trim() || 'CHECK constraint';
1954
- }
1955
-
1956
- function normalizeTableDesignerConstraintColumnName(name) {
1957
- return String(name ?? '')
1958
- .trim()
1959
- .toLowerCase();
1960
- }
1961
-
1962
- function tableDesignerConstraintIncludesColumn(constraint, columnName) {
1963
- const normalizedColumn = normalizeTableDesignerConstraintColumnName(columnName);
1964
-
1965
- if (!normalizedColumn) {
1966
- return false;
1967
- }
1968
-
1969
- return (constraint.columns ?? []).some(
1970
- constraintColumn => normalizeTableDesignerConstraintColumnName(constraintColumn.name) === normalizedColumn,
1971
- );
1972
- }
1973
-
1974
- function renderTableDesignerUniqueConstraintEditor(constraint) {
1975
- const columns = (constraint.columns ?? []).map(column => column.name).filter(Boolean);
1976
-
1977
- return `
1978
- <article class="table-designer-constraint-editor">
1979
- <div class="table-designer-constraint-editor__header">
1980
- <div class="min-w-0">
1981
- <div class="table-designer-constraint-editor__type">
1982
- ${escapeHtml(getTableDesignerUniqueConstraintTypeLabel(constraint))}
1983
- ${constraint.origin ? ` // origin ${escapeHtml(constraint.origin)}` : ''}
1984
- </div>
1985
- <input
1986
- class="table-designer-field table-designer-constraint-editor__name"
1987
- data-bind="table-designer-constraint-field"
1988
- data-constraint-id="${escapeHtml(constraint.id)}"
1989
- data-constraint-kind="unique"
1990
- data-field="name"
1991
- spellcheck="false"
1992
- type="text"
1993
- value="${escapeHtml(constraint.name || 'UNIQUE constraint')}"
1994
- />
1995
- </div>
1996
- <div class="status-badge status-badge--muted">Rebuild</div>
1997
- </div>
1998
- ${
1999
- columns.length
2000
- ? `
2001
- <div class="table-designer-constraint__columns">
2002
- ${columns.map(column => `<span>${escapeHtml(column)}</span>`).join('')}
2003
- </div>
2004
- `
2005
- : ''
2006
- }
2007
- <textarea
2008
- class="table-designer-constraint-editor__sql custom-scrollbar"
2009
- data-bind="table-designer-constraint-field"
2010
- data-constraint-id="${escapeHtml(constraint.id)}"
2011
- data-constraint-kind="unique"
2012
- data-field="expression"
2013
- spellcheck="false"
2014
- >${escapeHtml(renderTableDesignerUniqueConstraintExpression(constraint))}</textarea>
2015
- </article>
2016
- `;
2017
- }
2018
-
2019
- function renderTableDesignerCheckConstraintEditor(constraint) {
2020
- const columns = constraint.columns ?? [];
2021
- const allowedValues = columns.flatMap(column =>
2022
- (column.allowedValues ?? []).map(value => ({
2023
- columnName: column.name,
2024
- value,
2025
- })),
2026
- );
2027
-
2028
- return `
2029
- <article class="table-designer-constraint-editor">
2030
- <div class="table-designer-constraint-editor__header">
2031
- <div class="min-w-0">
2032
- <div class="table-designer-constraint-editor__type">
2033
- ${columns.length ? escapeHtml(columns.map(column => column.name).join(', ')) : 'Table check'}
2034
- </div>
2035
- <input
2036
- class="table-designer-field table-designer-constraint-editor__name"
2037
- data-bind="table-designer-constraint-field"
2038
- data-constraint-id="${escapeHtml(constraint.id)}"
2039
- data-constraint-kind="check"
2040
- data-field="name"
2041
- spellcheck="false"
2042
- type="text"
2043
- value="${escapeHtml(constraint.name || 'CHECK constraint')}"
2044
- />
2045
- </div>
2046
- <div class="status-badge status-badge--muted">Rebuild</div>
2047
- </div>
2048
- ${
2049
- allowedValues.length
2050
- ? `
2051
- <div class="table-designer-constraint__values custom-scrollbar">
2052
- ${allowedValues
2053
- .map(
2054
- entry => `
2055
- <span title="${escapeHtml(entry.columnName)}">${escapeHtml(entry.value)}</span>
2056
- `,
2057
- )
2058
- .join('')}
2059
- </div>
2060
- `
2061
- : ''
2062
- }
2063
- <textarea
2064
- class="table-designer-constraint-editor__sql custom-scrollbar"
2065
- data-bind="table-designer-constraint-field"
2066
- data-constraint-id="${escapeHtml(constraint.id)}"
2067
- data-constraint-kind="check"
2068
- data-field="expression"
2069
- spellcheck="false"
2070
- >${escapeHtml(renderTableDesignerCheckConstraintExpression(constraint))}</textarea>
2071
- </article>
2072
- `;
2073
- }
2074
-
2075
- function renderTableDesignerConstraintSection({ title, count, emptyText, body }) {
2076
- return `
2077
- <section class="table-designer-constraints-modal__section">
2078
- <div class="table-designer-constraints-modal__section-header">
2079
- <div class="table-designer-constraints-modal__section-title">${escapeHtml(title)}</div>
2080
- <div class="status-badge status-badge--muted">${formatNumber(count)}</div>
2081
- </div>
2082
- ${
2083
- count
2084
- ? `<div class="table-designer-constraints__list">${body}</div>`
2085
- : `<div class="table-designer-constraints-modal__empty">${escapeHtml(emptyText)}</div>`
2086
- }
2087
- </section>
2088
- `;
2089
- }
2090
-
2091
- function renderTableDesignerConstraintsModal(modal, state) {
2092
- const draft = state.tableDesigner?.draft;
2093
-
2094
- if (!draft) {
2095
- return `
2096
- <div class="table-designer-constraints-modal__empty">
2097
- No active table designer draft.
2098
- </div>
2099
- <div class="flex items-center justify-between gap-3 pt-2">
2100
- <button class="standard-button" data-action="close-modal" type="button">Close</button>
2101
- </div>
2102
- `;
2103
- }
2104
-
2105
- const selectedColumn = modal.columnId ? (draft.columns ?? []).find(column => column.id === modal.columnId) : null;
2106
- const selectedColumnName = String(modal.columnName ?? selectedColumn?.name ?? '').trim();
2107
- const hasSelectedColumn = Boolean(modal.columnId || selectedColumnName);
2108
- const selectedColumnLabel = selectedColumnName || 'Unnamed column';
2109
- const allUniqueConstraints = draft.mode === 'edit' ? (draft.uniqueConstraints ?? []) : [];
2110
- const allCheckConstraints = draft.mode === 'edit' ? (draft.checkConstraints ?? []) : [];
2111
- const uniqueConstraints = hasSelectedColumn
2112
- ? allUniqueConstraints.filter(constraint =>
2113
- tableDesignerConstraintIncludesColumn(constraint, selectedColumnName),
2114
- )
2115
- : allUniqueConstraints;
2116
- const checkConstraints = hasSelectedColumn
2117
- ? allCheckConstraints.filter(constraint =>
2118
- tableDesignerConstraintIncludesColumn(constraint, selectedColumnName),
2119
- )
2120
- : allCheckConstraints;
2121
- const totalCount = uniqueConstraints.length + checkConstraints.length;
2122
-
2123
- return `
2124
- <div class="table-designer-constraints-modal">
2125
- <div class="table-designer-constraints-modal__summary">
2126
- <div class="min-w-0">
2127
- <div class="table-designer-constraints-modal__label">Table</div>
2128
- <code title="${escapeHtml(draft.tableName ?? '')}">${escapeHtml(draft.tableName ?? '')}</code>
2129
- </div>
2130
- ${
2131
- hasSelectedColumn
2132
- ? `
2133
- <div class="min-w-0">
2134
- <div class="table-designer-constraints-modal__label">Column</div>
2135
- <code title="${escapeHtml(selectedColumnLabel)}">${escapeHtml(selectedColumnLabel)}</code>
2136
- </div>
2137
- `
2138
- : ''
2139
- }
2140
- <div class="status-badge status-badge--primary">V2 · ${formatNumber(totalCount)}</div>
2141
- </div>
2142
- ${renderTableDesignerConstraintSection({
2143
- title: 'Check constraints',
2144
- count: checkConstraints.length,
2145
- emptyText: hasSelectedColumn
2146
- ? 'No check constraints detected for this column.'
2147
- : 'No check constraints detected.',
2148
- body: checkConstraints.map(renderTableDesignerCheckConstraintEditor).join(''),
2149
- })}
2150
- ${renderTableDesignerConstraintSection({
2151
- title: hasSelectedColumn ? 'Related unique constraints' : 'Unique constraints',
2152
- count: uniqueConstraints.length,
2153
- emptyText: hasSelectedColumn
2154
- ? 'No related multi-column or partial unique constraints detected.'
2155
- : 'No multi-column or partial unique constraints detected.',
2156
- body: uniqueConstraints.map(renderTableDesignerUniqueConstraintEditor).join(''),
2157
- })}
2158
- <div class="flex items-center justify-between gap-3 pt-2">
2159
- <button class="standard-button" data-action="close-modal" type="button">Close</button>
2160
- </div>
2161
- </div>
2162
- `;
2163
- }
2164
-
2165
2042
  function renderCreateMediaTaggingMappingTableForm(modal, state) {
2166
2043
  const mappingExists = hasDefaultMediaTaggingMappingTable(state.mediaTagging.schemaTables ?? []);
2167
2044
  const readOnly = Boolean(state.mediaTagging.connection?.readOnly);
@@ -2346,7 +2223,7 @@ export function renderModal(state) {
2346
2223
  'edit-connection': {
2347
2224
  eyebrow: 'Registry // Update saved SQLite target',
2348
2225
  title: 'Edit Connection',
2349
- body: renderEditConnectionForm(modal),
2226
+ body: renderEditConnectionForm(modal, state),
2350
2227
  },
2351
2228
  'delete-row': {
2352
2229
  eyebrow: 'Mutation // Confirm row deletion',
@@ -2408,11 +2285,6 @@ export function renderModal(state) {
2408
2285
  title: isMarkdownTodoCopyColumnMode(modal.copyMode) ? 'Export Markdown Todo' : 'Copy column',
2409
2286
  body: renderCopyColumnModal(modal, state),
2410
2287
  },
2411
- 'table-designer-constraints': {
2412
- eyebrow: 'Table Designer // Constraints',
2413
- title: 'Checks',
2414
- body: renderTableDesignerConstraintsModal(modal, state),
2415
- },
2416
2288
  'create-media-tagging-tag-table': {
2417
2289
  eyebrow: 'Media Tagging // Create default tag table',
2418
2290
  title: 'Create Tag Table',
@@ -2442,8 +2314,7 @@ export function renderModal(state) {
2442
2314
  modal.kind === 'chart-editor' ||
2443
2315
  modal.kind === 'document-insert-table' ||
2444
2316
  modal.kind === 'document-insert-note' ||
2445
- modal.kind === 'row-update-preview' ||
2446
- modal.kind === 'table-designer-constraints'
2317
+ modal.kind === 'row-update-preview'
2447
2318
  ? 'max-w-3xl'
2448
2319
  : modal.kind === 'generate-types' || modal.kind === 'generate-data'
2449
2320
  ? 'max-w-6xl'
@@ -23,42 +23,14 @@ function canOpenQueryHistoryInCharts(item) {
23
23
  return Boolean(item?.chartsEligible);
24
24
  }
25
25
 
26
- function getQueryTypeTone(queryType) {
27
- if (queryType === "select" || queryType === "update") {
28
- return "success";
29
- }
30
-
31
- if (queryType === "pragma") {
32
- return "primary";
33
- }
34
-
35
- return "muted";
36
- }
37
-
38
- function getExecutionSourceTone(source) {
39
- if (source === "api") {
40
- return "primary";
41
- }
42
-
43
- if (source === "cli") {
44
- return "warning";
45
- }
46
-
47
- if (source === "mcp") {
48
- return "muted";
49
- }
50
-
51
- return "success";
52
- }
53
-
54
26
  function renderRunItem(run) {
55
27
  const executedBy = String(run.executedBy ?? "user").trim().toLowerCase() || "user";
56
28
 
57
29
  return `
58
30
  <div class="border border-outline-variant/10 bg-surface-container px-3 py-3">
59
31
  <div class="flex flex-wrap items-center gap-2">
60
- ${renderStatusBadge(run.status, run.status === "error" ? "alert" : "success")}
61
- ${renderStatusBadge(executedBy, getExecutionSourceTone(executedBy))}
32
+ ${renderStatusBadge(run.status, "muted")}
33
+ ${renderStatusBadge(executedBy, "muted")}
62
34
  <span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
63
35
  ${escapeHtml(formatCompactDateTime(run.executedAt))}
64
36
  </span>
@@ -100,9 +72,14 @@ export function renderQueryHistoryDetail({
100
72
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
101
73
  <div class="border-b border-outline-variant/10 px-5 py-4">
102
74
  <div class="flex items-center justify-between gap-3">
103
- <span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
104
- Query Detail
105
- </span>
75
+ <div class="min-w-0">
76
+ <div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
77
+ SQL Editor // Query History
78
+ </div>
79
+ <h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
80
+ Loading
81
+ </h2>
82
+ </div>
106
83
  <button
107
84
  class="query-history-icon-button"
108
85
  data-action="clear-query-history-selection"
@@ -124,9 +101,14 @@ export function renderQueryHistoryDetail({
124
101
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
125
102
  <div class="border-b border-outline-variant/10 px-5 py-4">
126
103
  <div class="flex items-center justify-between gap-3">
127
- <span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
128
- Query Detail
129
- </span>
104
+ <div class="min-w-0">
105
+ <div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
106
+ SQL Editor // Query History
107
+ </div>
108
+ <h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
109
+ Error
110
+ </h2>
111
+ </div>
130
112
  <button
131
113
  class="query-history-icon-button"
132
114
  data-action="clear-query-history-selection"
@@ -153,12 +135,10 @@ export function renderQueryHistoryDetail({
153
135
  ].join("")
154
136
  : "";
155
137
  const statusMarkup = [
156
- renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
157
- item.isSaved ? renderStatusBadge("saved", "primary") : "",
158
- item.isDestructive ? renderStatusBadge("destructive", "warning") : "",
159
- item.lastRun
160
- ? renderStatusBadge(item.lastRun.status, item.lastRun.status === "error" ? "alert" : "success")
161
- : "",
138
+ renderStatusBadge(item.queryType, "muted"),
139
+ item.isSaved ? renderStatusBadge("saved", "muted") : "",
140
+ item.isDestructive ? renderStatusBadge("destructive", "muted") : "",
141
+ item.lastRun ? renderStatusBadge(item.lastRun.status, "muted") : "",
162
142
  ].join("");
163
143
  const metaMarkup = [
164
144
  renderDetailMetaItem("Last Used", formatDateTime(item.lastUsedAt)),
@@ -176,9 +156,9 @@ export function renderQueryHistoryDetail({
176
156
  return [
177
157
  '<section class="flex h-full min-h-0 flex-col bg-surface-low">',
178
158
  '<div class="border-b border-outline-variant/10 px-5 py-4">',
179
- '<div class="flex items-center justify-between gap-3"><div>',
180
- '<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">Query Detail</div>',
181
- '<h2 class="mt-1 font-body text-lg font-black uppercase tracking-tight text-on-surface">',
159
+ '<div class="flex items-center justify-between gap-3"><div class="min-w-0">',
160
+ '<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">SQL Editor // Query History</div>',
161
+ '<h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">',
182
162
  escapeHtml(item.displayTitle),
183
163
  "</h2></div>",
184
164
  '<button class="query-history-icon-button" data-action="clear-query-history-selection" type="button"><span class="material-symbols-outlined text-[18px]">close</span></button>',
@@ -215,7 +215,7 @@ function renderTextCharacterCountBadge(value) {
215
215
 
216
216
  return `
217
217
  <span
218
- class="border px-2 py-1 text-[9px] ${getFieldBadgeClassName("char-count")}"
218
+ class="status-badge status-badge--muted"
219
219
  data-row-editor-char-count
220
220
  ${count === null ? "hidden" : ""}
221
221
  >
@@ -337,22 +337,10 @@ function renderReadonlyField(field = {}, tableMeta = {}) {
337
337
  `;
338
338
  }
339
339
 
340
- function getValueStateBadgeClassName(state) {
341
- if (state === "null") {
342
- return "border-primary-container/35 bg-primary-container/15 text-primary-container";
343
- }
344
-
345
- if (state === "empty") {
346
- return "border-outline-variant/35 bg-surface-container-high text-on-surface-variant";
347
- }
348
-
349
- return "border-outline-variant/20 bg-surface-container text-on-surface-variant";
350
- }
351
-
352
340
  function renderValueStateBadge(state) {
353
341
  return `
354
342
  <span
355
- class="border px-2 py-1 text-[9px] ${getValueStateBadgeClassName(state)}"
343
+ class="status-badge status-badge--muted"
356
344
  data-row-editor-value-state
357
345
  data-value-state="${escapeHtml(state)}"
358
346
  >${escapeHtml(getRowEditorValueStateLabel(state))}</span>
@@ -447,44 +435,11 @@ function renderEditableField(field, tableMeta = {}) {
447
435
  `;
448
436
  }
449
437
 
450
- function getFieldBadgeClassName(tone) {
451
- if (tone === "primary-key") {
452
- return "border-primary-container/35 bg-primary-container/15 text-primary-container";
453
- }
454
-
455
- if (tone === "foreign-key") {
456
- return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
457
- }
458
-
459
- if (tone === "url") {
460
- return "border-primary-container/35 bg-primary-container/15 text-primary-container";
461
- }
462
-
463
- if (tone === "email") {
464
- return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
465
- }
466
-
467
- if (tone === "check") {
468
- return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
469
- }
470
-
471
- if (tone === "filepath") {
472
- return "border-outline-variant/30 bg-surface-container-high text-on-surface";
473
- }
474
-
475
- if (tone === "char-count") {
476
- return "border-outline-variant/25 bg-surface-container text-on-surface-variant";
477
- }
478
-
479
- return "border-outline-variant/20 bg-surface-container text-on-surface-variant";
480
- }
481
-
482
438
  function renderFieldBadge(badge) {
483
439
  const label = typeof badge === "object" ? badge.label : badge;
484
- const tone = typeof badge === "object" ? badge.tone : "";
485
440
 
486
441
  return `
487
- <span class="border px-2 py-1 text-[9px] ${getFieldBadgeClassName(tone)}">
442
+ <span class="status-badge status-badge--muted">
488
443
  ${escapeHtml(label)}
489
444
  </span>
490
445
  `;
@@ -578,27 +533,19 @@ export function renderRowEditorPanel({
578
533
  ]
579
534
  .filter(Boolean)
580
535
  .join("");
536
+ const eyebrow = [sectionLabel, subtitle].filter(Boolean).join(" // ");
581
537
 
582
538
  return `
583
539
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
584
540
  <header class="border-b border-outline-variant/10 bg-surface-container px-6 py-5">
585
541
  <div class="flex items-start justify-between gap-4">
586
542
  <div class="min-w-0 flex-1">
587
- <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
588
- ${escapeHtml(sectionLabel)}
543
+ <div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
544
+ ${escapeHtml(eyebrow)}
589
545
  </div>
590
- <h2 class="mt-2 font-body text-3xl font-black uppercase tracking-tight text-primary-container">
546
+ <h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
591
547
  ${escapeHtml(title)}
592
548
  </h2>
593
- ${
594
- subtitle
595
- ? `
596
- <div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
597
- ${escapeHtml(subtitle)}
598
- </div>
599
- `
600
- : ""
601
- }
602
549
  </div>
603
550
  <button
604
551
  aria-label="Close panel"