steven0226 commited on
Commit
bc7098f
·
verified ·
1 Parent(s): 9d60e8e

deploy: 同步 UI 驗收版本

Browse files
docs/assets/desktop.png CHANGED

Git LFS Details

  • SHA256: bd64688e1cc125aedb29621daea4e93fd08a99f5e15355a75bfc82b08e2c7c1b
  • Pointer size: 131 Bytes
  • Size of remote file: 311 kB

Git LFS Details

  • SHA256: 00c8b0c2babd6d4fcabaae894fd888b7c1b72e5cbc7884583b6999275bd8bdd9
  • Pointer size: 131 Bytes
  • Size of remote file: 314 kB
scripts/verify_ui_layout.py CHANGED
@@ -52,13 +52,61 @@ def _metrics(locator: Locator) -> dict[str, float | str]:
52
  width: Math.round(box.width),
53
  height: Math.round(box.height),
54
  fontSize: style.fontSize,
55
- lineHeight: style.lineHeight
 
 
56
  };
57
  }"""
58
  )
59
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  def _page_metrics(page: Page) -> dict:
 
62
  metrics = {
63
  "viewportWidth": page.evaluate("window.innerWidth"),
64
  "documentWidth": page.evaluate("document.documentElement.scrollWidth"),
@@ -70,11 +118,18 @@ def _page_metrics(page: Page) -> dict:
70
  "resultLegend": _metrics(page.locator(".result-legend").first),
71
  "legendTitle": _metrics(page.locator(".result-legend h2").first),
72
  "legendItem": _metrics(page.locator(".legend-items li").first),
 
73
  "legendLabel": _metrics(page.locator(".legend-items strong").first),
 
 
74
  "sourceHeading": _metrics(page.locator(".source-brief h3").first),
75
  "sourceText": _metrics(page.locator(".source-brief p").first),
76
  "demoHeading": _metrics(page.locator(".demo-heading h3").first),
77
  "demoText": _metrics(page.locator(".demo-heading p").first),
 
 
 
 
78
  "demoSelectorAccessible": page.get_by_label("範例文件").is_visible(),
79
  "documentPickerLabelVisible": page.locator(
80
  "#document-upload"
@@ -91,6 +146,7 @@ def _page_metrics(page: Page) -> dict:
91
  "settingsPanel": _metrics(page.locator(".settings-section").first),
92
  "actionPanel": _metrics(page.locator(".action-section").first),
93
  "taskHeading": _metrics(page.locator(".task-heading").first),
 
94
  "taskTitle": _metrics(page.locator(".task-heading h2").first),
95
  "taskCopy": _metrics(page.locator(".task-heading p").first),
96
  "fieldLabel": _metrics(
@@ -540,14 +596,62 @@ def main() -> None:
540
  encoding="utf-8",
541
  )
542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  assert desktop["viewportWidth"] == desktop["documentWidth"]
544
  assert mobile["viewportWidth"] == mobile["documentWidth"]
545
  assert float(desktop["bodyText"]["fontSize"].removesuffix("px")) >= 20
546
  assert float(desktop["legendTitle"]["fontSize"].removesuffix("px")) >= 22
547
  assert float(desktop["legendItem"]["fontSize"].removesuffix("px")) >= 18
548
- assert float(desktop["legendLabel"]["fontSize"].removesuffix("px")) >= 20
 
 
 
 
549
  assert float(desktop["sourceText"]["fontSize"].removesuffix("px")) >= 19
550
  assert float(desktop["demoText"]["fontSize"].removesuffix("px")) >= 18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  assert float(desktop["taskTitle"]["fontSize"].removesuffix("px")) >= 28
552
  assert float(desktop["taskCopy"]["fontSize"].removesuffix("px")) >= 20
553
  assert float(desktop["fieldLabel"]["fontSize"].removesuffix("px")) >= 18
@@ -558,7 +662,27 @@ def main() -> None:
558
  assert float(desktop["statusText"]["fontSize"].removesuffix("px")) >= 19
559
  assert float(mobile["bodyText"]["fontSize"].removesuffix("px")) >= 19
560
  assert float(mobile["legendItem"]["fontSize"].removesuffix("px")) >= 18
 
 
 
 
 
561
  assert float(mobile["sourceText"]["fontSize"].removesuffix("px")) >= 19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  assert float(mobile["fieldLabel"]["fontSize"].removesuffix("px")) >= 18
563
  assert desktop["masthead"]["height"] <= 150
564
  assert desktop["resultLegend"]["height"] <= 140
@@ -569,6 +693,16 @@ def main() -> None:
569
  assert desktop["uploadPromptInsetTop"] >= 6
570
  assert desktop["uploadPromptInsetBottom"] >= 6
571
  assert desktop["documentHeight"] <= 1900
 
 
 
 
 
 
 
 
 
 
572
  assert (
573
  abs(
574
  desktop["settingsPanel"]["y"]
 
52
  width: Math.round(box.width),
53
  height: Math.round(box.height),
54
  fontSize: style.fontSize,
55
+ fontWeight: style.fontWeight,
56
+ lineHeight: style.lineHeight,
57
+ borderTopWidth: style.borderTopWidth
58
  };
59
  }"""
