@dollhousemcp/mcp-server 2.0.26 → 2.0.27-rc.2

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.
@@ -105,6 +105,7 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
105
105
  let activeSort = 'date-desc';
106
106
  let activeSource = 'all'; // 'all' | 'collection' | 'portfolio'
107
107
  let searchQuery = '';
108
+ const DOLLHOUSE_SERVER_VERSION = document.querySelector('meta[name="dollhouse-server-version"]')?.content || '';
108
109
  const DOLLHOUSE_SESSION_ID = document.querySelector('meta[name="dollhouse-session-id"]')?.content || '';
109
110
  const DOLLHOUSE_RUNTIME_SESSION_ID = document.querySelector('meta[name="dollhouse-runtime-session-id"]')?.content || '';
110
111
 
@@ -117,6 +118,10 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
117
118
  .replaceAll("'", ''');
118
119
  }
119
120
 
121
+ function normalizeInlineMetaText(value) {
122
+ return typeof value === 'string' ? value.trim() : '';
123
+ }
124
+
120
125
  function renderHeaderStatsMarkup(primaryMarkup) {
121
126
  const sessionTitle = DOLLHOUSE_RUNTIME_SESSION_ID && DOLLHOUSE_RUNTIME_SESSION_ID !== DOLLHOUSE_SESSION_ID
122
127
  ? `Stable session ${DOLLHOUSE_SESSION_ID}; runtime ${DOLLHOUSE_RUNTIME_SESSION_ID}`
@@ -130,6 +135,96 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
130
135
  return `${primaryMarkup}${sessionMarkup}`;
131
136
  }
132
137
 
138
+ function updateFooterVersion() {
139
+ // Keep the running build visible in the fixed footer so operators can
140
+ // quickly confirm which console version a given tab or machine is serving.
141
+ const footerVersion = document.getElementById('footer-version');
142
+ if (!footerVersion) return;
143
+ footerVersion.textContent = `Version: ${DOLLHOUSE_SERVER_VERSION || 'unknown'}`;
144
+ }
145
+
146
+ function wireThemeToggle() {
147
+ const themeToggleBtn = document.getElementById('theme-toggle');
148
+ const themeToggleIcon = document.getElementById('theme-toggle-icon');
149
+ const themeToggleLbl = document.getElementById('theme-toggle-label');
150
+ const html = document.documentElement;
151
+
152
+ function applyTheme(theme) {
153
+ html.dataset.theme = theme;
154
+ const isDark = theme === 'dark';
155
+ if (themeToggleIcon) themeToggleIcon.textContent = isDark ? '☀' : '☾';
156
+ if (themeToggleLbl) themeToggleLbl.textContent = isDark ? 'Switch to light mode' : 'Switch to dark mode';
157
+ if (themeToggleBtn) themeToggleBtn.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode');
158
+ const hljsLight = document.getElementById('hljs-theme-light');
159
+ const hljsDark = document.getElementById('hljs-theme-dark');
160
+ if (hljsLight) hljsLight.disabled = isDark;
161
+ if (hljsDark) hljsDark.disabled = !isDark;
162
+ try { localStorage.setItem('color-scheme', theme); } catch {}
163
+ }
164
+
165
+ const saved = (() => { try { return localStorage.getItem('color-scheme'); } catch {} })();
166
+ const preferred = saved || (globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
167
+ applyTheme(preferred);
168
+
169
+ if (themeToggleBtn) {
170
+ themeToggleBtn.addEventListener('click', () => {
171
+ applyTheme(html.dataset.theme === 'dark' ? 'light' : 'dark');
172
+ });
173
+ }
174
+ }
175
+
176
+ function wireViewToggle() {
177
+ const viewToggle = document.getElementById('view-toggle');
178
+ const elemGrid = document.getElementById('elements-grid');
179
+ let activeView = (() => { try { return localStorage.getItem('collection-view') || 'grid'; } catch { return 'grid'; } })();
180
+
181
+ function applyView(view) {
182
+ activeView = view;
183
+ if (elemGrid) elemGrid.dataset.view = view;
184
+ viewToggle?.querySelectorAll('.view-btn').forEach(btn => {
185
+ const on = btn.dataset.view === view;
186
+ btn.classList.toggle('active', on);
187
+ btn.setAttribute('aria-pressed', on);
188
+ });
189
+ try { localStorage.setItem('collection-view', view); } catch {}
190
+ }
191
+
192
+ applyView(activeView);
193
+
194
+ viewToggle?.addEventListener('click', e => {
195
+ const btn = e.target.closest('[data-view]');
196
+ if (btn) applyView(btn.dataset.view);
197
+ });
198
+ }
199
+
200
+ function wireSortControls() {
201
+ const sortSelect = document.getElementById('sort-select');
202
+ if (!sortSelect) return;
203
+ sortSelect.value = activeSort;
204
+ sortSelect.addEventListener('change', e => {
205
+ activeSort = e.target.value;
206
+ applyFilters();
207
+ });
208
+ }
209
+
210
+ function wireSourceToggle() {
211
+ const sourceToggle = document.getElementById('source-toggle');
212
+ if (!sourceToggle) return;
213
+ sourceToggle.addEventListener('click', e => {
214
+ const btn = e.target.closest('[data-source]');
215
+ if (!btn) return;
216
+ activeSource = btn.dataset.source;
217
+ sourceToggle.querySelectorAll('[data-source]').forEach(b => {
218
+ const on = b.dataset.source === activeSource;
219
+ b.classList.toggle('active', on);
220
+ b.setAttribute('aria-pressed', on);
221
+ });
222
+ renderTypeFilters();
223
+ renderTopicFilters();
224
+ applyFilters();
225
+ });
226
+ }
227
+
133
228
  // ── Bootstrap ──────────────────────────────────────────────────────────────
134
229
 
135
230
  function mergeCollectionData(data) {
@@ -458,6 +553,7 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
458
553
  const idx = pageStart + i; // absolute index into filteredElements
459
554
  const unavailable = el._unavailable;
460
555
  const compSummary = renderComponentSummary(el);
556
+ const author = normalizeInlineMetaText(el.author);
461
557
  return `
462
558
  <article
463
559
  class="element-card"
@@ -483,7 +579,7 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
483
579
  ${compSummary}
484
580
  <footer class="card-footer">
485
581
  <div class="card-meta">
486
- ${el.author ? `<span class="meta-author">${escapeHtml(el.author)}</span>` : ''}
582
+ ${author ? `<span class="meta-author">by ${escapeHtml(author)}</span>` : ''}
487
583
  ${el.version ? `<span class="meta-version">v${escapeHtml(el.version)}</span>` : ''}
488
584
  ${el.category ? `<span class="meta-category">${escapeHtml(el.category)}</span>` : ''}
489
585
  ${el.created ? `<span class="meta-date">${formatDate(el.created)}</span>` : ''}
@@ -682,9 +778,10 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
682
778
  }
683
779
 
684
780
  function setupModalMeta(element, modal) {
781
+ const author = normalizeInlineMetaText(element.author);
685
782
  modal.querySelector('.modal-title').textContent = element.name;
686
783
  modal.querySelector('.modal-type').textContent = capitalize(element.type);
687
- modal.querySelector('.modal-author').textContent = element.author ? `by ${element.author}` : '';
784
+ modal.querySelector('.modal-author').textContent = author ? `by ${author}` : '';
688
785
  modal.querySelector('.modal-version').textContent = element.version ? `v${element.version}` : '';
689
786
  const modalDate = modal.querySelector('.modal-date');
690
787
  const modalSource = modal.querySelector('.modal-source');
@@ -1877,70 +1974,10 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
1877
1974
  // ── Event wiring ───────────────────────────────────────────────────────────
1878
1975
 
1879
1976
  document.addEventListener('DOMContentLoaded', () => {
1880
-
1881
- // Theme toggle
1882
- const themeToggleBtn = document.getElementById('theme-toggle');
1883
- const themeToggleIcon = document.getElementById('theme-toggle-icon');
1884
- const themeToggleLbl = document.getElementById('theme-toggle-label');
1885
- const html = document.documentElement;
1886
-
1887
- function applyTheme(theme) {
1888
- html.dataset.theme = theme;
1889
- const isDark = theme === 'dark';
1890
- if (themeToggleIcon) themeToggleIcon.textContent = isDark ? '☀' : '☾';
1891
- if (themeToggleLbl) themeToggleLbl.textContent = isDark ? 'Switch to light mode' : 'Switch to dark mode';
1892
- if (themeToggleBtn) themeToggleBtn.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode');
1893
- // Sync highlight.js theme
1894
- const hljsLight = document.getElementById('hljs-theme-light');
1895
- const hljsDark = document.getElementById('hljs-theme-dark');
1896
- if (hljsLight) hljsLight.disabled = isDark;
1897
- if (hljsDark) hljsDark.disabled = !isDark;
1898
- try { localStorage.setItem('color-scheme', theme); } catch {}
1899
- }
1900
-
1901
- // Restore saved preference; fall back to OS preference
1902
- const saved = (() => { try { return localStorage.getItem('color-scheme'); } catch {} })();
1903
- const preferred = saved || (globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
1904
- applyTheme(preferred);
1905
-
1906
- if (themeToggleBtn) {
1907
- themeToggleBtn.addEventListener('click', () => {
1908
- applyTheme(html.dataset.theme === 'dark' ? 'light' : 'dark');
1909
- });
1910
- }
1911
-
1912
- // View toggle
1913
- const viewToggle = document.getElementById('view-toggle');
1914
- const elemGrid = document.getElementById('elements-grid');
1915
- let activeView = (() => { try { return localStorage.getItem('collection-view') || 'grid'; } catch { return 'grid'; } })();
1916
-
1917
- function applyView(view) {
1918
- activeView = view;
1919
- if (elemGrid) elemGrid.dataset.view = view;
1920
- viewToggle?.querySelectorAll('.view-btn').forEach(btn => {
1921
- const on = btn.dataset.view === view;
1922
- btn.classList.toggle('active', on);
1923
- btn.setAttribute('aria-pressed', on);
1924
- });
1925
- try { localStorage.setItem('collection-view', view); } catch {}
1926
- }
1927
-
1928
- applyView(activeView);
1929
-
1930
- viewToggle?.addEventListener('click', e => {
1931
- const btn = e.target.closest('[data-view]');
1932
- if (btn) applyView(btn.dataset.view);
1933
- });
1934
-
1935
- // Sort
1936
- const sortSelect = document.getElementById('sort-select');
1937
- if (sortSelect) {
1938
- sortSelect.value = activeSort;
1939
- sortSelect.addEventListener('change', e => {
1940
- activeSort = e.target.value;
1941
- applyFilters();
1942
- });
1943
- }
1977
+ updateFooterVersion();
1978
+ wireThemeToggle();
1979
+ wireViewToggle();
1980
+ wireSortControls();
1944
1981
 
1945
1982
  // Search
1946
1983
  const searchInput = document.getElementById('search-input');
@@ -1975,29 +2012,16 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
1975
2012
  }
1976
2013
  });
1977
2014
 
1978
- // Source toggle
1979
- const sourceToggle = document.getElementById('source-toggle');
1980
- if (sourceToggle) {
1981
- sourceToggle.addEventListener('click', e => {
1982
- const btn = e.target.closest('[data-source]');
1983
- if (!btn) return;
1984
- activeSource = btn.dataset.source;
1985
- sourceToggle.querySelectorAll('[data-source]').forEach(b => {
1986
- const on = b.dataset.source === activeSource;
1987
- b.classList.toggle('active', on);
1988
- b.setAttribute('aria-pressed', on);
1989
- });
1990
- renderTypeFilters();
1991
- renderTopicFilters();
1992
- applyFilters();
1993
- });
1994
- }
2015
+ wireSourceToggle();
1995
2016
 
1996
2017
  // Portfolio button
1997
2018
  document.getElementById('btn-portfolio')?.addEventListener('click', loadLocalPortfolio);
1998
2019
 
1999
2020
  // ── Tab switching ─────────────────────────────────────────────────────────
2000
2021
  const consoleTabs = document.getElementById('console-tabs');
2022
+ const headerNavRow = document.querySelector('.header-nav-row');
2023
+ const consoleTabMenuToggle = document.getElementById('console-tab-menu-toggle');
2024
+ const consoleTabMenu = document.getElementById('console-tab-menu');
2001
2025
  const tabInits = { logs: false, metrics: false, permissions: false, security: false };
2002
2026
 
2003
2027
  const TAB_KEY = 'dollhousemcp-active-tab';
@@ -2082,8 +2106,71 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
2082
2106
  p.hidden = p.id !== 'tab-' + tabName;
2083
2107
  p.classList.toggle('active', p.id === 'tab-' + tabName);
2084
2108
  });
2109
+ syncConsoleTabMenuSelection();
2110
+ closeConsoleTabMenu();
2111
+ scheduleConsoleTabOverflowCheck();
2085
2112
  };
2086
2113
 
2114
+ function renderConsoleTabMenu() {
2115
+ if (!consoleTabs || !consoleTabMenu) return;
2116
+ consoleTabMenu.innerHTML = '';
2117
+ consoleTabs.querySelectorAll('.console-tab').forEach((btn) => {
2118
+ const item = document.createElement('button');
2119
+ item.type = 'button';
2120
+ item.className = 'console-tab-menu-item';
2121
+ item.dataset.tab = btn.dataset.tab || '';
2122
+ item.setAttribute('role', 'menuitem');
2123
+ item.textContent = btn.textContent || '';
2124
+ if (btn.classList.contains('active')) item.classList.add('active');
2125
+ consoleTabMenu.appendChild(item);
2126
+ });
2127
+ }
2128
+
2129
+ function syncConsoleTabMenuSelection() {
2130
+ if (!consoleTabs || !consoleTabMenu) return;
2131
+ const activeTab = consoleTabs.querySelector('.console-tab.active')?.dataset.tab || '';
2132
+ consoleTabMenu.querySelectorAll('.console-tab-menu-item').forEach((item) => {
2133
+ item.classList.toggle('active', item.dataset.tab === activeTab);
2134
+ });
2135
+ }
2136
+
2137
+ function closeConsoleTabMenu() {
2138
+ if (!consoleTabMenu || !consoleTabMenuToggle) return;
2139
+ consoleTabMenu.hidden = true;
2140
+ consoleTabMenuToggle.setAttribute('aria-expanded', 'false');
2141
+ }
2142
+
2143
+ function tabsNeedOverflowMenu() {
2144
+ if (!consoleTabs) return false;
2145
+ const buttons = Array.from(consoleTabs.querySelectorAll('.console-tab'));
2146
+ if (buttons.length < 2) return false;
2147
+ const firstTop = buttons[0].offsetTop;
2148
+ return buttons.some((btn) => btn.offsetTop !== firstTop);
2149
+ }
2150
+
2151
+ let consoleTabOverflowFrame = 0;
2152
+ function updateConsoleTabOverflow() {
2153
+ if (!consoleTabs || !headerNavRow || !consoleTabMenuToggle) return;
2154
+ headerNavRow.classList.remove('header-nav-row--collapsed');
2155
+ consoleTabMenuToggle.hidden = true;
2156
+ const shouldCollapse = tabsNeedOverflowMenu();
2157
+ if (shouldCollapse) {
2158
+ headerNavRow.classList.add('header-nav-row--collapsed');
2159
+ consoleTabMenuToggle.hidden = false;
2160
+ }
2161
+ if (!shouldCollapse) {
2162
+ closeConsoleTabMenu();
2163
+ }
2164
+ }
2165
+
2166
+ function scheduleConsoleTabOverflowCheck() {
2167
+ if (consoleTabOverflowFrame) cancelAnimationFrame(consoleTabOverflowFrame);
2168
+ consoleTabOverflowFrame = requestAnimationFrame(() => {
2169
+ consoleTabOverflowFrame = 0;
2170
+ updateConsoleTabOverflow();
2171
+ });
2172
+ }
2173
+
2087
2174
  /**
2088
2175
  * Parse the URL hash into tab name and query parameters.
2089
2176
  * Supports fragment-query pattern: #tab?key=value&key=value
@@ -2218,6 +2305,15 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
2218
2305
  // Handle hash changes for deep-linking (e.g., open_logs operation)
2219
2306
  globalThis.addEventListener('hashchange', () => applyHashTab());
2220
2307
 
2308
+ renderConsoleTabMenu();
2309
+ syncConsoleTabMenuSelection();
2310
+ scheduleConsoleTabOverflowCheck();
2311
+ globalThis.addEventListener('resize', scheduleConsoleTabOverflowCheck);
2312
+ if (typeof ResizeObserver === 'function' && headerNavRow) {
2313
+ const tabOverflowObserver = new ResizeObserver(() => scheduleConsoleTabOverflowCheck());
2314
+ tabOverflowObserver.observe(headerNavRow);
2315
+ }
2316
+
2221
2317
  if (consoleTabs) {
2222
2318
  consoleTabs.addEventListener('click', (e) => {
2223
2319
  const btn = e.target.closest('.console-tab');
@@ -2232,6 +2328,31 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
2232
2328
  });
2233
2329
  }
2234
2330
 
2331
+ consoleTabMenuToggle?.addEventListener('click', () => {
2332
+ if (!consoleTabMenu) return;
2333
+ const willOpen = consoleTabMenu.hidden;
2334
+ consoleTabMenu.hidden = !willOpen;
2335
+ consoleTabMenuToggle.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
2336
+ });
2337
+
2338
+ consoleTabMenu?.addEventListener('click', (e) => {
2339
+ const btn = e.target.closest('.console-tab-menu-item');
2340
+ if (!btn?.dataset.tab) return;
2341
+ const tab = btn.dataset.tab;
2342
+ switchToTab(tab);
2343
+ localStorage.setItem(TAB_KEY, tab);
2344
+ lazyInitTab(tab, tabInits);
2345
+ });
2346
+
2347
+ globalThis.addEventListener('click', (e) => {
2348
+ if (!consoleTabMenu || !consoleTabMenuToggle) return;
2349
+ if (consoleTabMenu.hidden) return;
2350
+ const target = e.target;
2351
+ if (!(target instanceof Element)) return;
2352
+ if (consoleTabMenu.contains(target) || consoleTabMenuToggle.contains(target)) return;
2353
+ closeConsoleTabMenu();
2354
+ });
2355
+
2235
2356
  function lazyInitTab(tab, tabInits, params) {
2236
2357
  const dc = globalThis.DollhouseConsole;
2237
2358
  const module = dc?.[tab];
@@ -18,6 +18,8 @@
18
18
  <meta name="dollhouse-runtime-session-id" content="{{DOLLHOUSE_RUNTIME_SESSION_ID}}">
19
19
  <!-- Asset version — injected at request time for cache-busting local CSS/JS/img files. -->
20
20
  <meta name="dollhouse-console-asset-version" content="{{DOLLHOUSE_ASSET_VERSION}}">
21
+ <link rel="icon" type="image/png" href="dollhouse-logo.png?v={{DOLLHOUSE_ASSET_VERSION}}">
22
+ <link rel="apple-touch-icon" href="dollhouse-logo.png?v={{DOLLHOUSE_ASSET_VERSION}}">
21
23
  <link rel="stylesheet" href="fonts.css?v={{DOLLHOUSE_ASSET_VERSION}}">
22
24
  <link rel="stylesheet" href="styles.css?v={{DOLLHOUSE_ASSET_VERSION}}">
23
25
  <link rel="stylesheet" href="logs.css?v={{DOLLHOUSE_ASSET_VERSION}}">
@@ -41,7 +43,14 @@
41
43
  <p class="site-tagline">Management Console</p>
42
44
  </div>
43
45
  </div>
44
- <div class="header-right">
46
+ <div class="header-controls">
47
+ <div class="site-stats" id="stats" aria-live="polite"></div>
48
+ <button class="theme-toggle" id="theme-toggle" type="button" aria-label="Switch to dark mode" title="Switch to dark mode">
49
+ <span class="theme-toggle-icon" id="theme-toggle-icon" aria-hidden="true">&#9790;</span>
50
+ <span class="sr-only" id="theme-toggle-label">Switch to dark mode</span>
51
+ </button>
52
+ </div>
53
+ <div class="header-nav-row">
45
54
  <nav class="console-tabs" id="console-tabs" aria-label="Console tabs">
46
55
  <button class="console-tab" data-tab="setup">Setup</button>
47
56
  <button class="console-tab" data-tab="security">Auth</button>
@@ -50,12 +59,14 @@
50
59
  <button class="console-tab" data-tab="permissions">Permissions</button>
51
60
  <button class="console-tab active" data-tab="portfolio">Portfolio</button>
52
61
  </nav>
62
+ <div class="console-tab-menu-shell" id="console-tab-menu-shell">
63
+ <button class="console-tab-menu-toggle" id="console-tab-menu-toggle" type="button" hidden aria-haspopup="menu" aria-expanded="false" aria-controls="console-tab-menu">
64
+ <span class="console-tab-menu-icon" aria-hidden="true">&#9776;</span>
65
+ <span class="console-tab-menu-label">Menu</span>
66
+ </button>
67
+ <div class="console-tab-menu" id="console-tab-menu" role="menu" hidden></div>
68
+ </div>
53
69
  <div class="session-indicator" id="session-indicator" title="Active sessions"></div>
54
- <div class="site-stats" id="stats" aria-live="polite"></div>
55
- <button class="theme-toggle" id="theme-toggle" type="button" aria-label="Switch to dark mode" title="Switch to dark mode">
56
- <span class="theme-toggle-icon" id="theme-toggle-icon" aria-hidden="true">&#9790;</span>
57
- <span class="sr-only" id="theme-toggle-label">Switch to dark mode</span>
58
- </button>
59
70
  </div>
60
71
  </header>
61
72
 
@@ -570,6 +581,7 @@ npm install @dollhousemcp/mcp-server</code></pre>
570
581
  <a href="https://github.com/DollhouseMCP/mcp-server" class="footer-link">GitHub Repository</a>
571
582
  <a href="./collection-index.json" class="footer-link">JSON API</a>
572
583
  <a href="https://dollhousemcp.com" class="footer-link">DollhouseMCP</a>
584
+ <span class="footer-version" id="footer-version" aria-live="polite" aria-atomic="true"></span>
573
585
  <span class="footer-updated" id="footer-updated"></span>
574
586
  <span class="footer-copyright">&copy; 2026 DollhouseMCP</span>
575
587
  </div>
@@ -6,7 +6,7 @@
6
6
  .log-viewer {
7
7
  display: flex;
8
8
  flex-direction: column;
9
- height: calc(100vh - 120px);
9
+ height: calc(100dvh - var(--site-footer-height, 4.5rem) - 120px);
10
10
  font-family: var(--font-mono);
11
11
  font-size: 12.5px;
12
12
  line-height: 1.5;
@@ -470,3 +470,159 @@
470
470
  .log-jump-bottom:hover {
471
471
  background: var(--signal-2);
472
472
  }
473
+
474
+ @media (max-width: 48rem) {
475
+ .log-viewer {
476
+ height: calc(100dvh - var(--site-footer-height, 4.5rem) - 104px);
477
+ }
478
+
479
+ .log-controls {
480
+ padding: 10px;
481
+ gap: 10px;
482
+ }
483
+
484
+ .log-filter-group {
485
+ flex: 1 1 calc(50% - 10px);
486
+ min-width: 0;
487
+ }
488
+
489
+ .log-filter-group label {
490
+ flex-shrink: 0;
491
+ }
492
+
493
+ .log-controls select,
494
+ .log-controls input,
495
+ .log-search {
496
+ min-width: 0;
497
+ width: 100%;
498
+ max-width: none;
499
+ }
500
+
501
+ .log-status-bar {
502
+ flex-wrap: wrap;
503
+ row-gap: 6px;
504
+ padding: 8px 10px;
505
+ }
506
+
507
+ .log-entry-count {
508
+ margin-left: 0;
509
+ width: 100%;
510
+ order: 10;
511
+ }
512
+
513
+ .log-entry {
514
+ display: grid;
515
+ grid-template-columns: auto auto auto minmax(0, 1fr) auto;
516
+ grid-template-areas:
517
+ "checkbox time level source corr"
518
+ ". message message message message";
519
+ align-items: start;
520
+ gap: 4px 8px;
521
+ padding: 6px 10px;
522
+ min-height: 74px;
523
+ white-space: normal;
524
+ }
525
+
526
+ .log-checkbox {
527
+ grid-area: checkbox;
528
+ margin-top: 1px;
529
+ }
530
+
531
+ .log-time {
532
+ grid-area: time;
533
+ width: auto;
534
+ }
535
+
536
+ .log-level {
537
+ grid-area: level;
538
+ width: auto;
539
+ min-width: 42px;
540
+ padding-inline: 6px;
541
+ }
542
+
543
+ .log-category {
544
+ display: none;
545
+ }
546
+
547
+ .log-source {
548
+ grid-area: source;
549
+ max-width: none;
550
+ min-width: 0;
551
+ }
552
+
553
+ .log-corr-badge {
554
+ grid-area: corr;
555
+ width: auto;
556
+ justify-self: end;
557
+ }
558
+
559
+ .log-message {
560
+ grid-area: message;
561
+ white-space: normal;
562
+ overflow: hidden;
563
+ text-overflow: initial;
564
+ display: -webkit-box;
565
+ -webkit-line-clamp: 2;
566
+ -webkit-box-orient: vertical;
567
+ line-height: 1.45;
568
+ }
569
+
570
+ .log-detail-card {
571
+ width: min(680px, calc(100vw - 1rem));
572
+ max-height: calc(100vh - 1rem);
573
+ border-radius: 0.9rem;
574
+ }
575
+
576
+ .log-detail-card-header {
577
+ flex-wrap: wrap;
578
+ gap: 8px;
579
+ align-items: flex-start;
580
+ }
581
+
582
+ .log-detail-card-actions {
583
+ width: 100%;
584
+ justify-content: flex-end;
585
+ flex-wrap: wrap;
586
+ }
587
+
588
+ .log-detail-field {
589
+ flex-direction: column;
590
+ gap: 2px;
591
+ }
592
+
593
+ .log-detail-field-label {
594
+ min-width: 0;
595
+ }
596
+
597
+ .log-jump-bottom {
598
+ right: 12px;
599
+ bottom: 12px;
600
+ }
601
+ }
602
+
603
+ @media (max-width: 32rem) {
604
+ .log-filter-group {
605
+ flex: 1 1 100%;
606
+ }
607
+
608
+ .log-entry {
609
+ grid-template-columns: auto auto minmax(0, 1fr) auto;
610
+ grid-template-areas:
611
+ "checkbox level source corr"
612
+ ". time time time"
613
+ ". message message message";
614
+ min-height: 96px;
615
+ }
616
+
617
+ .log-time {
618
+ color: var(--ink-700);
619
+ }
620
+
621
+ .log-source {
622
+ font-size: 10px;
623
+ }
624
+
625
+ .log-status-indicator {
626
+ width: 100%;
627
+ }
628
+ }