sqlite-hub 0.12.0 → 0.16.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 (46) hide show
  1. package/README.md +12 -4
  2. package/frontend/js/api.js +4 -2
  3. package/frontend/js/app.js +632 -12
  4. package/frontend/js/components/modal.js +432 -3
  5. package/frontend/js/components/queryEditor.js +9 -1
  6. package/frontend/js/components/queryResults.js +79 -17
  7. package/frontend/js/components/rowEditorPanel.js +204 -10
  8. package/frontend/js/components/sidebar.js +101 -18
  9. package/frontend/js/components/tableDesignerEditor.js +69 -11
  10. package/frontend/js/store.js +227 -9
  11. package/frontend/js/utils/copyColumnExport.js +117 -0
  12. package/frontend/js/utils/exportFilenames.js +32 -0
  13. package/frontend/js/utils/filePathPreview.js +315 -0
  14. package/frontend/js/utils/format.js +1 -1
  15. package/frontend/js/utils/rowEditorJson.js +65 -0
  16. package/frontend/js/utils/sqlFormatter.js +691 -0
  17. package/frontend/js/utils/tableDesigner.js +178 -6
  18. package/frontend/js/utils/textCellStats.js +20 -0
  19. package/frontend/js/utils/timestampPreview.js +264 -0
  20. package/frontend/js/views/charts.js +3 -1
  21. package/frontend/js/views/data.js +34 -2
  22. package/frontend/js/views/editor.js +48 -1
  23. package/frontend/js/views/settings.js +0 -3
  24. package/frontend/js/views/structure.js +154 -212
  25. package/frontend/styles/base.css +6 -0
  26. package/frontend/styles/components.css +463 -2
  27. package/frontend/styles/structure-graph.css +0 -3
  28. package/frontend/styles/tailwind.generated.css +1 -1
  29. package/frontend/styles/tokens.css +1 -1
  30. package/package.json +2 -3
  31. package/server/services/sqlite/introspection.js +10 -0
  32. package/server/services/sqlite/sqlExecutor.js +29 -0
  33. package/server/services/sqlite/tableDesigner/changeAnalysis.js +25 -1
  34. package/server/services/sqlite/tableDesigner/schemaMapping.js +105 -10
  35. package/server/services/sqlite/tableDesigner/validation.js +60 -2
  36. package/server/services/sqlite/tableDesignerService.js +1 -1
  37. package/tests/check-constraint-options.test.js +14 -0
  38. package/tests/export-filenames.test.js +34 -0
  39. package/tests/file-path-preview.test.js +165 -0
  40. package/tests/row-editor-json.test.js +82 -0
  41. package/tests/row-editor-timestamp-preview.test.js +192 -0
  42. package/tests/sql-formatter.test.js +173 -0
  43. package/tests/sql-highlight.test.js +38 -0
  44. package/tests/table-designer-v2-unique-constraints.test.js +78 -0
  45. package/tests/text-cell-stats.test.js +38 -0
  46. package/fill.js +0 -526
@@ -143,6 +143,35 @@ function getUniqueResultColumns(columns = []) {
143
143
  return uniqueColumns;
144
144
  }
145
145
 