60
  )
61
 
62
 
63
+ def _all_metrics(locator: Locator) -> list[dict[str, float | str]]:
64
+ return locator.evaluate_all(
65
+ """elements => elements.map(element => {
66
+ const box = element.getBoundingClientRect();
67
+ const style = window.getComputedStyle(element);
68
+ return {
69
+ x: Math.round(box.x),
70
+ y: Math.round(box.y),
71
+ width: Math.round(box.width),
72
+ height: Math.round(box.height),
73
+ fontSize: style.fontSize,
74
+ fontWeight: style.fontWeight,
75
+ lineHeight: style.lineHeight,
76
+ borderTopWidth: style.borderTopWidth
77
+ };
78
+ })"""
79
+ )
80
+
81
+
82
+ def _dropdown_metrics(page: Page) -> dict:
83
+ combo = page.locator("#demo-selector [role='combobox']").first
84
+ combo.click()
85
+ listbox = page.locator('#demo-selector [role="listbox"]').first
86
+ option = page.locator(
87
+ '#demo-selector [role="listbox"] [role="option"]'
88
+ ).first
89
+ option.wait_for(state="visible")
90
+ metrics = {
91
+ "control": _metrics(combo),
92
+ "listbox": _metrics(listbox),
93
+ "option": _metrics(option),
94
+ "frontmostRole": option.evaluate(
95
+ """element => {
96
+ const box = element.getBoundingClientRect();
97
+ return document.elementFromPoint(
98
+ box.left + 16,
99
+ box.top + box.height / 2
100
+ )?.getAttribute("role") ?? "";
101
+ }"""
102
+ ),
103
+ }
104
+ page.keyboard.press("Escape")
105
+ return metrics
106
+
107
+
108
  def _page_metrics(page: Page) -> dict:
