@worca/ui 0.40.0 → 0.41.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.
package/app/styles.css CHANGED
@@ -155,15 +155,50 @@ h1, h2, h3, h4, h5, h6 {
155
155
  }
156
156
 
157
157
  .sidebar.collapsed .sidebar-section-header,
158
- .sidebar.collapsed .sidebar-item span,
158
+ /* `.sidebar-item-left > span` targets only label spans — the outer
159
+ `.sidebar-item-left` is itself a span and would have been swept up
160
+ by the broader `.sidebar-item span` selector, hiding the icon too. */
161
+ .sidebar.collapsed .sidebar-item-left > span,
159
162
  .sidebar.collapsed .conn-label,
160
- .sidebar.collapsed .logo-text {
163
+ .sidebar.collapsed .logo-text,
164
+ .sidebar.collapsed .sidebar-project-section,
165
+ .sidebar.collapsed .sidebar-new-run-chevron-dropdown {
161
166
  display: none;
162
167
  }
163
168
 
169
+ /* In the collapsed rail, nav items become icon-only — center them so they
170
+ sit under the toggle button rather than floating against the left edge. */
171
+ .sidebar.collapsed .sidebar-item {
172
+ justify-content: center;
173
+ margin: 2px 6px;
174
+ padding: 8px 6px;
175
+ }
176
+ .sidebar.collapsed .sidebar-item-left {
177
+ gap: 0;
178
+ }
179
+ .sidebar.collapsed .sidebar-new-run {
180
+ padding: 4px 6px;
181
+ }
182
+ .sidebar.collapsed .sidebar-new-run-split {
183
+ width: auto;
184
+ }
185
+ .sidebar.collapsed .sidebar-footer {
186
+ justify-content: center;
187
+ padding: 12px 6px;
188
+ }
189
+
164
190
  .sidebar-logo {
165
- padding: 20px 16px 12px;
191
+ padding: 16px 12px 12px 16px;
166
192
  border-bottom: 1px solid var(--border-subtle);
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: space-between;
196
+ gap: 8px;
197
+ }
198
+
199
+ .sidebar.collapsed .sidebar-logo {
200
+ justify-content: center;
201
+ padding: 16px 6px 12px;
167
202
  }
168
203
 
169
204
  .logo-text {
@@ -174,6 +209,35 @@ h1, h2, h3, h4, h5, h6 {
174
209
  color: var(--fg);
175
210
  }
176
211
 
212
+ .sidebar-toggle-btn {
213
+ display: inline-flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ width: 28px;
217
+ height: 28px;
218
+ padding: 0;
219
+ border: 1px solid transparent;
220
+ border-radius: var(--radius);
221
+ background: transparent;
222
+ color: var(--muted);
223
+ cursor: pointer;
224
+ flex-shrink: 0;
225
+ transition:
226
+ background var(--transition-fast),
227
+ color var(--transition-fast),
228
+ border-color var(--transition-fast);
229
+ }
230
+
231
+ .sidebar-toggle-btn:hover {
232
+ background: var(--border);
233
+ color: var(--fg);
234
+ }
235
+
236
+ .sidebar-toggle-btn:focus-visible {
237
+ outline: none;
238
+ border-color: var(--accent);
239
+ }
240
+
177
241
  /* --- 6. Sidebar Sections --- */
178
242
  .sidebar-section {
179
243
  padding: 8px 0;
@@ -410,11 +474,548 @@ h1, h2, h3, h4, h5, h6 {
410
474
  color: #ffffff;
411
475
  }
412
476
 
477
+ .action-btn--ghost {
478
+ border-color: transparent;
479
+ color: var(--text-muted);
480
+ }
481
+
482
+ .action-btn--ghost:hover {
483
+ border-color: var(--border);
484
+ color: var(--text);
485
+ }
486
+
413
487
  .action-btn:disabled {
414
488
  opacity: 0.6;
415
489
  cursor: not-allowed;
416
490
  }
417
491
 
492
+ .run-stage-actions {
493
+ display: flex;
494
+ align-items: center;
495
+ justify-content: flex-end;
496
+ gap: 6px;
497
+ padding: 0 0 4px;
498
+ flex-wrap: wrap;
499
+ }
500
+
501
+ /* --- Run Timeline ---
502
+ Local tokens layered on the global palette. --text-strong gives a
503
+ WCAG-AA-safe muted color for labels (4.5:1 on white at 12-13px), avoiding
504
+ the very pale --muted (#94a3b8). All other colors route through brand vars
505
+ so dark mode and theme tweaks land for free. */
506
+ .run-timeline {
507
+ --tl-text-strong: #475569;
508
+ --tl-row-alt-bg: rgba(15, 23, 42, 0.04);
509
+ --tl-row-divider: rgba(15, 23, 42, 0.08);
510
+ --tl-grid-line: rgba(15, 23, 42, 0.05);
511
+ --tl-axis-line: rgba(15, 23, 42, 0.18);
512
+ --tl-axis-tick: rgba(15, 23, 42, 0.28);
513
+ --tl-axis-label: rgba(15, 23, 42, 0.62);
514
+ --tl-row-label: rgba(15, 23, 42, 0.78);
515
+ --tl-row-label-count: rgba(15, 23, 42, 0.45);
516
+ --tl-loopback-default-opacity: 0.7;
517
+ --tl-loopback-hint: rgba(15, 23, 42, 0.4);
518
+ --tl-bar-stroke: rgba(15, 23, 42, 0.22);
519
+ --tl-bar-shadow: rgba(15, 23, 42, 0.22);
520
+ --timeline-gap-fill: rgba(15, 23, 42, 0.04);
521
+ --timeline-gap-dot: rgba(15, 23, 42, 0.32);
522
+
523
+ padding: 16px 20px 24px;
524
+ background: var(--bg, #fff);
525
+ border: 1px solid var(--border-subtle, rgba(226, 232, 240, 0.6));
526
+ border-radius: 10px;
527
+ overflow-x: auto;
528
+ position: relative;
529
+ }
530
+
531
+ /* worca-ui drives dark mode via [data-theme="dark"] on the html element,
532
+ not via prefers-color-scheme. Both selectors here so the timeline tokens
533
+ flip with the rest of the chrome. */
534
+ [data-theme="dark"] .run-timeline,
535
+ .run-timeline[data-theme="dark"] {
536
+ --tl-text-strong: #cbd5e1;
537
+ --tl-row-alt-bg: rgba(255, 255, 255, 0.04);
538
+ --tl-row-divider: rgba(255, 255, 255, 0.1);
539
+ --tl-grid-line: rgba(255, 255, 255, 0.07);
540
+ --tl-axis-line: rgba(255, 255, 255, 0.22);
541
+ --tl-axis-tick: rgba(255, 255, 255, 0.35);
542
+ --tl-axis-label: rgba(255, 255, 255, 0.7);
543
+ --tl-row-label: rgba(255, 255, 255, 0.9);
544
+ --tl-row-label-count: rgba(255, 255, 255, 0.55);
545
+ --tl-loopback-hint: rgba(255, 255, 255, 0.5);
546
+ --tl-bar-stroke: rgba(255, 255, 255, 0.32);
547
+ --tl-bar-shadow: rgba(0, 0, 0, 0.45);
548
+ --timeline-gap-fill: rgba(255, 255, 255, 0.06);
549
+ --timeline-gap-dot: rgba(255, 255, 255, 0.4);
550
+ }
551
+
552
+ /* Top stats summary */
553
+ .timeline-summary {
554
+ display: flex;
555
+ align-items: center;
556
+ gap: 28px;
557
+ /* Right-padding reserves space for the absolutely-positioned .timeline-toolbar
558
+ anchored at top:16px / right:20px so the stats never run under the buttons. */
559
+ padding: 4px 260px 16px 0;
560
+ flex-wrap: wrap;
561
+ }
562
+ .summary-stat {
563
+ display: flex;
564
+ flex-direction: column;
565
+ gap: 2px;
566
+ min-width: 64px;
567
+ }
568
+ .summary-stat-label {
569
+ font-size: 11px;
570
+ text-transform: uppercase;
571
+ letter-spacing: 0.04em;
572
+ color: var(--tl-text-strong);
573
+ font-weight: 500;
574
+ }
575
+ .summary-stat-value {
576
+ font-size: 18px;
577
+ font-weight: 600;
578
+ color: var(--fg, #0f172a);
579
+ font-variant-numeric: tabular-nums;
580
+ }
581
+ .summary-spacer {
582
+ flex: 1;
583
+ }
584
+
585
+ /* Toolbar — anchored to the top-right corner of the timeline box.
586
+ Stacks the zoom button group above the keyboard-hint text, both right-aligned. */
587
+ .timeline-toolbar {
588
+ position: absolute;
589
+ top: 16px;
590
+ right: 20px;
591
+ z-index: 2;
592
+ display: flex;
593
+ flex-direction: column;
594
+ align-items: flex-end;
595
+ gap: 6px;
596
+ }
597
+ .timeline-toolbar-group {
598
+ display: inline-flex;
599
+ border: 1px solid var(--border, #e2e8f0);
600
+ border-radius: 6px;
601
+ overflow: hidden;
602
+ background: var(--bg, #fff);
603
+ }
604
+ .timeline-zoom-btn {
605
+ display: inline-flex;
606
+ align-items: center;
607
+ justify-content: center;
608
+ min-width: 30px;
609
+ height: 28px;
610
+ padding: 0 8px;
611
+ border: none;
612
+ background: transparent;
613
+ color: var(--tl-text-strong);
614
+ cursor: pointer;
615
+ user-select: none;
616
+ border-right: 1px solid var(--border, #e2e8f0);
617
+ }
618
+ .timeline-zoom-btn:last-child {
619
+ border-right: none;
620
+ }
621
+ .timeline-zoom-btn:hover {
622
+ background: var(--bg-secondary, #f8fafc);
623
+ color: var(--fg, #0f172a);
624
+ }
625
+ .timeline-zoom-btn:active {
626
+ background: var(--bg-tertiary, #f1f5f9);
627
+ }
628
+ .timeline-zoom-btn:focus-visible {
629
+ outline: 2px solid var(--accent, #3b82f6);
630
+ outline-offset: -2px;
631
+ z-index: 1;
632
+ }
633
+ .timeline-toolbar-hint {
634
+ font-size: 11px;
635
+ color: var(--tl-text-strong);
636
+ text-align: right;
637
+ }
638
+
639
+ /* SVG wrapper */
640
+ .timeline-svg-wrap {
641
+ position: relative;
642
+ overflow: visible;
643
+ }
644
+
645
+ /* Row backgrounds */
646
+ .row-bg-alt {
647
+ fill: var(--tl-row-alt-bg);
648
+ }
649
+ .row-divider {
650
+ stroke: var(--tl-row-divider);
651
+ stroke-width: 1;
652
+ shape-rendering: crispEdges;
653
+ }
654
+ .grid-line {
655
+ stroke: var(--tl-grid-line);
656
+ stroke-width: 1;
657
+ shape-rendering: crispEdges;
658
+ }
659
+
660
+ /* Row labels — match the uppercase tracking used by .pipeline-stage-name so
661
+ timeline rows read like the stage section headers elsewhere in the UI. */
662
+ .row-label {
663
+ font-size: 12px;
664
+ font-weight: 600;
665
+ fill: var(--tl-row-label);
666
+ font-family: var(--sl-font-sans, system-ui, sans-serif);
667
+ text-transform: uppercase;
668
+ letter-spacing: 0.03em;
669
+ }
670
+ .row-label-count {
671
+ font-size: 11px;
672
+ font-weight: 500;
673
+ fill: var(--tl-row-label-count);
674
+ text-transform: none;
675
+ letter-spacing: 0;
676
+ }
677
+ .loopback-hint {
678
+ font-size: 10px;
679
+ fill: var(--tl-loopback-hint);
680
+ pointer-events: none;
681
+ }
682
+
683
+ /* Bars
684
+ Default: thin contrasting hairline stroke so bar edges read clearly without
685
+ any per-bar divider. Hover/active: lift via translateY + drop-shadow so the
686
+ bar "raises" off the chart. Active state persists while the iteration drawer
687
+ is open (driven by the .is-active class). Reduced-motion drops the lift. */
688
+ .timeline-bar {
689
+ cursor: pointer;
690
+ stroke: var(--tl-bar-stroke);
691
+ stroke-width: 1;
692
+ transition:
693
+ filter 130ms ease,
694
+ transform 130ms ease,
695
+ stroke-width 130ms ease;
696
+ /* SVG rect transforms relative to the SVG viewport origin, not the rect
697
+ itself — fine for translateY since we want a small vertical lift. */
698
+ }
699
+ .timeline-bar:hover,
700
+ .timeline-bar.is-active {
701
+ filter: drop-shadow(0 3px 6px var(--tl-bar-shadow));
702
+ transform: translateY(-1px);
703
+ stroke-width: 1.25;
704
+ }
705
+ .timeline-bar:focus-visible {
706
+ /* Subtle keyboard-focus indicator — no bold ring. The raised+shadow state
707
+ above also kicks in on focus via :focus-visible :is() below. */
708
+ outline: 1.5px dashed var(--accent, #3b82f6);
709
+ outline-offset: 2px;
710
+ }
711
+ .timeline-bar:focus-visible {
712
+ filter: drop-shadow(0 3px 6px var(--tl-bar-shadow));
713
+ transform: translateY(-1px);
714
+ }
715
+ @media (prefers-reduced-motion: reduce) {
716
+ .timeline-bar,
717
+ .timeline-bar:hover,
718
+ .timeline-bar.is-active,
719
+ .timeline-bar:focus-visible {
720
+ transform: none;
721
+ }
722
+ }
723
+ .bar-label {
724
+ font-size: 10px;
725
+ font-weight: 600;
726
+ fill: #fff;
727
+ font-family: var(--sl-font-sans, system-ui, sans-serif);
728
+ font-variant-numeric: tabular-nums;
729
+ letter-spacing: 0.02em;
730
+ }
731
+
732
+ /* Gaps */
733
+ .timeline-gap {
734
+ cursor: default;
735
+ }
736
+ .timeline-gap:focus-visible {
737
+ outline: 2px solid var(--accent, #3b82f6);
738
+ outline-offset: 1px;
739
+ }
740
+ .gap-bg {
741
+ fill: var(--timeline-gap-fill);
742
+ }
743
+ .gap-dot {
744
+ fill: var(--timeline-gap-dot);
745
+ }
746
+
747
+ /* Loopbacks */
748
+ .loopback {
749
+ stroke-opacity: var(--tl-loopback-default-opacity);
750
+ transition: stroke-opacity 120ms ease, stroke-width 120ms ease;
751
+ }
752
+ .loopback.highlight {
753
+ stroke-opacity: 1;
754
+ stroke-width: 2.5;
755
+ }
756
+
757
+ /* Time axis */
758
+ .axis-baseline {
759
+ stroke: var(--tl-axis-line);
760
+ stroke-width: 1;
761
+ shape-rendering: crispEdges;
762
+ }
763
+ .axis-tick {
764
+ stroke: var(--tl-axis-tick);
765
+ stroke-width: 1;
766
+ shape-rendering: crispEdges;
767
+ }
768
+ .axis-label {
769
+ font-size: 11px;
770
+ fill: var(--tl-axis-label);
771
+ font-family: var(--sl-font-sans, system-ui, sans-serif);
772
+ font-variant-numeric: tabular-nums;
773
+ }
774
+
775
+ /* Legend */
776
+ .timeline-legend {
777
+ display: flex;
778
+ align-items: center;
779
+ gap: 18px;
780
+ padding: 14px 0 4px;
781
+ margin-top: 8px;
782
+ border-top: 1px solid var(--border-subtle, rgba(226, 232, 240, 0.6));
783
+ flex-wrap: wrap;
784
+ }
785
+ .legend-item {
786
+ display: inline-flex;
787
+ align-items: center;
788
+ gap: 6px;
789
+ font-size: 11px;
790
+ color: var(--tl-text-strong);
791
+ }
792
+ .legend-swatch {
793
+ display: inline-block;
794
+ width: 12px;
795
+ height: 12px;
796
+ border-radius: 3px;
797
+ background: var(--muted);
798
+ }
799
+ .legend-swatch--gap {
800
+ background:
801
+ radial-gradient(circle, var(--timeline-gap-dot) 0.6px, transparent 1.2px),
802
+ var(--timeline-gap-fill);
803
+ background-size: 5px 5px;
804
+ border: 1px solid var(--tl-row-divider);
805
+ }
806
+ .legend-loopback {
807
+ /* Inline SVG arc + chevron; inherits color from the legend-item color, so
808
+ it tracks the rest of the legend chrome in both themes. */
809
+ color: var(--accent, #3b82f6);
810
+ flex-shrink: 0;
811
+ }
812
+
813
+ /* Tooltip */
814
+ .timeline-tooltip {
815
+ position: fixed;
816
+ z-index: 1000;
817
+ background: var(--bg, #fff);
818
+ border: 1px solid var(--border, #e2e8f0);
819
+ border-radius: 8px;
820
+ box-shadow: 0 10px 28px rgba(15, 23, 42, 0.12), 0 2px 6px rgba(15, 23, 42, 0.08);
821
+ padding: 10px 12px;
822
+ pointer-events: none;
823
+ min-width: 200px;
824
+ max-width: 280px;
825
+ font-size: 12px;
826
+ line-height: 1.55;
827
+ color: var(--fg, #0f172a);
828
+ }
829
+ .tooltip-header {
830
+ display: flex;
831
+ align-items: center;
832
+ gap: 6px;
833
+ font-weight: 600;
834
+ margin-bottom: 6px;
835
+ padding-bottom: 6px;
836
+ border-bottom: 1px solid var(--border-subtle, rgba(226, 232, 240, 0.6));
837
+ }
838
+ .tooltip-hue-dot {
839
+ display: inline-block;
840
+ width: 8px;
841
+ height: 8px;
842
+ border-radius: 50%;
843
+ flex-shrink: 0;
844
+ }
845
+ .tooltip-header-sub {
846
+ font-weight: 400;
847
+ color: var(--tl-text-strong);
848
+ font-size: 11px;
849
+ }
850
+ .tooltip-bead-sub {
851
+ font-size: 11px;
852
+ color: var(--tl-text-strong);
853
+ margin-top: -2px;
854
+ margin-bottom: 4px;
855
+ line-height: 1.35;
856
+ }
857
+ .tooltip-bead-id {
858
+ font-family: var(--sl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
859
+ font-size: 10.5px;
860
+ background: var(--tl-row-alt, rgba(148, 163, 184, 0.12));
861
+ padding: 1px 5px;
862
+ border-radius: 3px;
863
+ margin-right: 4px;
864
+ }
865
+ .tooltip-row {
866
+ display: flex;
867
+ justify-content: space-between;
868
+ gap: 14px;
869
+ }
870
+ .tooltip-label {
871
+ color: var(--tl-text-strong);
872
+ font-weight: 500;
873
+ }
874
+ .tooltip-value {
875
+ font-family: var(--sl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
876
+ font-variant-numeric: tabular-nums;
877
+ color: var(--fg, #0f172a);
878
+ }
879
+ .tooltip-status {
880
+ text-transform: capitalize;
881
+ }
882
+ .tooltip-status.status-completed { color: var(--status-completed); }
883
+ .tooltip-status.status-running, .tooltip-status.status-in_progress { color: var(--status-running); }
884
+ .tooltip-status.status-failed { color: var(--status-failed); }
885
+ .tooltip-status.status-paused { color: var(--status-paused); }
886
+ .tooltip-status.status-interrupted { color: var(--status-interrupted); }
887
+
888
+ /* --- Iteration drawer --- */
889
+ .iteration-drawer::part(panel) {
890
+ width: 420px;
891
+ max-width: 90vw;
892
+ }
893
+ .iteration-drawer::part(header) {
894
+ border-bottom: 1px solid var(--border-subtle, rgba(226, 232, 240, 0.6));
895
+ }
896
+ .drawer-body {
897
+ display: flex;
898
+ flex-direction: column;
899
+ gap: 14px;
900
+ font-size: 13px;
901
+ color: var(--fg, #0f172a);
902
+ }
903
+ .drawer-title {
904
+ display: flex;
905
+ align-items: center;
906
+ gap: 8px;
907
+ margin: 0 0 4px;
908
+ font-size: 16px;
909
+ font-weight: 600;
910
+ color: var(--fg, #0f172a);
911
+ outline: none;
912
+ }
913
+ .drawer-hue-dot {
914
+ display: inline-block;
915
+ width: 10px;
916
+ height: 10px;
917
+ border-radius: 50%;
918
+ }
919
+ .drawer-title-sub {
920
+ font-weight: 400;
921
+ color: var(--tl-text-strong);
922
+ font-size: 13px;
923
+ }
924
+ .drawer-status-row {
925
+ display: inline-flex;
926
+ align-items: center;
927
+ gap: 8px;
928
+ padding: 6px 12px;
929
+ border-radius: 999px;
930
+ background: var(--bg-secondary, #f8fafc);
931
+ align-self: flex-start;
932
+ }
933
+ .drawer-status-pill {
934
+ display: inline-block;
935
+ width: 8px;
936
+ height: 8px;
937
+ border-radius: 50%;
938
+ }
939
+ .drawer-status-text {
940
+ font-size: 12px;
941
+ font-weight: 600;
942
+ text-transform: capitalize;
943
+ }
944
+ .drawer-status-text.status-completed { color: var(--status-completed); }
945
+ .drawer-status-text.status-running, .drawer-status-text.status-in_progress { color: var(--status-running); }
946
+ .drawer-status-text.status-failed { color: var(--status-failed); }
947
+ .drawer-fields {
948
+ display: grid;
949
+ grid-template-columns: max-content 1fr;
950
+ gap: 8px 18px;
951
+ margin: 0;
952
+ }
953
+ .drawer-label {
954
+ color: var(--tl-text-strong);
955
+ font-weight: 500;
956
+ font-size: 12px;
957
+ text-transform: uppercase;
958
+ letter-spacing: 0.04em;
959
+ align-self: center;
960
+ }
961
+ .drawer-value {
962
+ margin: 0;
963
+ font-family: var(--sl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
964
+ font-variant-numeric: tabular-nums;
965
+ color: var(--fg, #0f172a);
966
+ }
967
+ .drawer-bead {
968
+ font-family: inherit;
969
+ display: flex;
970
+ align-items: baseline;
971
+ gap: 6px;
972
+ flex-wrap: wrap;
973
+ }
974
+ .drawer-bead-id {
975
+ font-family: var(--sl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
976
+ font-size: 11px;
977
+ background: var(--tl-row-alt, rgba(148, 163, 184, 0.12));
978
+ padding: 1px 6px;
979
+ border-radius: 3px;
980
+ }
981
+ .drawer-raw-json {
982
+ margin-top: 4px;
983
+ }
984
+ .drawer-raw-json summary {
985
+ cursor: pointer;
986
+ color: var(--tl-text-strong);
987
+ font-size: 12px;
988
+ user-select: none;
989
+ }
990
+ .drawer-raw-json pre {
991
+ font-size: 11px;
992
+ font-family: var(--sl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
993
+ background: var(--bg-secondary, #f8fafc);
994
+ border: 1px solid var(--border, #e2e8f0);
995
+ border-radius: 6px;
996
+ padding: 10px;
997
+ overflow-x: auto;
998
+ white-space: pre;
999
+ margin-top: 6px;
1000
+ max-height: 280px;
1001
+ }
1002
+ .drawer-run-detail-link {
1003
+ color: var(--accent, #3b82f6);
1004
+ text-decoration: none;
1005
+ font-size: 13px;
1006
+ font-weight: 500;
1007
+ }
1008
+ .drawer-run-detail-link:hover {
1009
+ text-decoration: underline;
1010
+ }
1011
+
1012
+ @media (prefers-reduced-motion: reduce) {
1013
+ .timeline-bar,
1014
+ .loopback {
1015
+ transition: none;
1016
+ }
1017
+ }
1018
+
418
1019
  .action-btn:disabled:hover {
419
1020
  background: var(--bg-secondary);
420
1021
  color: inherit;
@@ -568,6 +1169,12 @@ h1, h2, h3, h4, h5, h6 {
568
1169
  }
569
1170
 
570
1171
  /* Pipeline timing bar */
1172
+ .pipeline-timing-bar-actions {
1173
+ display: flex;
1174
+ justify-content: flex-end;
1175
+ margin-bottom: 8px;
1176
+ }
1177
+
571
1178
  .pipeline-timing-bar-container {
572
1179
  margin-top: 8px;
573
1180
  }
@@ -3133,6 +3740,9 @@ sl-option.template-grouped:focus::part(suffix) {
3133
3740
 
3134
3741
  .sidebar.collapsed .sidebar-new-run-btn-primary {
3135
3742
  padding: 8px;
3743
+ /* Chevron half is hidden — drop the divider so the button reads as a
3744
+ single icon control, not a button with a stray edge. */
3745
+ border-right: 0;
3136
3746
  }
3137
3747
 
3138
3748
  /* Stage restart button */
@@ -7805,3 +8415,269 @@ sl-dialog.markdown-dialog::part(body) {
7805
8415
  color: var(--fg-muted);
7806
8416
  gap: 8px;
7807
8417
  }
8418
+
8419
+ /* ===========================================================================
8420
+ * Help mode (W-061 prototype, prototype/W-061-help-mode-toggle)
8421
+ *
8422
+ * Two concerns:
8423
+ * 1. The right-edge "Docs" tab (.help-edge-tab) — always visible at 75%
8424
+ * opacity, fully opaque on hover/active.
8425
+ * 2. Per-surface "?" badges (.help-badge) — hidden until body has the
8426
+ * .help-mode-active class.
8427
+ *
8428
+ * Colour: violet (~#7c3aed) — deliberately chosen so help mode reads as
8429
+ * "informational / secondary" and never competes with the blue used for
8430
+ * primary actions + running state, the green used for completed, the
8431
+ * orange used for caution, or the red used for failed.
8432
+ *
8433
+ * Animation: each badge radiates a slow sonar wave outward and fades to
8434
+ * transparent, like a discoverability signal. `prefers-reduced-motion`
8435
+ * collapses the wave to a static dim halo.
8436
+ * ===========================================================================
8437
+ */
8438
+
8439
+ /* --- Edge tab --------------------------------------------------------- */
8440
+
8441
+ .help-edge-tab-root {
8442
+ position: fixed;
8443
+ inset: 0;
8444
+ pointer-events: none;
8445
+ z-index: 1000;
8446
+ }
8447
+
8448
+ /* Vertical pill anchored ~100px from the top of the viewport (deliberately
8449
+ off-centre so it sits above the eye line and doesn't compete with
8450
+ sidebar/main content for the visual centre). */
8451
+ .help-edge-tab {
8452
+ position: fixed;
8453
+ right: 0;
8454
+ top: 100px;
8455
+ transform: translateX(0);
8456
+ transform-origin: right center;
8457
+ pointer-events: auto;
8458
+
8459
+ display: inline-flex;
8460
+ flex-direction: column;
8461
+ align-items: center;
8462
+ justify-content: center;
8463
+ /* Sized to wrap the rotated content (icon ~20px + 10px gap + "Docs"
8464
+ label ~38px ≈ 68px length, 20px max thickness). Height bumped +25%
8465
+ (84 → 105) per the user's request to give the rotated content more
8466
+ padding at the top and bottom of the tab. */
8467
+ width: 30px;
8468
+ height: 105px;
8469
+ padding: 0;
8470
+ border: 1px solid #c4b5fd; /* violet-300 */
8471
+ border-right: none;
8472
+ border-top-left-radius: 8px;
8473
+ border-bottom-left-radius: 8px;
8474
+ background: rgba(124, 58, 237, 0.1); /* violet-600 @ 10% */
8475
+ color: #5b21b6; /* violet-800 */
8476
+ font-size: 16px;
8477
+ font-weight: 600;
8478
+ letter-spacing: 0.04em;
8479
+ cursor: pointer;
8480
+ opacity: 0.75;
8481
+ box-shadow: -2px 0 8px rgba(0, 0, 0, 0.06);
8482
+ transition: opacity 160ms ease, background-color 120ms ease, color 120ms ease,
8483
+ transform 180ms ease, box-shadow 180ms ease;
8484
+ }
8485
+
8486
+ .help-edge-tab:hover,
8487
+ .help-edge-tab:focus-visible {
8488
+ opacity: 1;
8489
+ color: #4c1d95; /* violet-900 */
8490
+ background: rgba(124, 58, 237, 0.18);
8491
+ transform: translateX(-2px);
8492
+ box-shadow: -4px 0 12px rgba(0, 0, 0, 0.08);
8493
+ outline: none;
8494
+ }
8495
+
8496
+ .help-edge-tab:focus-visible {
8497
+ box-shadow: -4px 0 12px rgba(0, 0, 0, 0.08), 0 0 0 2px #7c3aed;
8498
+ }
8499
+
8500
+ .help-edge-tab--active {
8501
+ opacity: 1;
8502
+ background: #7c3aed; /* violet-600 */
8503
+ color: #fff;
8504
+ border-color: #6d28d9; /* violet-700 */
8505
+ }
8506
+
8507
+ .help-edge-tab--active:hover,
8508
+ .help-edge-tab--active:focus-visible {
8509
+ background: #6d28d9;
8510
+ color: #fff;
8511
+ }
8512
+
8513
+ /* Inner row is icon + "Docs". Rotating the entire row -90deg (counter-
8514
+ clockwise) makes both the icon and the label share the same rotation,
8515
+ reading bottom-to-top along the right edge (head tilted left). */
8516
+ .help-edge-tab__inner {
8517
+ display: inline-flex;
8518
+ flex-direction: row;
8519
+ align-items: center;
8520
+ gap: 10px;
8521
+ transform: rotate(-90deg);
8522
+ transform-origin: center center;
8523
+ white-space: nowrap;
8524
+ }
8525
+
8526
+ .help-edge-tab__icon {
8527
+ display: inline-flex;
8528
+ align-items: center;
8529
+ justify-content: center;
8530
+ }
8531
+
8532
+ .help-edge-tab__label {
8533
+ /* Matches the sidebar's WORCA wordmark (.logo-text) — JetBrains Mono
8534
+ family + bold weight + wide letter-spacing — so the affordance reads
8535
+ as part of the brand vocabulary rather than a generic side tab. */
8536
+ font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
8537
+ font-weight: 700;
8538
+ letter-spacing: 0.08em;
8539
+ line-height: 1;
8540
+ }
8541
+
8542
+ /* --- Per-surface help badge ------------------------------------------- */
8543
+
8544
+ /* Hidden by default. Once help-mode-active flips on the body, every badge
8545
+ reveals at the top-right of its parent and emits a slow sonar wave that
8546
+ radiates outward then fades.
8547
+
8548
+ The badge is a real <a> — keyboard reachable when visible, opens the
8549
+ doc in a new tab, never depends on JS for click handling. */
8550
+ .help-badge {
8551
+ display: none;
8552
+ position: absolute;
8553
+ /* Vertically centred on the host's right edge — gives the sonar wave
8554
+ equal headroom above and below the badge so it doesn't overflow
8555
+ the host's bounding box. With disc 27px and wave max scale 1.5,
8556
+ max wave diameter is ~40px → fits inside any host ≥ 40px tall
8557
+ (typical sl-tab ~40px, panel headers larger). */
8558
+ top: 50%;
8559
+ right: 6px;
8560
+ transform: translateY(-50%);
8561
+ z-index: 5;
8562
+
8563
+ align-items: center;
8564
+ justify-content: center;
8565
+ /* Solid disc 27×27 — entire badge scaled up 50% (18 → 27). Icon, wave
8566
+ base, and clickable hit target all grow proportionally. */
8567
+ width: 27px;
8568
+ height: 27px;
8569
+ border-radius: 50%;
8570
+ /* Solid disc at 75% opacity — rgba on the background only so the
8571
+ ::after sonar wave's own opacity animation (0.55 → 0) isn't dimmed
8572
+ by parent inheritance. The white glyph stays fully visible. */
8573
+ background: rgba(124, 58, 237, 0.75); /* violet-600 @ 75% */
8574
+ color: #fff;
8575
+ text-decoration: none;
8576
+ transition: transform 120ms ease, background-color 120ms ease;
8577
+ }
8578
+
8579
+ /* Plain text "?" glyph — no container artifact (lucide's question-mark
8580
+ variants all wrap the ? in some outline shape). Sized + weighted to
8581
+ fill the 27px disc with a comfortable optical balance. */
8582
+ .help-badge__glyph {
8583
+ display: inline-flex;
8584
+ align-items: center;
8585
+ justify-content: center;
8586
+ font-family: var(--sl-font-sans, system-ui, sans-serif);
8587
+ font-size: 18px;
8588
+ font-weight: 700;
8589
+ line-height: 1;
8590
+ /* Most "?" glyphs sit slightly above the optical centre of their
8591
+ em-box because of the descender-free shape. Nudge down 1px so it
8592
+ reads centred in the disc. */
8593
+ margin-top: 1px;
8594
+ }
8595
+
8596
+ /* The sonar wave — a same-colour pseudo-element behind the badge that
8597
+ slowly grows outward and fades. Sits at z-index 0 (badge itself is
8598
+ at z-index 5) so it appears under the badge during expansion. */
8599
+ .help-badge::after {
8600
+ content: '';
8601
+ position: absolute;
8602
+ inset: 0;
8603
+ border-radius: 50%;
8604
+ background: #7c3aed;
8605
+ opacity: 0;
8606
+ z-index: -1;
8607
+ pointer-events: none;
8608
+ }
8609
+
8610
+ body.help-mode-active .help-badge {
8611
+ display: inline-flex;
8612
+ }
8613
+
8614
+ body.help-mode-active .help-badge::after {
8615
+ animation: help-badge-sonar 2.4s ease-out infinite;
8616
+ }
8617
+
8618
+ /* Auto-relative any direct parent of a badge so absolute positioning has
8619
+ a containing block. Modern browsers (Chrome 105+, Safari 15.4+,
8620
+ Firefox 121+) all support :has(). For sl-tab specifically, also force
8621
+ overflow: visible so the badge (and especially the sonar wave) isn't
8622
+ clipped against the tab strip's edge. */
8623
+ body.help-mode-active *:has(> .help-badge) {
8624
+ position: relative;
8625
+ }
8626
+
8627
+ body.help-mode-active sl-tab {
8628
+ overflow: visible;
8629
+ }
8630
+
8631
+ .help-badge:hover,
8632
+ .help-badge:focus-visible {
8633
+ /* Compose the hover lift with the vertical-centre translate so the
8634
+ badge doesn't jump when hovered. */
8635
+ transform: translateY(-50%) scale(1.1);
8636
+ /* Full opacity on hover so the affordance reads stronger. */
8637
+ background: #6d28d9; /* violet-700, opaque */
8638
+ outline: none;
8639
+ }
8640
+
8641
+ /* Sonar wave: scale 1 → 1.5 from the 27px badge → max diameter ~40px,
8642
+ centred on a badge that's vertically centred on the host. Wave fits
8643
+ the host's bounding box on any host ≥ 40px tall (typical sl-tab
8644
+ ~40px, panel headers larger; thin inline hosts may graze the edge). */
8645
+ @keyframes help-badge-sonar {
8646
+ 0% {
8647
+ transform: scale(1);
8648
+ opacity: 0.55;
8649
+ }
8650
+ 100% {
8651
+ transform: scale(1.5);
8652
+ opacity: 0;
8653
+ }
8654
+ }
8655
+
8656
+ @media (prefers-reduced-motion: reduce) {
8657
+ body.help-mode-active .help-badge::after {
8658
+ animation: none;
8659
+ opacity: 0.22;
8660
+ transform: scale(1.3);
8661
+ }
8662
+ .help-edge-tab {
8663
+ transition: none;
8664
+ }
8665
+ }
8666
+
8667
+ /* Dark-mode — violet stays violet, but we lift the idle background so it
8668
+ doesn't blend into dark panels. */
8669
+ :root[data-theme='dark'] .help-edge-tab,
8670
+ .sl-theme-dark .help-edge-tab {
8671
+ background: rgba(124, 58, 237, 0.22);
8672
+ color: #e9d5ff; /* violet-200 */
8673
+ border-color: #6d28d9;
8674
+ box-shadow: -2px 0 8px rgba(0, 0, 0, 0.4);
8675
+ }
8676
+
8677
+ :root[data-theme='dark'] .help-edge-tab:hover,
8678
+ :root[data-theme='dark'] .help-edge-tab:focus-visible,
8679
+ .sl-theme-dark .help-edge-tab:hover,
8680
+ .sl-theme-dark .help-edge-tab:focus-visible {
8681
+ background: rgba(124, 58, 237, 0.35);
8682
+ color: #f5f3ff;
8683
+ }