goelak commited on
Commit
04e16a6
·
1 Parent(s): 85333ea

Refine demo navigation

Browse files
Files changed (4) hide show
  1. static/script.js +15 -98
  2. static/style.css +63 -18
  3. templates/index.html +32 -23
  4. test_app.py +49 -0
static/script.js CHANGED
@@ -14,15 +14,7 @@ import { initClearButton, updateClearButtonState } from './reset.js';
14
 
15
  document.addEventListener('DOMContentLoaded', function () {
16
  // === CONFIGURATION CONSTANTS ===
17
- const GRID_CONFIG = {
18
- MOBILE_MIN_WIDTH: 120,
19
- DESKTOP_MIN_WIDTH: 160,
20
- MOBILE_BREAKPOINT: 768,
21
- NARROW_BREAKPOINT: 360,
22
- MAX_LABEL_LENGTH: 60,
23
- BALANCE_DELAY: 100,
24
- RESIZE_DEBOUNCE: 250,
25
- };
26
 
27
  const UI_CONFIG = {
28
  SCROLL_SMOOTH_BEHAVIOR: 'smooth',
@@ -75,6 +67,7 @@ document.addEventListener('DOMContentLoaded', function () {
75
  const outputViewTabs = Array.from(
76
  document.querySelectorAll('[data-output-view]'),
77
  );
 
78
  const instructionsEl = document.querySelector('.instructions');
79
  const loadingOverlay = document.getElementById('loading-overlay');
80
  let processingLoadingTimer = null;
@@ -322,10 +315,6 @@ document.addEventListener('DOMContentLoaded', function () {
322
  }
323
  loadSampleReport(defaultSample, { scroll: false });
324
  }
325
-
326
- setTimeout(() => {
327
- balanceByColumnCount();
328
- }, GRID_CONFIG.BALANCE_DELAY);
329
  }
330
 
331
  /**
@@ -353,73 +342,6 @@ document.addEventListener('DOMContentLoaded', function () {
353
  buttons[nextIndex].scrollIntoView({ block: 'nearest', inline: 'center' });
354
  }
355
 
356
- /**
357
- * Balances sample button rows by calculating optimal column count for even distribution.
358
- * Keeps row-wise reading order while achieving visual balance (e.g., 5+5 instead of 6+4).
359
- * Uses responsive sizing for better mobile experience.
360
- */
361
- function balanceByColumnCount() {
362
- const container = document.querySelector('.sample-buttons');
363
- if (!container) {
364
- console.warn('Sample buttons container not found');
365
- return;
366
- }
367
-
368
- const cards = container.querySelectorAll('.sample-button').length;
369
- const styles = getComputedStyle(container);
370
- const gap = parseFloat(styles.columnGap) || 12;
371
-
372
- const viewport = window.innerWidth;
373
- const minWidth =
374
- viewport <= GRID_CONFIG.MOBILE_BREAKPOINT
375
- ? GRID_CONFIG.MOBILE_MIN_WIDTH
376
- : GRID_CONFIG.DESKTOP_MIN_WIDTH;
377
-
378
- const containerWidth = container.clientWidth;
379
- const columnsFit = Math.max(
380
- 1,
381
- Math.floor((containerWidth + gap) / (minWidth + gap)),
382
- );
383
-
384
- if (viewport <= GRID_CONFIG.NARROW_BREAKPOINT) {
385
- return;
386
- }
387
-
388
- // Find the column count that provides the most even distribution
389
- let bestCols = columnsFit;
390
- let bestRem = cards % columnsFit;
391
-
392
- for (let cols = columnsFit - 1; cols >= 1; cols--) {
393
- const rem = cards % cols;
394
- if (rem === 0) {
395
- bestCols = cols;
396
- break; // Perfect distribution found
397
- }
398
- if (rem > bestRem) continue; // Worse distribution, skip
399
- bestCols = cols;
400
- bestRem = rem;
401
- }
402
-
403
- // Mobile-specific logic: prefer 2-3 columns for better touch targets
404
- if (viewport <= GRID_CONFIG.MOBILE_BREAKPOINT) {
405
- if (bestCols === 1 && columnsFit >= 2) {
406
- bestCols = 2; // Force at least 2 columns on mobile
407
- } else if (bestCols > 3 && cards >= 6) {
408
- // If we have many columns, prefer 2-3 for mobile UX
409
- const cols2Rem = cards % 2;
410
- const cols3Rem = cards % 3;
411
- if (cols2Rem <= cols3Rem) {
412
- bestCols = 2;
413
- } else {
414
- bestCols = 3;
415
- }
416
- }
417
- }
418
-
419
- // Always apply the balanced column count for optimal visual distribution
420
- container.style.gridTemplateColumns = `repeat(${bestCols}, minmax(${minWidth}px, 1fr))`;
421
- }
422
-
423
  /**
424
  * Loads a sample report into the input area and automatically processes it.
425
  * @param {Object} sample - The sample report data object
@@ -467,14 +389,6 @@ document.addEventListener('DOMContentLoaded', function () {
467
 
468
  loadSampleReports();
469
 
470
- let resizeTimeout;
471
- window.addEventListener('resize', () => {
472
- clearTimeout(resizeTimeout);
473
- resizeTimeout = setTimeout(() => {
474
- balanceByColumnCount();
475
- }, GRID_CONFIG.RESIZE_DEBOUNCE);
476
- });
477
-
478
  /**
479
  * Updates the cache status display in the UI.
480
  * @returns {Promise<void>}
@@ -563,8 +477,8 @@ document.addEventListener('DOMContentLoaded', function () {
563
  }
564
  const mobilePromptToggle = document.getElementById('prompt-toggle-mobile');
565
  if (mobilePromptToggle) mobilePromptToggle.checked = false;
566
- setOutputViewsAvailable(false);
567
  selectOutputView('cards');
 
568
  updateLXToggleStates(); // Disable toggles when output is cleared
569
  updateCopyButtonState();
570
 
@@ -729,7 +643,7 @@ document.addEventListener('DOMContentLoaded', function () {
729
  showPromptView(false);
730
  }
731
 
732
- selectOutputView('cards');
733
 
734
  const hasIntervals = data.segments.some(
735
  (segment) => segment.intervals && segment.intervals.length > 0,
@@ -1171,8 +1085,7 @@ document.addEventListener('DOMContentLoaded', function () {
1171
  */
1172
  function extractLabelInfo(content) {
1173
  const colonIndex = content.indexOf(':');
1174
- const hasLabel =
1175
- colonIndex > 0 && colonIndex < GRID_CONFIG.MAX_LABEL_LENGTH;
1176
 
1177
  return {
1178
  hasLabel,
@@ -1477,7 +1390,7 @@ document.addEventListener('DOMContentLoaded', function () {
1477
  * Selects one of the three output representations.
1478
  * @param {'cards'|'report'|'raw'} view - View to make visible
1479
  */
1480
- function selectOutputView(view) {
1481
  const requestedTab = outputViewTabs.find(
1482
  (tab) => tab.dataset.outputView === view,
1483
  );
@@ -1508,6 +1421,8 @@ document.addEventListener('DOMContentLoaded', function () {
1508
  tab.setAttribute('aria-selected', String(selected));
1509
  tab.tabIndex = selected ? 0 : -1;
1510
  });
 
 
1511
  }
1512
 
1513
  /** Enables alternate representations only when structured output exists. */
@@ -1519,10 +1434,10 @@ document.addEventListener('DOMContentLoaded', function () {
1519
  });
1520
  }
1521
 
1522
- outputViewTabs.forEach((tab, index) => {
1523
- tab.tabIndex = index === 0 ? 0 : -1;
1524
  tab.addEventListener('click', () =>
1525
- selectOutputView(tab.dataset.outputView),
1526
  );
1527
  tab.addEventListener('keydown', (event) => {
1528
  if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key))
@@ -1543,7 +1458,9 @@ document.addEventListener('DOMContentLoaded', function () {
1543
  if (event.key === 'Home') nextIndex = 0;
1544
  if (event.key === 'End') nextIndex = enabledTabs.length - 1;
1545
  enabledTabs[nextIndex].focus();
1546
- selectOutputView(enabledTabs[nextIndex].dataset.outputView);
 
 
1547
  });
1548
  });
1549
 
@@ -1562,7 +1479,7 @@ document.addEventListener('DOMContentLoaded', function () {
1562
  if (rawToggle) {
1563
  rawToggle.addEventListener('change', () => {
1564
  const showRaw = rawToggle.checked;
1565
- selectOutputView(showRaw ? 'raw' : 'cards');
1566
 
1567
  const mobileRawToggle = document.getElementById('raw-toggle-mobile');
1568
  if (mobileRawToggle) {
 
14
 
15
  document.addEventListener('DOMContentLoaded', function () {
16
  // === CONFIGURATION CONSTANTS ===
17
+ const MAX_LABEL_LENGTH = 60;
 
 
 
 
 
 
 
 
18
 
19
  const UI_CONFIG = {
20
  SCROLL_SMOOTH_BEHAVIOR: 'smooth',
 
67
  const outputViewTabs = Array.from(
68
  document.querySelectorAll('[data-output-view]'),
69
  );
70
+ let preferredStructuredView = 'report';
71
  const instructionsEl = document.querySelector('.instructions');
72
  const loadingOverlay = document.getElementById('loading-overlay');
73
  let processingLoadingTimer = null;
 
315
  }
316
  loadSampleReport(defaultSample, { scroll: false });
317
  }
 
 
 
 
318
  }
319
 
320
  /**
 
342
  buttons[nextIndex].scrollIntoView({ block: 'nearest', inline: 'center' });
343
  }
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  /**
346
  * Loads a sample report into the input area and automatically processes it.
347
  * @param {Object} sample - The sample report data object
 
389
 
390
  loadSampleReports();
391
 
 
 
 
 
 
 
 
 
392
  /**
393
  * Updates the cache status display in the UI.
394
  * @returns {Promise<void>}
 
477
  }
478
  const mobilePromptToggle = document.getElementById('prompt-toggle-mobile');
479
  if (mobilePromptToggle) mobilePromptToggle.checked = false;
 
480
  selectOutputView('cards');
481
+ setOutputViewsAvailable(false);
482
  updateLXToggleStates(); // Disable toggles when output is cleared
483
  updateCopyButtonState();
484
 
 
643
  showPromptView(false);
644
  }
645
 
646
+ selectOutputView(preferredStructuredView);
647
 
648
  const hasIntervals = data.segments.some(
649
  (segment) => segment.intervals && segment.intervals.length > 0,
 
1085
  */
1086
  function extractLabelInfo(content) {
1087
  const colonIndex = content.indexOf(':');
1088
+ const hasLabel = colonIndex > 0 && colonIndex < MAX_LABEL_LENGTH;
 
1089
 
1090
  return {
1091
  hasLabel,
 
1390
  * Selects one of the three output representations.
1391
  * @param {'cards'|'report'|'raw'} view - View to make visible
1392
  */
1393
+ function selectOutputView(view, { remember = false } = {}) {
1394
  const requestedTab = outputViewTabs.find(
1395
  (tab) => tab.dataset.outputView === view,
1396
  );
 
1421
  tab.setAttribute('aria-selected', String(selected));
1422
  tab.tabIndex = selected ? 0 : -1;
1423
  });
1424
+
1425
+ if (remember) preferredStructuredView = view;
1426
  }
1427
 
1428
  /** Enables alternate representations only when structured output exists. */
 
1434
  });
1435
  }
1436
 
1437
+ outputViewTabs.forEach((tab) => {
1438
+ tab.tabIndex = tab.getAttribute('aria-selected') === 'true' ? 0 : -1;
1439
  tab.addEventListener('click', () =>
1440
+ selectOutputView(tab.dataset.outputView, { remember: true }),
1441
  );
1442
  tab.addEventListener('keydown', (event) => {
1443
  if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key))
 
1458
  if (event.key === 'Home') nextIndex = 0;
1459
  if (event.key === 'End') nextIndex = enabledTabs.length - 1;
1460
  enabledTabs[nextIndex].focus();
1461
+ selectOutputView(enabledTabs[nextIndex].dataset.outputView, {
1462
+ remember: true,
1463
+ });
1464
  });
1465
  });
1466
 
 
1479
  if (rawToggle) {
1480
  rawToggle.addEventListener('change', () => {
1481
  const showRaw = rawToggle.checked;
1482
+ selectOutputView(showRaw ? 'raw' : 'cards', { remember: true });
1483
 
1484
  const mobileRawToggle = document.getElementById('raw-toggle-mobile');
1485
  if (mobileRawToggle) {
static/style.css CHANGED
@@ -2378,6 +2378,24 @@ body.page-wrapper {
2378
  text-decoration: none;
2379
  }
2380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2381
  .hero-attribution .material-symbols-outlined {
2382
  font-size: 14px;
2383
  }
@@ -2442,40 +2460,29 @@ body.page-wrapper {
2442
  }
2443
 
2444
  .sample-rail-shell::after {
2445
- position: absolute;
2446
- top: 0;
2447
- right: 0;
2448
- bottom: 7px;
2449
- width: 34px;
2450
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.96));
2451
- content: '';
2452
- pointer-events: none;
2453
  }
2454
 
2455
  .sample-buttons {
2456
- display: flex;
2457
- justify-content: flex-start;
2458
  gap: 8px;
2459
  margin: 0;
2460
- padding: 2px 28px 7px 2px;
2461
- overflow-x: auto;
2462
- overscroll-behavior-inline: contain;
2463
- scrollbar-color: #c5d0e1 transparent;
2464
- scrollbar-width: thin;
2465
  }
2466
 
2467
  .sample-button {
2468
- flex: 0 0 auto;
2469
- min-width: 136px;
2470
  padding: 9px 11px;
2471
  border-radius: 10px;
2472
  box-shadow: none;
2473
  }
2474
 
2475
  .sample-title {
 
2476
  margin-bottom: 4px;
2477
  font-size: 13px;
2478
- white-space: nowrap;
2479
  }
2480
 
2481
  .sample-modality {
@@ -2939,6 +2946,11 @@ body.page-wrapper {
2939
  width: 130px;
2940
  }
2941
 
 
 
 
 
 
2942
  .safety-strip {
2943
  align-items: flex-start;
2944
  }
@@ -2953,6 +2965,39 @@ body.page-wrapper {
2953
  display: none;
2954
  }
2955
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2956
  .transformation-studio.text-area-container {
2957
  display: flex !important;
2958
  flex-direction: column;
 
2378
  text-decoration: none;
2379
  }
2380
 
2381
+ .hero-links {
2382
+ display: flex;
2383
+ justify-content: flex-end;
2384
+ flex-wrap: wrap;
2385
+ gap: 5px 13px;
2386
+ }
2387
+
2388
+ .hero-attribution a:hover {
2389
+ text-decoration: underline;
2390
+ text-underline-offset: 3px;
2391
+ }
2392
+
2393
+ .hero-attribution a:focus-visible {
2394
+ border-radius: 4px;
2395
+ outline: 3px solid rgba(26, 115, 232, 0.35);
2396
+ outline-offset: 3px;
2397
+ }
2398
+
2399
  .hero-attribution .material-symbols-outlined {
2400
  font-size: 14px;
2401
  }
 
2460
  }
2461
 
2462
  .sample-rail-shell::after {
2463
+ content: none;
 
 
 
 
 
 
 
2464
  }
2465
 
2466
  .sample-buttons {
2467
+ display: grid;
2468
+ grid-template-columns: repeat(5, minmax(0, 1fr));
2469
  gap: 8px;
2470
  margin: 0;
2471
+ padding: 2px;
 
 
 
 
2472
  }
2473
 
2474
  .sample-button {
2475
+ min-width: 0;
 
2476
  padding: 9px 11px;
2477
  border-radius: 10px;
2478
  box-shadow: none;
2479
  }
2480
 
2481
  .sample-title {
2482
+ min-height: 2.4em;
2483
  margin-bottom: 4px;
2484
  font-size: 13px;
2485
+ line-height: 1.2;
2486
  }
2487
 
2488
  .sample-modality {
 
2946
  width: 130px;
2947
  }
2948
 
2949
+ .hero-links {
2950
+ width: 100%;
2951
+ justify-content: flex-start;
2952
+ }
2953
+
2954
  .safety-strip {
2955
  align-items: flex-start;
2956
  }
 
2965
  display: none;
2966
  }
2967
 
2968
+ .sample-rail-shell::after {
2969
+ position: absolute;
2970
+ top: 0;
2971
+ right: 0;
2972
+ bottom: 7px;
2973
+ width: 34px;
2974
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.96));
2975
+ content: '';
2976
+ pointer-events: none;
2977
+ }
2978
+
2979
+ .sample-buttons {
2980
+ display: flex;
2981
+ justify-content: flex-start;
2982
+ padding: 2px 28px 7px 2px;
2983
+ overflow-x: auto;
2984
+ overscroll-behavior-inline: contain;
2985
+ scrollbar-color: #c5d0e1 transparent;
2986
+ scrollbar-width: thin;
2987
+ }
2988
+
2989
+ .sample-button {
2990
+ flex: 0 0 auto;
2991
+ min-width: 136px;
2992
+ }
2993
+
2994
+ .sample-title {
2995
+ min-height: 0;
2996
+ overflow: hidden;
2997
+ text-overflow: ellipsis;
2998
+ white-space: nowrap;
2999
+ }
3000
+
3001
  .transformation-studio.text-area-container {
3002
  display: flex !important;
3003
  flex-direction: column;
templates/index.html CHANGED
@@ -95,15 +95,26 @@
95
  width="174"
96
  height="25"
97
  />
98
- <a
99
- href="https://developers.googleblog.com/en/introducing-langextract-a-gemini-powered-information-extraction-library"
100
- target="_blank"
101
- rel="noopener noreferrer"
102
- >Read the LangExtract story
103
- <span class="material-symbols-outlined" aria-hidden="true"
104
- >open_in_new</span
105
- ></a
106
- >
 
 
 
 
 
 
 
 
 
 
 
107
  </div>
108
  <div class="safety-strip" role="note">
109
  <span class="material-symbols-outlined" aria-hidden="true"
@@ -121,11 +132,9 @@
121
  <div class="sample-heading-row">
122
  <div>
123
  <p class="section-kicker">Start with a study</p>
124
- <h2 id="sample-heading">Explore a cached report</h2>
125
  </div>
126
- <p>
127
- Choose any modality. Results appear instantly from the demo cache.
128
- </p>
129
  </div>
130
  <div class="sample-rail-shell">
131
  <div class="sample-buttons" aria-label="Sample radiology reports"></div>
@@ -217,16 +226,6 @@
217
  <h2>Structured extraction</h2>
218
  </div>
219
  <div class="output-view-tabs" role="tablist" aria-label="Output view">
220
- <button
221
- class="output-view-tab active"
222
- id="findings-tab"
223
- data-output-view="cards"
224
- role="tab"
225
- aria-selected="true"
226
- aria-controls="findings-cards"
227
- >
228
- Findings
229
- </button>
230
  <button
231
  class="output-view-tab"
232
  id="report-tab"
@@ -239,6 +238,16 @@
239
  >
240
  Report
241
  </button>
 
 
 
 
 
 
 
 
 
 
242
  <button
243
  class="output-view-tab"
244
  id="raw-tab"
 
95
  width="174"
96
  height="25"
97
  />
98
+ <div class="hero-links">
99
+ <a
100
+ href="https://github.com/google/langextract"
101
+ target="_blank"
102
+ rel="noopener noreferrer"
103
+ >LangExtract on GitHub
104
+ <span class="material-symbols-outlined" aria-hidden="true"
105
+ >open_in_new</span
106
+ ></a
107
+ >
108
+ <a
109
+ href="https://developers.googleblog.com/en/introducing-langextract-a-gemini-powered-information-extraction-library"
110
+ target="_blank"
111
+ rel="noopener noreferrer"
112
+ >LangExtract release blog
113
+ <span class="material-symbols-outlined" aria-hidden="true"
114
+ >open_in_new</span
115
+ ></a
116
+ >
117
+ </div>
118
  </div>
119
  <div class="safety-strip" role="note">
120
  <span class="material-symbols-outlined" aria-hidden="true"
 
132
  <div class="sample-heading-row">
133
  <div>
134
  <p class="section-kicker">Start with a study</p>
135
+ <h2 id="sample-heading">Explore sample reports</h2>
136
  </div>
137
+ <p>Choose a study. Built-in examples can use saved or live results.</p>
 
 
138
  </div>
139
  <div class="sample-rail-shell">
140
  <div class="sample-buttons" aria-label="Sample radiology reports"></div>
 
226
  <h2>Structured extraction</h2>
227
  </div>
228
  <div class="output-view-tabs" role="tablist" aria-label="Output view">
 
 
 
 
 
 
 
 
 
 
229
  <button
230
  class="output-view-tab"
231
  id="report-tab"
 
238
  >
239
  Report
240
  </button>
241
+ <button
242
+ class="output-view-tab active"
243
+ id="findings-tab"
244
+ data-output-view="cards"
245
+ role="tab"
246
+ aria-selected="true"
247
+ aria-controls="findings-cards"
248
+ >
249
+ Findings
250
+ </button>
251
  <button
252
  class="output-view-tab"
253
  id="raw-tab"
test_app.py CHANGED
@@ -494,6 +494,55 @@ class TestFrontendAndProjectConfiguration(unittest.TestCase):
494
  self.assertIn('data-output-view="raw"', template)
495
  self.assertIn('id="prompt-toggle"', template)
496
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  def test_mobile_and_reduced_motion_studio_rules_are_defined(self):
498
  script = Path('static/script.js').read_text()
499
  stylesheet = Path('static/style.css').read_text()
 
494
  self.assertIn('data-output-view="raw"', template)
495
  self.assertIn('id="prompt-toggle"', template)
496
 
497
+ def test_langextract_links_and_sample_copy_are_professional(self):
498
+ template = Path('templates/index.html').read_text()
499
+ stylesheet = Path('static/style.css').read_text()
500
+
501
+ self.assertIn('href="https://github.com/google/langextract"', template)
502
+ self.assertIn('LangExtract on GitHub', template)
503
+ self.assertIn('LangExtract release blog', template)
504
+ self.assertGreaterEqual(template.count('rel="noopener noreferrer"'), 2)
505
+ self.assertIn('<h2 id="sample-heading">Explore sample reports</h2>', template)
506
+ self.assertIn('Choose a study.', template)
507
+ self.assertNotIn('Explore a cached report', template)
508
+ self.assertNotIn('Results appear instantly from the demo cache.', template)
509
+ self.assertIn('.hero-attribution a:focus-visible', stylesheet)
510
+
511
+ def test_sample_grid_and_report_default_preserve_responsive_fallbacks(self):
512
+ script = Path('static/script.js').read_text()
513
+ stylesheet = Path('static/style.css').read_text()
514
+ template = Path('templates/index.html').read_text()
515
+
516
+ self.assertIn('grid-template-columns: repeat(5, minmax(0, 1fr));', stylesheet)
517
+ mobile_rules = stylesheet[stylesheet.index('@media (max-width: 768px)') :]
518
+ self.assertIn('.sample-buttons {', mobile_rules)
519
+ self.assertIn('display: flex;', mobile_rules)
520
+ self.assertIn('overflow-x: auto;', mobile_rules)
521
+ self.assertLess(
522
+ template.index('id="report-tab"'), template.index('id="findings-tab"')
523
+ )
524
+ self.assertIn("let preferredStructuredView = 'report';", script)
525
+ self.assertIn('selectOutputView(preferredStructuredView);', script)
526
+ self.assertGreaterEqual(script.count('remember: true'), 3)
527
+ self.assertIn(
528
+ "selectOutputView(showRaw ? 'raw' : 'cards', { remember: true });",
529
+ script,
530
+ )
531
+ self.assertIn("tab.getAttribute('aria-selected') === 'true'", script)
532
+ self.assertLess(
533
+ script.index(
534
+ "selectOutputView('cards');",
535
+ script.index('predictButton.addEventListener'),
536
+ ),
537
+ script.index(
538
+ 'setOutputViewsAvailable(false);',
539
+ script.index('predictButton.addEventListener'),
540
+ ),
541
+ )
542
+ self.assertNotIn('balanceByColumnCount', script)
543
+ self.assertNotIn('BALANCE_DELAY', script)
544
+ self.assertNotIn('RESIZE_DEBOUNCE', script)
545
+
546
  def test_mobile_and_reduced_motion_studio_rules_are_defined(self):
547
  script = Path('static/script.js').read_text()
548
  stylesheet = Path('static/style.css').read_text()