109
+ dropdown = _dropdown_metrics(page)
110
  metrics = {
111
  "viewportWidth": page.evaluate("window.innerWidth"),
112
  "documentWidth": page.evaluate("document.documentElement.scrollWidth"),
 
118
  "resultLegend": _metrics(page.locator(".result-legend").first),
119
  "legendTitle": _metrics(page.locator(".result-legend h2").first),
120
  "legendItem": _metrics(page.locator(".legend-items li").first),
121
+ "legendCards": _all_metrics(page.locator(".legend-items li")),
122
  "legendLabel": _metrics(page.locator(".legend-items strong").first),
123
+ "legendAction": _metrics(page.locator(".legend-action").first),
124
+ "legendDots": _all_metrics(page.locator(".legend-dot")),
125
  "sourceHeading": _metrics(page.locator(".source-brief h3").first),
126
  "sourceText": _metrics(page.locator(".source-brief p").first),
127
  "demoHeading": _metrics(page.locator(".demo-heading h3").first),
128
  "demoText": _metrics(page.locator(".demo-heading p").first),
129
+ "demoDropdownControl": dropdown["control"],
130
+ "demoDropdownListbox": dropdown["listbox"],
131
+ "demoDropdownOption": dropdown["option"],
132
+ "demoDropdownFrontmostRole": dropdown["frontmostRole"],
133
  "demoSelectorAccessible": page.get_by_label("範例文件").is_visible(),
134
  "documentPickerLabelVisible": page.locator(
135
  "#document-upload"
 
146
  "settingsPanel": _metrics(page.locator(".settings-section").first),
147
  "actionPanel": _metrics(page.locator(".action-section").first),
148
  "taskHeading": _metrics(page.locator(".task-heading").first),
149
+ "stepMarkers": _all_metrics(page.locator(".task-step")),
150
  "taskTitle": _metrics(page.locator(".task-heading h2").first),
151
  "taskCopy": _metrics(page.locator(".task-heading p").first),
152
  "fieldLabel": _metrics(
 
596
  encoding="utf-8",
597
  )
598
 
599
+ desktop_step_x = [marker["x"] for marker in desktop["stepMarkers"]]
600
+ mobile_step_x = [marker["x"] for marker in mobile["stepMarkers"]]
601
+ desktop_dot_widths = [dot["width"] for dot in desktop["legendDots"]]
602
+ desktop_dot_heights = [dot["height"] for dot in desktop["legendDots"]]
603
+ mobile_dot_widths = [dot["width"] for dot in mobile["legendDots"]]
604
+ mobile_dot_heights = [dot["height"] for dot in mobile["legendDots"]]
605
+
606
+ for viewport_name, metrics in (
607
+ ("wide", wide),
608
+ ("desktop", desktop),
609
+ ("mobile", mobile),
610
+ ):
611
+ legend_cards = metrics["legendCards"]
612
+ assert len(legend_cards) == 3, (
613
+ f"{viewport_name} viewport should render exactly three legend cards"
614
+ )
615
+ legend_widths = [card["width"] for card in legend_cards]
616
+ legend_heights = [card["height"] for card in legend_cards]
617
+ assert max(legend_widths) - min(legend_widths) <= 1, (
618
+ f"{viewport_name} legend cards should have equal widths: {legend_widths}"
619
+ )
620
+ assert max(legend_heights) - min(legend_heights) <= 1, (
621
+ f"{viewport_name} legend cards should have equal heights: {legend_heights}"
622
+ )
623
+
624
  assert desktop["viewportWidth"] == desktop["documentWidth"]
625
  assert mobile["viewportWidth"] == mobile["documentWidth"]
626
  assert float(desktop["bodyText"]["fontSize"].removesuffix("px")) >= 20
627
  assert float(desktop["legendTitle"]["fontSize"].removesuffix("px")) >= 22
628
  assert float(desktop["legendItem"]["fontSize"].removesuffix("px")) >= 18
629
+ assert float(desktop["legendLabel"]["fontSize"].removesuffix("px")) >= 22
630
+ assert float(desktop["legendAction"]["fontSize"].removesuffix("px")) >= 20
631
+ assert len(desktop["legendDots"]) == 3
632
+ assert desktop_dot_widths == [18, 18, 18]
633
+ assert desktop_dot_heights == [18, 18, 18]
634
  assert float(desktop["sourceText"]["fontSize"].removesuffix("px")) >= 19
635
  assert float(desktop["demoText"]["fontSize"].removesuffix("px")) >= 18
636
+ assert (
637
+ float(desktop["demoDropdownControl"]["fontSize"].removesuffix("px"))
638
+ >= 21
639
+ )
640
+ assert (
641
+ float(desktop["demoDropdownOption"]["fontSize"].removesuffix("px"))
642
+ >= 20
643
+ )
644
+ assert desktop["demoDropdownOption"]["height"] >= 44
645
+ assert (
646
+ desktop["demoDropdownListbox"]["y"]
647
+ >= desktop["demoDropdownControl"]["y"]
648
+ + desktop["demoDropdownControl"]["height"]
649
+ )
650
+ assert desktop["demoDropdownFrontmostRole"] == "option"
651
+ assert len(desktop["stepMarkers"]) == 4
652
+ assert all(marker["width"] >= 46 for marker in desktop["stepMarkers"])
653
+ assert all(marker["height"] >= 46 for marker in desktop["stepMarkers"])
654
+ assert max(desktop_step_x) - min(desktop_step_x) <= 1
655
  assert float(desktop["taskTitle"]["fontSize"].removesuffix("px")) >= 28
656
  assert float(desktop["taskCopy"]["fontSize"].removesuffix("px")) >= 20
657
  assert float(desktop["fieldLabel"]["fontSize"].removesuffix("px")) >= 18
 
662
  assert float(desktop["statusText"]["fontSize"].removesuffix("px")) >= 19
663
  assert float(mobile["bodyText"]["fontSize"].removesuffix("px")) >= 19
664
  assert float(mobile["legendItem"]["fontSize"].removesuffix("px")) >= 18
665
+ assert float(mobile["legendLabel"]["fontSize"].removesuffix("px")) >= 21
666
+ assert float(mobile["legendAction"]["fontSize"].removesuffix("px")) >= 19
667
+ assert len(mobile["legendDots"]) == 3
668
+ assert mobile_dot_widths == [18, 18, 18]
669
+ assert mobile_dot_heights == [18, 18, 18]
670
  assert float(mobile["sourceText"]["fontSize"].removesuffix("px")) >= 19
671
+ assert (
672
+ float(mobile["demoDropdownOption"]["fontSize"].removesuffix("px"))
673
+ >= 20
674
+ )
675
+ assert mobile["demoDropdownOption"]["height"] >= 44
676
+ assert (
677
+ mobile["demoDropdownListbox"]["y"]
678
+ >= mobile["demoDropdownControl"]["y"]
679
+ + mobile["demoDropdownControl"]["height"]
680
+ )
681
+ assert mobile["demoDropdownFrontmostRole"] == "option"
682
+ assert len(mobile["stepMarkers"]) == 4
683
+ assert all(marker["width"] >= 46 for marker in mobile["stepMarkers"])
684
+ assert all(marker["height"] >= 46 for marker in mobile["stepMarkers"])
685
+ assert max(mobile_step_x) - min(mobile_step_x) <= 1
686
  assert float(mobile["fieldLabel"]["fontSize"].removesuffix("px")) >= 18
687
  assert desktop["masthead"]["height"] <= 150
688
  assert desktop["resultLegend"]["height"] <= 140
 
693
  assert desktop["uploadPromptInsetTop"] >= 6
694
  assert desktop["uploadPromptInsetBottom"] >= 6
695
  assert desktop["documentHeight"] <= 1900
696
+ assert (
697
+ float(
698
+ desktop["settingsPanel"]["borderTopWidth"].removesuffix("px")
699
+ )
700
+ >= 8
701
+ )
702
+ assert (
703
+ float(desktop["actionPanel"]["borderTopWidth"].removesuffix("px"))
704
+ >= 8
705
+ )
706
  assert (
707
  abs(
708
  desktop["settingsPanel"]["y"]
src/doc_inspector/ui.py CHANGED
@@ -290,6 +290,8 @@ gradio-app {
290
  .legend-items {
291
  display: grid;
292
  grid-template-columns: repeat(3, minmax(0, 1fr));
 
 
293
  gap: 10px;
294
  margin: 0;
295
  padding: 0;
@@ -298,10 +300,13 @@ gradio-app {
298
 
299
  .legend-items li {
300
  display: flex;
 
301
  min-width: 0;
302
- min-height: 64px;
303
- gap: 9px;
 
304
  align-items: center;
 
305
  padding: 9px 11px;
306
  border: 1px solid;
307
  border-radius: var(--radius-sm);
@@ -333,7 +338,7 @@ gradio-app {
333
 
334
  .legend-items strong {
335
  color: var(--ui-ink);
336
- font-size: 20px;
337
  font-weight: 800;
338
  line-height: 1.2;
339
  }
@@ -341,16 +346,22 @@ gradio-app {
341
  .legend-action {
342
  margin-top: 2px;
343
  color: var(--ui-muted);
344
- font-size: 18px;
345
  line-height: 1.3;
346
  }
347
 
348
  .legend-dot {
349
- width: 14px;
350
- height: 14px;
351
- flex: 0 0 14px;
 
 
 
 
 
 
352
  border-radius: 50%;
353
- box-shadow: 0 0 0 3px oklch(99% 0 0 / 0.72);
354
  }
355
 
356
  .legend-dot-red {
@@ -376,26 +387,40 @@ gradio-app {
376
  }
377
 
378
  .workflow-section {
 
379
  gap: 8px !important;
380
  min-width: 0 !important;
381
  padding: 16px 28px !important;
382
  border: 0 !important;
383
  border-radius: 0 !important;
384
- background: transparent !important;
385
  box-shadow: none !important;
386
  }
387
 
388
  .workflow-section + .workflow-section {
389
- border-top: 1px solid var(--ui-border) !important;
 
 
 
 
 
390
  }
391
 
392
  .action-section {
393
  background: var(--ui-surface-soft) !important;
394
  }
395
 
 
 
 
 
 
 
 
 
396
  .task-heading {
397
  display: flex;
398
- gap: 13px;
399
  align-items: flex-start;
400
  margin: 0;
401
  }
@@ -433,17 +458,26 @@ gradio-app {
433
 
434
  .task-step {
435
  display: inline-flex;
436
- width: 38px;
437
- height: 38px;
438
- flex: 0 0 38px;
439
  align-items: center;
440
  justify-content: center;
441
  border-radius: 50%;
442
  background: var(--ui-primary);
443
  color: #ffffff;
444
- font-size: 20px;
445
  font-weight: 800;
446
  line-height: 1;
 
 
 
 
 
 
 
 
 
447
  }
448
 
449
  .upload-control {
@@ -512,7 +546,8 @@ gradio-app {
512
  .field-control [role="combobox"],
513
  .field-control button {
514
  min-height: 56px !important;
515
- font-size: 20px !important;
 
516
  }
517
 
518
  .field-control,
@@ -651,13 +686,52 @@ gradio-app {
651
  #demo-selector [role="combobox"],
652
  #demo-selector button {
653
  min-height: 56px !important;
654
- font-size: 20px !important;
 
655
  }
656
 
657
  #demo-selector {
658
  min-height: 56px !important;
659
  }
660
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  .demo-btn {
662
  min-height: 56px !important;
663
  border: 1px solid var(--ui-primary) !important;
@@ -938,9 +1012,9 @@ gradio-app {
938
  }
939
 
940
  .results-heading {
941
- margin-top: 22px;
942
  margin-bottom: 10px;
943
- padding: 0 4px;
944
  }
945
 
946
  .action-plan {
@@ -1278,7 +1352,7 @@ footer {
1278
 
1279
  .result-legend {
1280
  display: block;
1281
- padding: 12px;
1282
  }
1283
 
1284
  .result-legend h2 {
@@ -1313,7 +1387,7 @@ footer {
1313
  }
1314
 
1315
  .legend-items strong {
1316
- font-size: 20px;
1317
  }
1318
 
1319
  .legend-copy {
@@ -1324,7 +1398,7 @@ footer {
1324
 
1325
  .legend-action {
1326
  margin-top: 0;
1327
- font-size: 18px;
1328
  line-height: 1.25;
1329
  }
1330
 
@@ -1358,6 +1432,10 @@ footer {
1358
  padding: 14px 16px !important;
1359
  }
1360
 
 
 
 
 
1361
  .task-heading {
1362
  gap: 10px;
1363
  }
@@ -1417,7 +1495,8 @@ footer {
1417
  }
1418
 
1419
  .results-heading {
1420
- margin-top: 22px;
 
1421
  }
1422
 
1423
  .action-plan-header {
 
290
  .legend-items {
291
  display: grid;
292
  grid-template-columns: repeat(3, minmax(0, 1fr));
293
+ grid-auto-rows: 1fr;
294
+ align-items: stretch;
295
  gap: 10px;
296
  margin: 0;
297
  padding: 0;
 
300
 
301
  .legend-items li {
302
  display: flex;
303
+ height: 100%;
304
  min-width: 0;
305
+ min-height: 68px;
306
+ gap: 10px;
307
+ align-self: stretch;
308
  align-items: center;
309
+ box-sizing: border-box;
310
  padding: 9px 11px;
311
  border: 1px solid;
312
  border-radius: var(--radius-sm);
 
338
 
339
  .legend-items strong {
340
  color: var(--ui-ink);
341
+ font-size: 22px;
342
  font-weight: 800;
343
  line-height: 1.2;
344
  }
 
346
  .legend-action {
347
  margin-top: 2px;
348
  color: var(--ui-muted);
349
+ font-size: 20px;
350
  line-height: 1.3;
351
  }
352
 
353
  .legend-dot {
354
+ width: 18px;
355
+ min-width: 18px;
356
+ max-width: 18px;
357
+ height: 18px;
358
+ min-height: 18px;
359
+ max-height: 18px;
360
+ flex: 0 0 18px;
361
+ box-sizing: border-box;
362
+ border: 2px solid oklch(99% 0 0 / 0.84);
363
  border-radius: 50%;
364
+ box-shadow: none;
365
  }
366
 
367
  .legend-dot-red {
 
387
  }
388
 
389
  .workflow-section {
390
+ position: relative;
391
  gap: 8px !important;
392
  min-width: 0 !important;
393
  padding: 16px 28px !important;
394
  border: 0 !important;
395
  border-radius: 0 !important;
396
+ background: var(--ui-surface) !important;
397
  box-shadow: none !important;
398
  }
399
 
400
  .workflow-section + .workflow-section {
401
+ border-top: 10px solid var(--ui-bg) !important;
402
+ box-shadow: inset 0 2px 0 var(--ui-border) !important;
403
+ }
404
+
405
+ .settings-section {
406
+ background: oklch(98.4% 0.007 205) !important;
407
  }
408
 
409
  .action-section {
410
  background: var(--ui-surface-soft) !important;
411
  }
412
 
413
+ .workbench .form {
414
+ overflow: visible !important;
415
+ }
416
+
417
+ .workflow-section:has([role="listbox"]) {
418
+ z-index: 20;
419
+ }
420
+
421
  .task-heading {
422
  display: flex;
423
+ gap: 15px;
424
  align-items: flex-start;
425
  margin: 0;
426
  }
 
458
 
459
  .task-step {
460
  display: inline-flex;
461
+ width: 46px;
462
+ height: 46px;
463
+ flex: 0 0 46px;
464
  align-items: center;
465
  justify-content: center;
466
  border-radius: 50%;
467
  background: var(--ui-primary);
468
  color: #ffffff;
469
+ font-size: 23px;
470
  font-weight: 800;
471
  line-height: 1;
472
+ box-shadow: 0 0 0 4px var(--ui-surface);
473
+ }
474
+
475
+ .settings-section .task-step {
476
+ box-shadow: 0 0 0 4px oklch(98.4% 0.007 205);
477
+ }
478
+
479
+ .action-section .task-step {
480
+ box-shadow: 0 0 0 4px var(--ui-surface-soft);
481
  }
482
 
483
  .upload-control {
 
546
  .field-control [role="combobox"],
547
  .field-control button {
548
  min-height: 56px !important;
549
+ font-size: 21px !important;
550
+ font-weight: 620 !important;
551
  }
552
 
553
  .field-control,
 
686
  #demo-selector [role="combobox"],
687
  #demo-selector button {
688
  min-height: 56px !important;
689
+ font-size: 21px !important;
690
+ font-weight: 620 !important;
691
  }
692
 
693
  #demo-selector {
694
  min-height: 56px !important;
695
  }
696
 
697
+ .gradio-container [role="listbox"] {
698
+ position: absolute !important;
699
+ top: calc(100% + 6px) !important;
700
+ right: 0 !important;
701
+ bottom: auto !important;
702
+ left: 0 !important;
703
+ width: 100% !important;
704
+ max-height: min(320px, 42vh) !important;
705
+ overflow-y: auto !important;
706
+ box-sizing: border-box !important;
707
+ padding: 6px !important;
708
+ border: 1px solid var(--ui-border-strong) !important;
709
+ border-radius: var(--radius-md) !important;
710
+ background: var(--ui-surface) !important;
711
+ box-shadow: 0 12px 28px oklch(24% 0.036 205 / 0.16) !important;
712
+ }
713
+
714
+ .gradio-container [role="listbox"] [role="option"] {
715
+ min-height: 46px !important;
716
+ padding: 9px 12px !important;
717
+ border-radius: var(--radius-sm) !important;
718
+ color: var(--ui-ink) !important;
719
+ font-size: 20px !important;
720
+ font-weight: 620 !important;
721
+ line-height: 1.4 !important;
722
+ }
723
+
724
+ .gradio-container [role="listbox"] [role="option"]:hover,
725
+ .gradio-container [role="listbox"] [role="option"].active {
726
+ background: var(--ui-surface-muted) !important;
727
+ }
728
+
729
+ .gradio-container [role="listbox"] [role="option"][aria-selected="true"] {
730
+ background: var(--ui-primary-soft) !important;
731
+ color: var(--ui-primary-hover) !important;
732
+ font-weight: 760 !important;
733
+ }
734
+
735
  .demo-btn {
736
  min-height: 56px !important;
737
  border: 1px solid var(--ui-primary) !important;
 
1012
  }
1013
 
1014
  .results-heading {
1015
+ margin-top: 18px;
1016
  margin-bottom: 10px;
1017
+ padding: 18px 28px 0;
1018
  }
1019
 
1020
  .action-plan {
 
1352
 
1353
  .result-legend {
1354
  display: block;
1355
+ padding: 10px 12px;
1356
  }
1357
 
1358
  .result-legend h2 {
 
1387
  }
1388
 
1389
  .legend-items strong {
1390
+ font-size: 21px;
1391
  }
1392
 
1393
  .legend-copy {
 
1398
 
1399
  .legend-action {
1400
  margin-top: 0;
1401
+ font-size: 19px;
1402
  line-height: 1.25;
1403
  }
1404
 
 
1432
  padding: 14px 16px !important;
1433
  }
1434
 
1435
+ .workflow-section + .workflow-section {
1436
+ border-top-width: 8px !important;
1437
+ }
1438
+
1439
  .task-heading {
1440
  gap: 10px;
1441
  }
 
1495
  }
1496
 
1497
  .results-heading {
1498
+ margin-top: 16px;
1499
+ padding: 16px 16px 0;
1500
  }
1501
 
1502
  .action-plan-header {
tests/test_ui.py CHANGED
@@ -328,6 +328,39 @@ def test_civic_theme_keeps_light_and_dark_tokens_consistent() -> None:
328
  assert theme[token] == theme[f"{token}_dark"]
329
 
330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  def test_load_demo_document_selects_file_and_matching_schema(tmp_path: Path) -> None:
332
  file_path, schema, status = load_demo_document_callback(
333
  "receipt_green",
 
328
  assert theme[token] == theme[f"{token}_dark"]
329
 
330
 
331
+ def test_civic_css_keeps_steps_and_dropdowns_readable() -> None:
332
+ css = ui_module.CIVIC_CSS
333
+
334
+ assert ".workflow-section + .workflow-section" in css
335
+ assert "border-top: 10px solid var(--ui-bg)" in css
336
+ assert "box-shadow: inset 0 2px 0 var(--ui-border)" in css
337
+ assert "width: 18px" in css
338
+ assert "min-width: 18px" in css
339
+ assert "max-width: 18px" in css
340
+ assert "flex: 0 0 18px" in css
341
+ assert "font-size: 22px" in css
342
+ assert '.gradio-container [role="listbox"] [role="option"]' in css
343
+ assert "position: absolute !important" in css
344
+ assert "top: calc(100% + 6px) !important" in css
345
+ assert "bottom: auto !important" in css
346
+ assert '.workflow-section:has([role="listbox"])' in css
347
+ assert "min-height: 46px" in css
348
+ assert "font-size: 20px" in css
349
+ assert "width: 46px" in css
350
+ assert "flex: 0 0 46px" in css
351
+ assert "padding: 18px 28px 0" in css
352
+ assert ".results-heading::before" not in css
353
+
354
+
355
+ def test_civic_css_keeps_legend_cards_equal_sized() -> None:
356
+ css = ui_module.CIVIC_CSS
357
+
358
+ assert "grid-auto-rows: 1fr" in css
359
+ assert "align-items: stretch" in css
360
+ assert "height: 100%" in css
361
+ assert "align-self: stretch" in css
362
+
363
+
364
  def test_load_demo_document_selects_file_and_matching_schema(tmp_path: Path) -> None:
365
  file_path, schema, status = load_demo_document_callback(
366
  "receipt_green",