sqlite-hub 0.9.0 → 0.9.3

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.
@@ -20,10 +20,12 @@ function renderMissingDatabase() {
20
20
  }
21
21
 
22
22
  function renderChartsList(state) {
23
- const queries = state.charts.queries ?? [];
23
+ const activeTab = ['recent', 'saved'].includes(state.charts.historyTab) ? state.charts.historyTab : 'recent';
24
+ const allQueries = state.charts.queries ?? [];
25
+ const queries = activeTab === 'saved' ? allQueries.filter(item => item.isSaved) : allQueries;
24
26
  const selectedHistoryId = Number(state.charts.selectedHistoryId);
25
27
 
26
- if (state.charts.loading && !queries.length) {
28
+ if (state.charts.loading && !allQueries.length) {
27
29
  return `
28
30
  <div class="flex h-full items-center justify-center px-6 text-center text-on-surface-variant/45">
29
31
  <div>
@@ -34,7 +36,7 @@ function renderChartsList(state) {
34
36
  `;
35
37
  }
36
38
 
37
- if (state.charts.error && !queries.length) {
39
+ if (state.charts.error && !allQueries.length) {
38
40
  return `
39
41
  <div class="p-5">
40
42
  <div class="border border-error/30 bg-error-container/20 px-4 py-4 text-sm text-error">
@@ -45,15 +47,23 @@ function renderChartsList(state) {
45
47
  }
46
48
 
47
49
  if (!queries.length) {
50
+ const isSavedTab = activeTab === 'saved';
51
+
48
52
  return `
49
53
  <div class="flex h-full items-center justify-center px-6 text-center">
50
54
  <div>
51
- <span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">query_stats</span>
55
+ <span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">${
56
+ isSavedTab ? 'bookmark' : 'query_stats'
57
+ }</span>
52
58
  <p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
53
- No Chartable Queries
59
+ ${isSavedTab ? 'No Saved Charts Queries' : 'No Chartable Queries'}
54
60
  </p>
55
61
  <p class="mt-2 max-w-xs text-sm leading-6 text-on-surface-variant/60">
56
- Run SELECT queries in the SQL Editor first. They will appear here automatically.
62
+ ${
63
+ isSavedTab
64
+ ? 'Save chartable queries from this list or from the SQL Editor to keep them here.'
65
+ : 'Run SELECT queries in the SQL Editor first. They will appear here automatically.'
66
+ }
57
67
  </p>
58
68
  </div>
59
69
  </div>
@@ -66,36 +76,67 @@ function renderChartsList(state) {
66
76
  ${queries
67
77
  .map(
68
78
  item => `
69
- <button
70
- class="w-full border px-4 py-3 text-left transition-colors ${
71
- selectedHistoryId === item.id
79
+ <article
80
+ class="group w-full border transition-colors ${
81
+ selectedHistoryId === Number(item.id)
72
82
  ? 'border-primary-container/30 bg-surface-container-high'
73
83
  : 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
74
84
  }"
75
- data-action="navigate"
76
- data-to="/charts/${encodeURIComponent(item.id)}"
77
- type="button"
85
+ data-charts-history-item
78
86
  >
79
- <div class="flex items-start justify-between gap-3">
80
- <div class="min-w-0 flex-1 truncate font-mono text-xs ${
81
- selectedHistoryId === item.id ? 'text-primary-container' : 'text-on-surface'
82
- }">
83
- ${escapeHtml(item.displayTitle)}
87
+ <button
88
+ class="w-full px-4 py-3 text-left transition-colors group-hover:bg-surface-container-high"
89
+ data-action="navigate"
90
+ data-history-id="${escapeHtml(item.id)}"
91
+ data-to="/charts/${encodeURIComponent(item.id)}"
92
+ type="button"
93
+ >
94
+ <div class="flex items-start justify-between gap-3">
95
+ <div class="min-w-0 flex-1 truncate font-mono text-xs ${
96
+ selectedHistoryId === Number(item.id) ? 'text-primary-container' : 'text-on-surface'
97
+ }" data-charts-history-title>
98
+ ${escapeHtml(item.displayTitle)}
99
+ </div>
100
+ <div class="flex shrink-0 flex-wrap justify-end gap-1">
101
+ <span class="inline-flex" data-charts-saved-badge ${item.isSaved ? '' : 'hidden'}>
102
+ ${renderStatusBadge('saved', 'primary')}
103
+ </span>
104
+ ${
105
+ item.chartTypes?.length
106
+ ? item.chartTypes
107
+ .map(chartType => renderStatusBadge(getQueryChartTypeLabel(chartType), 'primary'))
108
+ .join('')
109
+ : renderStatusBadge('None', 'muted')
110
+ }
111
+ </div>
84
112
  </div>
85
- <div class="flex shrink-0 flex-wrap justify-end gap-1">
86
- ${
87
- item.chartTypes?.length
88
- ? item.chartTypes
89
- .map(chartType => renderStatusBadge(getQueryChartTypeLabel(chartType), 'primary'))
90
- .join('')
91
- : renderStatusBadge('None', 'muted')
92
- }
113
+ <div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
114
+ ${escapeHtml(item.previewSql)}
93
115
  </div>
116
+ </button>
117
+ <div class="flex items-center justify-end border-t border-outline-variant/10 px-3 py-2 transition-colors group-hover:bg-surface-container-high">
118
+ <button
119
+ aria-label="Open chart query"
120
+ class="min-h-[var(--control-height)] flex-1 self-stretch"
121
+ data-action="navigate"
122
+ data-history-id="${escapeHtml(item.id)}"
123
+ data-to="/charts/${encodeURIComponent(item.id)}"
124
+ type="button"
125
+ ></button>
126
+ <button
127
+ class="query-history-icon-button ${item.isSaved ? 'is-active' : ''}"
128
+ data-action="toggle-charts-query-history-saved"
129
+ data-history-id="${escapeHtml(item.id)}"
130
+ data-next-value="${item.isSaved ? 'false' : 'true'}"
131
+ title="${item.isSaved ? 'Remove from saved' : 'Save query'}"
132
+ type="button"
133
+ >
134
+ <span class="material-symbols-outlined text-[18px]">
135
+ ${item.isSaved ? 'bookmark' : 'bookmark_add'}
136
+ </span>
137
+ </button>
94
138
  </div>
95
- <div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
96
- ${escapeHtml(item.previewSql)}
97
- </div>
98
- </button>
139
+ </article>
99
140
  `,
100
141
  )
101
142
  .join('')}
@@ -104,6 +145,37 @@ function renderChartsList(state) {
104
145
  `;
105
146
  }
106
147
 
148
+ function renderChartsHistoryTabs(state) {
149
+ const activeTab = ['recent', 'saved'].includes(state.charts.historyTab) ? state.charts.historyTab : 'recent';
150
+ const savedCount = (state.charts.queries ?? []).filter(item => item.isSaved).length;
151
+ const tabs = [
152
+ { id: 'recent', label: 'Recent', count: state.charts.queries?.length ?? 0 },
153
+ { id: 'saved', label: 'Saved', count: savedCount },
154
+ ];
155
+
156
+ return `
157
+ <div class="mt-4 flex items-center gap-2">
158
+ ${tabs
159
+ .map(
160
+ tab => `
161
+ <button
162
+ class="query-history-tab ${activeTab === tab.id ? 'is-active' : ''}"
163
+ data-action="set-charts-history-tab"
164
+ data-tab="${escapeHtml(tab.id)}"
165
+ type="button"
166
+ >
167
+ ${escapeHtml(tab.label)}
168
+ </button>
169
+ `,
170
+ )
171
+ .join('')}
172
+ <span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">
173
+ <span data-charts-history-count>${escapeHtml(String(tabs.find(tab => tab.id === activeTab)?.count ?? 0))}</span>
174
+ </span>
175
+ </div>
176
+ `;
177
+ }
178
+
107
179
  function renderEmptyChartDetail() {
108
180
  return `
109
181
  <div class="flex flex-1 items-center justify-center px-8 py-10 text-center">
@@ -347,7 +419,7 @@ function renderChartCard(chart, state, analysis) {
347
419
  `;
348
420
  }
349
421
 
350
- function renderChartsDetail(state) {
422
+ export function renderChartsDetail(state) {
351
423
  const detail = state.charts.detail;
352
424
  const selectedHistoryId = state.charts.selectedHistoryId;
353
425
  const historyVisible = state.editor.historyPanelVisible !== false;
@@ -485,6 +557,7 @@ export function renderChartsView(state) {
485
557
  <h2 class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
486
558
  Charts
487
559
  </h2>
560
+ ${renderChartsHistoryTabs(state)}
488
561
  </div>
489
562
  ${renderChartsList(state)}
490
563
  </aside>
@@ -449,7 +449,36 @@ export function renderDataRowEditorPanel(state) {
449
449
  (foreignKey.mappings ?? []).map(mapping => String(mapping.from ?? '').trim()).filter(Boolean),
450
450
  ),
451
451
  );
452
- const getColumnBadges = column => (foreignKeyColumnNames.has(column.name) ? ['FK'] : []);
452
+ const getColumnTypeBadge = column => String(column.declaredType || column.affinity || 'BLOB').trim().toUpperCase();
453
+ const getColumnNumberInputMeta = column => {
454
+ const affinity = String(column.affinity ?? '').toUpperCase();
455
+
456
+ if (affinity === 'INTEGER') {
457
+ return { inputType: 'number', numberStep: '1' };
458
+ }
459
+
460
+ if (affinity === 'NUMERIC') {
461
+ return { inputType: 'number', numberStep: 'any' };
462
+ }
463
+
464
+ return {};
465
+ };
466
+ const getColumnBadges = column => {
467
+ const badges = [{ label: getColumnTypeBadge(column), tone: 'type' }];
468
+
469
+ if (column.primaryKeyPosition > 0) {
470
+ badges.push({
471
+ label: column.primaryKeyPosition > 1 ? `PK ${column.primaryKeyPosition}` : 'PK',
472
+ tone: 'primary-key',
473
+ });
474
+ }
475
+
476
+ if (foreignKeyColumnNames.has(column.name)) {
477
+ badges.push({ label: 'FK', tone: 'foreign-key' });
478
+ }
479
+
480
+ return badges;
481
+ };
453
482
 
454
483
  const identityColumns = table.identityStrategy?.type === 'primaryKey' ? (table.identityStrategy.columns ?? []) : [];
455
484
  const editableColumns = (table.columnMeta ?? []).filter(column => {
@@ -502,6 +531,7 @@ export function renderDataRowEditorPanel(state) {
502
531
  name: column.name,
503
532
  label: column.name,
504
533
  badges: getColumnBadges(column),
534
+ ...getColumnNumberInputMeta(column),
505
535
  value: value === null || value === undefined ? '' : String(value),
506
536
  };
507
537
  }),
@@ -265,6 +265,7 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
265
265
  error: state.editor.historyError,
266
266
  activeTab: state.editor.historyTab,
267
267
  search: state.editor.historySearchInput,
268
+ committedSearch: state.editor.historySearch,
268
269
  total: state.editor.historyTotal,
269
270
  hasMore: state.editor.historyHasMore,
270
271
  activeHistoryId: state.editor.historyActiveId,
@@ -326,7 +326,10 @@ function renderCreatedTagList(tags = [], { canRemove = false, removingTagKey = n
326
326
  `;
327
327
  }
328
328
 
329
- function renderPreviewMedia(currentItem, { detailsVisible = true, mediaTableName = '' } = {}) {
329
+ function renderPreviewMedia(
330
+ currentItem,
331
+ { detailsVisible = true, mediaTableName = '', rotationDegrees = 0, status = null } = {},
332
+ ) {
330
333
  if (!currentItem) {
331
334
  return `
332
335
  <div class="media-tagging-preview__empty">
@@ -344,6 +347,15 @@ function renderPreviewMedia(currentItem, { detailsVisible = true, mediaTableName
344
347
  const pathValue = String(currentItem.path ?? '');
345
348
  const fileName = pathValue.split(/[\\/]/).filter(Boolean).pop() || 'Audio file';
346
349
  const isAudioPreview = Boolean(currentItem.previewUrl && currentItem.previewKind === 'audio');
350
+ const isRotatablePreview = Boolean(
351
+ currentItem.previewUrl && (currentItem.previewKind === 'image' || currentItem.previewKind === 'video'),
352
+ );
353
+ const numericRotation = Number(rotationDegrees);
354
+ const normalizedRotation = Number.isFinite(numericRotation)
355
+ ? ((Math.round(numericRotation / 90) * 90) % 360 + 360) % 360
356
+ : 0;
357
+ const rotationStyle = `--media-tagging-preview-rotation: ${normalizedRotation}deg;`;
358
+ const rotationClass = normalizedRotation === 90 || normalizedRotation === 270 ? ' is-rotated-quarter' : '';
347
359
  let assetMarkup = `
348
360
  <div class="media-tagging-preview__placeholder">
349
361
  <span class="material-symbols-outlined text-5xl">perm_media</span>
@@ -353,16 +365,27 @@ function renderPreviewMedia(currentItem, { detailsVisible = true, mediaTableName
353
365
  if (currentItem.previewUrl && currentItem.previewKind === 'image') {
354
366
  assetMarkup = `
355
367
  <img
356
- class="media-tagging-preview__asset"
368
+ class="media-tagging-preview__asset${rotationClass}"
369
+ data-media-tagging-rotation-target="true"
370
+ data-rotation-degrees="${escapeHtml(String(normalizedRotation))}"
371
+ style="${escapeHtml(rotationStyle)}"
357
372
  src="${escapeHtml(currentItem.previewUrl)}"
358
373
  alt="${escapeHtml(pathValue || 'Current media item')}"
359
374
  />
360
375
  `;
361
376
  } else if (currentItem.previewUrl && currentItem.previewKind === 'video') {
362
377
  assetMarkup = `
363
- <video class="media-tagging-preview__asset" controls preload="metadata" src="${escapeHtml(
378
+ <video
379
+ class="media-tagging-preview__asset${rotationClass}"
380
+ data-media-tagging-rotation-target="true"
381
+ data-rotation-degrees="${escapeHtml(String(normalizedRotation))}"
382
+ style="${escapeHtml(rotationStyle)}"
383
+ controls
384
+ preload="metadata"
385
+ src="${escapeHtml(
364
386
  currentItem.previewUrl,
365
- )}"></video>
387
+ )}"
388
+ ></video>
366
389
  `;
367
390
  } else if (currentItem.previewUrl && currentItem.previewKind === 'audio') {
368
391
  assetMarkup = `
@@ -395,36 +418,100 @@ function renderPreviewMedia(currentItem, { detailsVisible = true, mediaTableName
395
418
  <div class="media-tagging-preview ${detailsVisible ? '' : 'media-tagging-preview--meta-hidden'}">
396
419
  <div class="media-tagging-preview__media${isAudioPreview ? ' media-tagging-preview__media--audio' : ''}">
397
420
  <div class="media-tagging-preview__media-toolbar">
398
- <button
399
- class="standard-button media-tagging-preview__toggle"
400
- data-action="toggle-media-tagging-current-media"
401
- data-next-value="${detailsVisible ? 'false' : 'true'}"
402
- data-expanded-label="Shrink Media Viewer"
403
- data-collapsed-label="Show Media Viewer"
404
- aria-expanded="${detailsVisible ? 'true' : 'false'}"
405
- type="button"
406
- >
407
- ${toggleLabel}
408
- </button>
421
+ <div class="media-tagging-status media-tagging-status--compact media-tagging-status--preview">
422
+ <div class="media-tagging-status__value">${escapeHtml(status?.ratioLabel ?? '0 / 0')}</div>
423
+ <div class="media-tagging-status__label">
424
+ tagged / total
425
+ </div>
426
+ </div>
427
+ <div class="media-tagging-preview__toolbar-actions">
428
+ ${
429
+ isRotatablePreview
430
+ ? `
431
+ <div class="media-tagging-preview__rotation-controls" aria-label="Rotate media preview">
432
+ <button
433
+ class="standard-button media-tagging-preview__icon-button"
434
+ data-action="rotate-media-tagging-current-media"
435
+ data-rotation-command="left"
436
+ type="button"
437
+ aria-label="Rotate left 90 degrees"
438
+ title="Rotate left 90 degrees"
439
+ >
440
+ <span class="material-symbols-outlined">rotate_left</span>
441
+ </button>
442
+ <button
443
+ class="standard-button media-tagging-preview__icon-button"
444
+ data-action="rotate-media-tagging-current-media"
445
+ data-rotation-command="right"
446
+ type="button"
447
+ aria-label="Rotate right 90 degrees"
448
+ title="Rotate right 90 degrees"
449
+ >
450
+ <span class="material-symbols-outlined">rotate_right</span>
451
+ </button>
452
+ <button
453
+ class="standard-button media-tagging-preview__icon-button"
454
+ data-action="rotate-media-tagging-current-media"
455
+ data-rotation-command="reset"
456
+ type="button"
457
+ aria-label="Reset rotation"
458
+ title="Reset rotation"
459
+ ${normalizedRotation === 0 ? 'disabled' : ''}
460
+ >
461
+ <span class="material-symbols-outlined">restart_alt</span>
462
+ </button>
463
+ </div>
464
+ `
465
+ : ''
466
+ }
467
+ <button
468
+ class="standard-button media-tagging-preview__toggle"
469
+ data-action="toggle-media-tagging-current-media"
470
+ data-next-value="${detailsVisible ? 'false' : 'true'}"
471
+ data-expanded-label="Shrink Media Viewer"
472
+ data-collapsed-label="Show Media Viewer"
473
+ aria-expanded="${detailsVisible ? 'true' : 'false'}"
474
+ type="button"
475
+ >
476
+ ${toggleLabel}
477
+ </button>
478
+ </div>
479
+ </div>
480
+ <div class="media-tagging-preview__asset-shell">
481
+ ${assetMarkup}
409
482
  </div>
410
- ${assetMarkup}
411
483
  </div>
412
484
  <div class="media-tagging-preview__meta">
413
485
  <div class="media-tagging-preview__meta-header">
414
486
  <div class="media-tagging-preview__eyebrow">Current Media</div>
415
- ${
416
- mediaTableName && currentItem.identity
417
- ? `
418
- <button
419
- class="standard-button media-tagging-preview__meta-action"
420
- data-action="open-media-tagging-current-in-data"
421
- type="button"
422
- >
423
- Open In Data
424
- </button>
425
- `
426
- : ''
427
- }
487
+ <div class="media-tagging-preview__meta-actions">
488
+ ${
489
+ mediaTableName
490
+ ? `
491
+ <button
492
+ class="standard-button media-tagging-preview__meta-action"
493
+ data-action="open-media-tagging-current-in-structure"
494
+ type="button"
495
+ >
496
+ Open Structure
497
+ </button>
498
+ `
499
+ : ''
500
+ }
501
+ ${
502
+ mediaTableName && currentItem.identity
503
+ ? `
504
+ <button
505
+ class="standard-button media-tagging-preview__meta-action"
506
+ data-action="open-media-tagging-current-in-data"
507
+ type="button"
508
+ >
509
+ Open In Data
510
+ </button>
511
+ `
512
+ : ''
513
+ }
514
+ </div>
428
515
  </div>
429
516
  ${
430
517
  metadata.length
@@ -548,7 +635,7 @@ function renderTaggingSection(state) {
548
635
  const booleanCandidates = state.mediaTagging.booleanCandidates ?? [];
549
636
 
550
637
  return `
551
- <section class="media-tagging-card shell-section">
638
+ <section class="media-tagging-card media-tagging-card--tagging shell-section">
552
639
  <div class="media-tagging-card__header">
553
640
  <div>
554
641
  <div class="media-tagging-card__eyebrow">2. Tagging</div>
@@ -739,7 +826,12 @@ function renderWorkflowSection(state) {
739
826
  `
740
827
  : ''
741
828
  }
742
- ${renderPreviewMedia(workflow?.currentItem ?? null, { detailsVisible, mediaTableName })}
829
+ ${renderPreviewMedia(workflow?.currentItem ?? null, {
830
+ detailsVisible,
831
+ mediaTableName,
832
+ rotationDegrees: state.mediaTagging.workflowMediaRotationDegrees,
833
+ status,
834
+ })}
743
835
  <div class="media-tagging-workflow-sidebar">
744
836
  <div class="media-tagging-tag-panel">
745
837
  <div class="media-tagging-tag-panel__header">
@@ -750,12 +842,6 @@ function renderWorkflowSection(state) {
750
842
  ${escapeHtml(formatNumber(selectableTags.length))} selectable tag(s)
751
843
  </div>
752
844
  </div>
753
- <div class="media-tagging-status media-tagging-status--compact">
754
- <div class="media-tagging-status__value">${escapeHtml(status.ratioLabel)}</div>
755
- <div class="media-tagging-status__label">
756
- tagged / total
757
- </div>
758
- </div>
759
845
  </div>
760
846
  <label class="media-tagging-field mt-4">
761
847
  <span class="media-tagging-field__label">Search</span>
@@ -769,24 +855,29 @@ function renderWorkflowSection(state) {
769
855
  </div>
770
856
  ${renderTagList(selectableTags, selectedTagKeys)}
771
857
  <div class="media-tagging-tag-panel__footer">
772
- <div class="flex flex-wrap items-center self-end gap-3">
773
- <button
774
- class="standard-button"
775
- data-action="skip-media-tagging-item"
776
- type="button"
777
- ${workflow?.currentItem && canWrite && !state.mediaTagging.previewLoading && !state.mediaTagging.applying ? '' : 'disabled'}
778
- >
779
- ${state.mediaTagging.applying ? 'Saving...' : 'Skip'}
780
- </button>
781
- <button
782
- class="signature-button"
783
- data-action="apply-media-tagging"
784
- data-can-apply="${workflow?.currentItem && canWrite && !state.mediaTagging.applying ? 'true' : 'false'}"
785
- type="button"
786
- ${workflow?.currentItem && canWrite && !state.mediaTagging.applying && selectedTagCount > 0 ? '' : 'disabled'}
787
- >
788
- ${state.mediaTagging.applying ? 'Saving...' : `${selectedTagCount} tagged & next`}
789
- </button>
858
+ <div class="media-tagging-tag-panel__footer-main">
859
+ <div class="media-tagging-tag-panel__remaining">
860
+ ${escapeHtml(formatNumber(workflow?.status?.remainingCount ?? 0))} remaining
861
+ </div>
862
+ <div class="media-tagging-tag-panel__actions">
863
+ <button
864
+ class="standard-button"
865
+ data-action="skip-media-tagging-item"
866
+ type="button"
867
+ ${workflow?.currentItem && canWrite && !state.mediaTagging.previewLoading && !state.mediaTagging.applying ? '' : 'disabled'}
868
+ >
869
+ ${state.mediaTagging.applying ? 'Saving...' : 'Skip'}
870
+ </button>
871
+ <button
872
+ class="signature-button"
873
+ data-action="apply-media-tagging"
874
+ data-can-apply="${workflow?.currentItem && canWrite && !state.mediaTagging.applying ? 'true' : 'false'}"
875
+ type="button"
876
+ ${workflow?.currentItem && canWrite && !state.mediaTagging.applying && selectedTagCount > 0 ? '' : 'disabled'}
877
+ >
878
+ ${state.mediaTagging.applying ? 'Saving...' : `${selectedTagCount} tagged & next`}
879
+ </button>
880
+ </div>
790
881
  </div>
791
882
  ${
792
883
  workflow?.allRemainingSkipped
@@ -801,9 +892,6 @@ function renderWorkflowSection(state) {
801
892
  `
802
893
  : ''
803
894
  }
804
- <div class="text-[11px] font-mono uppercase tracking-[0.14em] text-on-surface-variant/45">
805
- ${escapeHtml(formatNumber(workflow?.status?.remainingCount ?? 0))} remaining
806
- </div>
807
895
  </div>
808
896
  </div>
809
897
  </div>
@@ -820,24 +908,6 @@ export function renderMediaTaggingView(state, { subView = 'setup' } = {}) {
820
908
  main: `
821
909
  <section class="view-surface media-tagging-view">
822
910
  <div class="media-tagging-shell">
823
- <header class="media-tagging-header">
824
- <div class="media-tagging-header__copy">
825
- <div class="text-[10px] font-bold uppercase tracking-[0.22em] text-primary-container">
826
- Media Tagging
827
- </div>
828
- <h1 class="mt-3 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
829
- ${showSetup ? 'Setup' : 'Tagging Queue'}
830
- </h1>
831
- <p class="mt-3 max-w-3xl text-sm leading-7 text-on-surface-variant/65">
832
- ${
833
- showSetup
834
- ? 'Configure tags, media queries, and mapping once per database.'
835
- : 'Review the next untagged media item, select tags, skip it, or mark it as tagged.'
836
- }
837
- </p>
838
- </div>
839
- </header>
840
-
841
911
  ${renderIssueList(state)}
842
912
 
843
913
  ${