146
+ function getEditorColumnTypeBadge(column) {
147
+ if (column.sourceColumn === 'rowid') {
148
+ return 'ROWID';
149
+ }
150
+
151
+ return String(column.declaredType || column.affinity || 'BLOB')
152
+ .trim()
153
+ .toUpperCase();
154
+ }
155
+
156
+ function getEditorColumnBadges(column) {
157
+ const badges = [{ label: getEditorColumnTypeBadge(column), tone: 'type' }];
158
+
159
+ if (Number(column.primaryKeyPosition ?? 0) > 0) {
160
+ badges.push({
161
+ label: Number(column.primaryKeyPosition) > 1 ? `PK ${column.primaryKeyPosition}` : 'PK',
162
+ tone: 'primary-key',
163
+ });
164
+ } else if (column.identity && column.sourceColumn === 'rowid') {
165
+ badges.push({ label: 'ROWID', tone: 'primary-key' });
166
+ }
167
+
168
+ if (column.foreignKey) {
169
+ badges.push({ label: 'FK', tone: 'foreign-key' });
170
+ }
171
+
172
+ return badges;
173
+ }
174
+
146
175
  function renderEditorRowPanel(state) {
147
176
  const result = state.editor.result;
148
177
  const rowIndex = state.editor.selectedRowIndex;
@@ -153,6 +182,15 @@ function renderEditorRowPanel(state) {
153
182
  }
154
183
 
155
184
  const uniqueColumns = getUniqueResultColumns(result.editing?.columns ?? []);
185
+ const tableMeta =
186
+ result.editing?.tableMeta ?? {
187
+ columns: uniqueColumns.map(column => ({
188
+ name: column.sourceColumn,
189
+ primaryKeyPosition: Number(column.primaryKeyPosition ?? 0),
190
+ foreignKey: Boolean(column.foreignKey),
191
+ })),
192
+ foreignKeys: [],
193
+ };
156
194
  const editableColumns = uniqueColumns.filter(column => {
157
195
  if (column.identity || column.generated || !column.visible) {
158
196
  return false;
@@ -193,6 +231,7 @@ function renderEditorRowPanel(state) {
193
231
  return {
194
232
  name: column.sourceColumn,
195
233
  label: column.sourceColumn,
234
+ badges: getEditorColumnBadges(column),
196
235
  allowedValues: column.allowedValues ?? [],
197
236
  notNull: Boolean(column.notNull),
198
237
  value: value === null || value === undefined ? '' : String(value),
@@ -200,15 +239,21 @@ function renderEditorRowPanel(state) {
200
239
  }),
201
240
  readonlyFields: readonlyColumns.map(column => ({
202
241
  name: column.sourceColumn,
203
- label: column.sourceColumn,
242
+ label: {
243
+ label: column.sourceColumn,
244
+ badges: getEditorColumnBadges(column),
245
+ },
246
+ rawValue: row[column.resultName],
204
247
  value: formatCellValue(row[column.resultName]),
205
248
  })),
249
+ tableMeta,
206
250
  saveError: state.editor.saveError,
207
251
  saving: state.editor.saving,
208
252
  deleting: state.editor.deleting,
209
253
  deleteAction: 'delete-editor-row',
210
254
  deleteRowIndex: rowIndex,
211
255
  deleteEnabled: editingState.enabled && Boolean(row.__identity),
256
+ jsonActionsEnabled: true,
212
257
  });
213
258
  }
214
259
 
@@ -233,6 +278,8 @@ function renderResultsSurface(state, isResultsRoute) {
233
278
  editable: editingState.enabled,
234
279
  sortColumn: state.editor.resultSortColumn,
235
280
  sortDirection: state.editor.resultSortDirection,
281
+ resultScope: 'editor',
282
+ sortAction: 'sort-editor-results-column',
236
283
  })
237
284
  : renderMissingDatabase();
238
285
  }
@@ -39,9 +39,6 @@ function renderSettingsContent(state) {
39
39
  <div class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
40
40
  v${escapeHtml(state.settings.appVersion ?? '0.0.0')}
41
41
  </div>
42
- <div class="mt-2 text-sm text-on-surface-variant/70">
43
- Read directly from <code>package.json</code>.
44
- </div>
45
42
  </div>
46
43
  </div>
47
44
  </section>
@@ -1,61 +1,57 @@
1
- import { clearInspector, renderDdlSection, renderInspector } from "../components/structureGraph.js";
2
- import { escapeHtml, formatNumber } from "../utils/format.js";
1
+ import { clearInspector, renderDdlSection, renderInspector } from '../components/structureGraph.js';
2
+ import { escapeHtml, formatNumber } from '../utils/format.js';
3
3
 
4
4
  function renderEntryGroup(title, entries, activeName, options = {}) {
5
- const { compact = false, showMeta = true, emptyMessage = null } = options;
6
- const entriesMarkup = entries.length
7
- ? [
8
- '<div class="space-y-2">',
9
- entries
10
- .map((entry) => {
11
- const isActive = entry.name === activeName;
12
- const entryAttributes =
13
- entry.type === "table"
14
- ? [
15
- 'data-structure-entry-name="',
16
- escapeHtml(entry.name),
17
- '"',
18
- ].join("")
19
- : "";
20
- const metaMarkup = showMeta
21
- ? [
22
- '<div class="mt-1 text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">',
23
- escapeHtml(entry.tableName || entry.type),
24
- "</div>",
25
- ].join("")
26
- : "";
5
+ const { compact = false, showMeta = true, emptyMessage = null } = options;
6
+ const entriesMarkup = entries.length
7
+ ? [
8
+ '<div class="space-y-2">',
9
+ entries
10
+ .map(entry => {
11
+ const isActive = entry.name === activeName;
12
+ const entryAttributes =
13
+ entry.type === 'table'
14
+ ? ['data-structure-entry-name="', escapeHtml(entry.name), '"'].join('')
15
+ : '';
16
+ const metaMarkup = showMeta
17
+ ? [
18
+ '<div class="mt-1 text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">',
19
+ escapeHtml(entry.tableName || entry.type),
20
+ '</div>',
21
+ ].join('')
22
+ : '';
27
23
 
28
- return [
29
- '<button class="w-full border px-3 ',
30
- compact ? "py-2.5" : "py-3",
31
- " text-left transition-colors ",
32
- isActive
33
- ? "border-primary-container/30 bg-surface-container-high"
34
- : "border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high",
35
- '" data-action="select-structure-entry" data-entry-name="',
36
- escapeHtml(entry.name),
37
- '" ',
38
- entryAttributes,
39
- ' type="button">',
40
- '<div class="font-mono text-xs ',
41
- isActive ? "text-primary-container" : "text-on-surface",
42
- '">',
43
- escapeHtml(entry.name),
44
- "</div>",
45
- metaMarkup,
46
- "</button>",
47
- ].join("");
48
- })
49
- .join(""),
50
- "</div>",
51
- ].join("")
52
- : [
53
- '<div class="text-sm text-on-surface-variant/45">No ',
54
- emptyMessage ? escapeHtml(emptyMessage) : `${escapeHtml(title.toLowerCase())} found.`,
55
- "</div>",
56
- ].join("");
24
+ return [
25
+ '<button class="w-full border px-3 ',
26
+ compact ? 'py-2.5' : 'py-3',
27
+ ' text-left transition-colors ',
28
+ isActive
29
+ ? 'border-primary-container/30 bg-surface-container-high'
30
+ : 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high',
31
+ '" data-action="select-structure-entry" data-entry-name="',
32
+ escapeHtml(entry.name),
33
+ '" ',
34
+ entryAttributes,
35
+ ' type="button">',
36
+ '<div class="font-mono text-xs ',
37
+ isActive ? 'text-primary-container' : 'text-on-surface',
38
+ '">',
39
+ escapeHtml(entry.name),
40
+ '</div>',
41
+ metaMarkup,
42
+ '</button>',
43
+ ].join('');
44
+ })
45
+ .join(''),
46
+ '</div>',
47
+ ].join('')
48
+ : [
49
+ '<div class="text-sm text-on-surface-variant/45">No ',
50
+ emptyMessage ? escapeHtml(emptyMessage) : `${escapeHtml(title.toLowerCase())} found.`,
51
+ '</div>',
52
+ ].join('');
57
53
 
58
- return `
54
+ return `
59
55
  <section class="structure-sidebar__section">
60
56
  <div class="mb-4 shrink-0 text-[10px] font-bold uppercase tracking-[0.25em] text-primary-container">
61
57
  ${escapeHtml(title)}
@@ -66,19 +62,19 @@ function renderEntryGroup(title, entries, activeName, options = {}) {
66
62
  }
67
63
 
68
64
  function getFilteredTables(tables, searchQuery) {
69
- const normalizedSearch = String(searchQuery ?? "")
70
- .trim()
71
- .toLowerCase();
65
+ const normalizedSearch = String(searchQuery ?? '')
66
+ .trim()
67
+ .toLowerCase();
72
68
 
73
- if (!normalizedSearch) {
74
- return tables;
75
- }
69
+ if (!normalizedSearch) {
70
+ return tables;
71
+ }
76
72
 
77
- return tables.filter((table) => table.name.toLowerCase().includes(normalizedSearch));
73
+ return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
78
74
  }
79
75
 
80
76
  function renderStructureTableSearch(state) {
81
- return `
77
+ return `
82
78
  <label class="table-designer-sidebar__search">
83
79
  <span class="material-symbols-outlined text-sm text-on-surface-variant/55">search</span>
84
80
  <input
@@ -87,14 +83,14 @@ function renderStructureTableSearch(state) {
87
83
  placeholder="Search tables..."
88
84
  spellcheck="false"
89
85
  type="search"
90
- value="${escapeHtml(state.structure.tableSearchQuery ?? "")}"
86
+ value="${escapeHtml(state.structure.tableSearchQuery ?? '')}"
91
87
  />
92
88
  </label>
93
89
  `;
94
90
  }
95
91
 
96
92
  function renderLoadingInspector() {
97
- return `
93
+ return `
98
94
  <div class="structure-graph__panel is-empty">
99
95
  <span class="material-symbols-outlined structure-graph__empty-icon">progress_activity</span>
100
96
  <div class="structure-graph__title">Loading</div>
@@ -106,31 +102,31 @@ function renderLoadingInspector() {
106
102
  }
107
103
 
108
104
  function renderObjectInspector(detail) {
109
- if (!detail) {
110
- return clearInspector();
111
- }
105
+ if (!detail) {
106
+ return clearInspector();
107
+ }
112
108
 
113
- const visibleColumns = (detail.columns ?? []).filter((column) => column.visible !== false);
114
- const foreignKeyLinks = (detail.foreignKeys ?? []).reduce(
115
- (count, foreignKey) => count + (foreignKey.mappings?.length ?? 0),
116
- 0
117
- );
109
+ const visibleColumns = (detail.columns ?? []).filter(column => column.visible !== false);
110
+ const foreignKeyLinks = (detail.foreignKeys ?? []).reduce(
111
+ (count, foreignKey) => count + (foreignKey.mappings?.length ?? 0),
112
+ 0,
113
+ );
118
114
 
119
- return `
115
+ return `
120
116
  <div class="structure-graph__panel">
121
117
  <div class="space-y-3">
122
118
  <div class="structure-graph__eyebrow">Object Inspector</div>
123
119
  <div class="structure-graph__title">${escapeHtml(detail.name)}</div>
124
120
  <div class="flex flex-wrap items-center gap-3">
125
- <div class="structure-graph__subtitle">${escapeHtml(detail.type ?? "object")}</div>
121
+ <div class="structure-graph__subtitle">${escapeHtml(detail.type ?? 'object')}</div>
126
122
  ${
127
- detail.tableName
128
- ? `
123
+ detail.tableName
124
+ ? `
129
125
  <div class="structure-graph__subtitle">
130
126
  TABLE ${escapeHtml(detail.tableName)}
131
127
  </div>
132
128
  `
133
- : ""
129
+ : ''
134
130
  }
135
131
  </div>
136
132
  </div>
@@ -138,70 +134,60 @@ function renderObjectInspector(detail) {
138
134
  <div class="structure-graph__summary">
139
135
  <div class="structure-graph__summary-card">
140
136
  <div class="structure-graph__summary-label">Columns</div>
141
- <div class="structure-graph__summary-value">${escapeHtml(
142
- formatNumber(visibleColumns.length)
143
- )}</div>
137
+ <div class="structure-graph__summary-value">${escapeHtml(formatNumber(visibleColumns.length))}</div>
144
138
  </div>
145
139
  <div class="structure-graph__summary-card">
146
140
  <div class="structure-graph__summary-label">FK Links</div>
147
- <div class="structure-graph__summary-value">${escapeHtml(
148
- formatNumber(foreignKeyLinks)
149
- )}</div>
141
+ <div class="structure-graph__summary-value">${escapeHtml(formatNumber(foreignKeyLinks))}</div>
150
142
  </div>
151
143
  <div class="structure-graph__summary-card">
152
144
  <div class="structure-graph__summary-label">Indexes</div>
153
- <div class="structure-graph__summary-value">${escapeHtml(
154
- formatNumber(detail.indexes?.length ?? 0)
155
- )}</div>
145
+ <div class="structure-graph__summary-value">${escapeHtml(formatNumber(detail.indexes?.length ?? 0))}</div>
156
146
  </div>
157
147
  <div class="structure-graph__summary-card">
158
148
  <div class="structure-graph__summary-label">Triggers</div>
159
- <div class="structure-graph__summary-value">${escapeHtml(
160
- formatNumber(detail.triggers?.length ?? 0)
161
- )}</div>
149
+ <div class="structure-graph__summary-value">${escapeHtml(formatNumber(detail.triggers?.length ?? 0))}</div>
162
150
  </div>
163
151
  </div>
164
152
 
165
153
  ${
166
- visibleColumns.length
167
- ? `
154
+ visibleColumns.length
155
+ ? `
168
156
  <section class="structure-graph__section">
169
157
  <div class="structure-graph__section-title">Columns</div>
170
158
  <div class="structure-graph__column-list">
171
159
  ${visibleColumns
172
- .map(
173
- (column) => `
160
+ .map(
161
+ column => `
174
162
  <div class="structure-graph__column-row">
175
163
  <div class="min-w-0 space-y-1">
176
- <div class="structure-graph__column-name">${escapeHtml(
177
- column.name
178
- )}</div>
164
+ <div class="structure-graph__column-name">${escapeHtml(column.name)}</div>
179
165
  <div class="structure-graph__column-type">${escapeHtml(
180
- column.declaredType || column.affinity || "BLOB"
166
+ column.declaredType || column.affinity || 'BLOB',
181
167
  )}</div>
182
168
  </div>
183
169
  <div class="structure-graph__column-flags">
184
170
  ${
185
- column.primaryKeyPosition > 0
186
- ? `<span class="structure-graph__flag is-key">PK${
187
- column.primaryKeyPosition > 1
188
- ? ` ${escapeHtml(String(column.primaryKeyPosition))}`
189
- : ""
190
- }</span>`
191
- : ""
171
+ column.primaryKeyPosition > 0
172
+ ? `<span class="structure-graph__flag is-key">PK${
173
+ column.primaryKeyPosition > 1
174
+ ? ` ${escapeHtml(String(column.primaryKeyPosition))}`
175
+ : ''
176
+ }</span>`
177
+ : ''
192
178
  }
193
179
  <span class="structure-graph__flag is-nullable">${
194
- column.notNull ? "NOT NULL" : "NULLABLE"
180
+ column.notNull ? 'NOT NULL' : 'NULLABLE'
195
181
  }</span>
196
182
  </div>
197
183
  </div>
198
- `
199
- )
200
- .join("")}
184
+ `,
185
+ )
186
+ .join('')}
201
187
  </div>
202
188
  </section>
203
189
  `
204
- : ""
190
+ : ''
205
191
  }
206
192
 
207
193
  ${renderDdlSection(detail.ddl)}
@@ -210,19 +196,19 @@ function renderObjectInspector(detail) {
210
196
  }
211
197
 
212
198
  function renderGraphSurface(structure, selectedName, detail, detailLoading, tablesVisible = true) {
213
- const graph = structure?.graph ?? { tables: [], relationshipCount: 0 };
214
- const selectedGraphTable =
215
- graph.tables?.find((table) => table.name === selectedName && table.type === "table") ?? null;
216
- const toolbarButtonClass = "standard-button";
217
- const inspectorMarkup = selectedGraphTable
218
- ? renderInspector(selectedGraphTable)
219
- : detailLoading
220
- ? renderLoadingInspector()
221
- : detail
222
- ? renderObjectInspector(detail)
223
- : clearInspector();
199
+ const graph = structure?.graph ?? { tables: [], relationshipCount: 0 };
200
+ const selectedGraphTable =
201
+ graph.tables?.find(table => table.name === selectedName && table.type === 'table') ?? null;
202
+ const toolbarButtonClass = 'standard-button';
203
+ const inspectorMarkup = selectedGraphTable
204
+ ? renderInspector(selectedGraphTable)
205
+ : detailLoading
206
+ ? renderLoadingInspector()
207
+ : detail
208
+ ? renderObjectInspector(detail)
209
+ : clearInspector();
224
210
 
225
- return `
211
+ return `
226
212
  <section class="structure-graph" data-structure-graph-root>
227
213
  <div class="structure-graph__toolbar">
228
214
  <div class="structure-graph__toolbar-main">
@@ -231,10 +217,8 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
231
217
  data-action="toggle-structure-tables"
232
218
  type="button"
233
219
  >
234
- <span class="material-symbols-outlined text-sm">${
235
- tablesVisible ? "visibility_off" : "visibility"
236
- }</span>
237
- ${tablesVisible ? "Hide Tables" : "Show Tables"}
220
+ <span class="material-symbols-outlined text-sm">${tablesVisible ? 'visibility_off' : 'visibility'}</span>
221
+ ${tablesVisible ? 'Hide Tables' : 'Show Tables'}
238
222
  </button>
239
223
  </div>
240
224
  <div class="structure-graph__toolbar-actions">
@@ -288,7 +272,7 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
288
272
  <div
289
273
  class="structure-graph__empty"
290
274
  data-structure-graph-empty
291
- ${graph.tables?.length ? "hidden" : ""}
275
+ ${graph.tables?.length ? 'hidden' : ''}
292
276
  >
293
277
  <span class="material-symbols-outlined structure-graph__empty-icon">account_tree</span>
294
278
  <div class="structure-graph__title">No Tables</div>
@@ -309,46 +293,20 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
309
293
  `;
310
294
  }
311
295
 
312
- function renderStructureWorkspaceHeader(structure, selectedName) {
313
- const tableCount = structure?.grouped?.tables?.length ?? 0;
314
- const viewCount = structure?.grouped?.views?.length ?? 0;
315
-
316
- return `
317
- <header class="structure-view__header">
318
- <div class="structure-headline-container">
319
- <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
320
- Schema Graph
321
- </div>
322
- <h1 class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
323
- Structure
324
- </h1>
325
- <div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
326
- ${
327
- selectedName
328
- ? `selected ${escapeHtml(selectedName)} // tables ${escapeHtml(formatNumber(tableCount))} // views ${escapeHtml(formatNumber(viewCount))}`
329
- : `tables ${escapeHtml(formatNumber(tableCount))} // views ${escapeHtml(formatNumber(viewCount))}`
330
- }
331
- </div>
332
- </div>
333
- </header>
334
- `;
335
- }
336
-
337
296
  export function renderStructureView(state) {
338
- const structure = state.structure.data;
339
- const filteredTables = structure
340
- ? getFilteredTables(structure.grouped.tables ?? [], state.structure.tableSearchQuery)
341
- : [];
342
- const detail =
343
- state.structure.detail?.name === state.structure.selectedName ? state.structure.detail : null;
344
- const tablesVisible = state.structure.tablesVisible !== false;
297
+ const structure = state.structure.data;
298
+ const filteredTables = structure
299
+ ? getFilteredTables(structure.grouped.tables ?? [], state.structure.tableSearchQuery)
300
+ : [];
301
+ const detail = state.structure.detail?.name === state.structure.selectedName ? state.structure.detail : null;
302
+ const tablesVisible = state.structure.tablesVisible !== false;
345
303
 
346
- return {
347
- main: `
304
+ return {
305
+ main: `
348
306
  <section class="view-surface structure-view">
349
307
  ${
350
- tablesVisible
351
- ? `
308
+ tablesVisible
309
+ ? `
352
310
  <aside class="structure-view__sidebar">
353
311
  <div class="structure-view__sidebar-header">
354
312
  <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
@@ -356,62 +314,46 @@ export function renderStructureView(state) {
356
314
  </div>
357
315
  <div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
358
316
  total ${escapeHtml(
359
- formatNumber(
360
- (structure?.grouped?.tables?.length ?? 0) +
361
- (structure?.grouped?.views?.length ?? 0) +
362
- (structure?.grouped?.indexes?.length ?? 0) +
363
- (structure?.grouped?.triggers?.length ?? 0)
364
- )
317
+ formatNumber(
318
+ (structure?.grouped?.tables?.length ?? 0) +
319
+ (structure?.grouped?.views?.length ?? 0) +
320
+ (structure?.grouped?.indexes?.length ?? 0) +
321
+ (structure?.grouped?.triggers?.length ?? 0),
322
+ ),
365
323
  )}
366
324
  </div>
367
325
  </div>
368
326
  ${renderStructureTableSearch(state)}
369
327
  <div class="structure-view__sidebar-body custom-scrollbar">
370
328
  ${
371
- structure
372
- ? `
373
- ${renderEntryGroup(
374
- "Tables",
375
- filteredTables,
376
- state.structure.selectedName,
377
- {
329
+ structure
330
+ ? `
331
+ ${renderEntryGroup('Tables', filteredTables, state.structure.selectedName, {
378
332
  compact: true,
379
333
  showMeta: false,
380
334
  emptyMessage: state.structure.tableSearchQuery
381
- ? "tables match the current search."
382
- : null,
383
- }
384
- )}
385
- ${renderEntryGroup(
386
- "Views",
387
- structure.grouped.views,
388
- state.structure.selectedName
389
- )}
390
- ${renderEntryGroup(
391
- "Indexes",
392
- structure.grouped.indexes,
393
- state.structure.selectedName,
394
- { compact: true, showMeta: false }
395
- )}
396
- ${renderEntryGroup(
397
- "Triggers",
398
- structure.grouped.triggers,
399
- state.structure.selectedName
400
- )}
335
+ ? 'tables match the current search.'
336
+ : null,
337
+ })}
338
+ ${renderEntryGroup('Views', structure.grouped.views, state.structure.selectedName)}
339
+ ${renderEntryGroup('Indexes', structure.grouped.indexes, state.structure.selectedName, {
340
+ compact: true,
341
+ showMeta: false,
342
+ })}
343
+ ${renderEntryGroup('Triggers', structure.grouped.triggers, state.structure.selectedName)}
401
344
  `
402
- : ""
345
+ : ''
403
346
  }
404
347
  </div>
405
348
  </aside>
406
349
  `
407
- : ""
350
+ : ''
408
351
  }
409
352
 
410
353
  <section class="structure-view__detail">
411
- ${renderStructureWorkspaceHeader(structure, state.structure.selectedName)}
412
354
  ${
413
- state.structure.loading && !structure
414
- ? `
355
+ state.structure.loading && !structure
356
+ ? `
415
357
  <div class="flex min-h-0 flex-1 items-center justify-center border-t border-outline-variant/10 bg-surface-container-low">
416
358
  <div class="text-center text-on-surface-variant/40">
417
359
  <span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
@@ -419,8 +361,8 @@ export function renderStructureView(state) {
419
361
  </div>
420
362
  </div>
421
363
  `
422
- : state.structure.error
423
- ? `
364
+ : state.structure.error
365
+ ? `
424
366
  <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">
425
367
  <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
426
368
  ${escapeHtml(state.structure.error.code)}
@@ -428,23 +370,23 @@ export function renderStructureView(state) {
428
370
  <div class="mt-2">${escapeHtml(state.structure.error.message)}</div>
429
371
  </div>
430
372
  `
431
- : structure
432
- ? `
373
+ : structure
374
+ ? `
433
375
  <div class="structure-view__graph-shell">
434
376
  ${renderGraphSurface(
435
- structure,
436
- state.structure.selectedName,
437
- detail,
438
- state.structure.detailLoading,
439
- tablesVisible
377
+ structure,
378
+ state.structure.selectedName,
379
+ detail,
380
+ state.structure.detailLoading,
381
+ tablesVisible,
440
382
  )}
441
383
  </div>
442
384
  `
443
- : ""
385
+ : ''
444
386
  }
445
387
  </section>
446
388
  </section>
447
389
  `,
448
- panel: "",
449
- };
390
+ panel: '',
391
+ };
450
392
  }
@@ -279,6 +279,12 @@ img {
279
279
  border-right: 2px solid var(--color-primary-container);
280
280
  }
281
281
 
282
+ .text-xs {
283
+ white-space: nowrap;
284
+ overflow: hidden;
285
+ text-overflow: ellipsis;
286
+ }
287
+
282
288
  @keyframes cursor-blink {
283
289
  from,
284
290
  to {