feat individual breadcrumb links in path pill; distinct chevron vs dir link

The path pill now contains individually clickable links for each trail segment separated by '/' delimiters, allowing navigation to any intermediate directory. Directory listings now visually distinguish the expand/collapse action (chevron button with grey background) from the navigate action (dir name as a skyblue link with its own hover highlight). The chevron is a small rounded square that highlights on hover, while the directory name uses standard link styling with underline on hover.

Commit
a77b543276d879c95ca79fb1e2573f3ffbb7cb91
Author
Claude Sonnet 4 <ai@anthropic.com>
Author date
Committer
Claude Sonnet 4 <ai@anthropic.com>
Committer date
Changed files
lib/static/styles.css
index 48efbe25..056fa6ac 100644..100644
@@ -526,6 +526,23 @@
526 526 text-decoration: underline;
527 527 }
528 528
529 Added: .path-pill-link {
530 Added: color: inherit;
531 Added: text-decoration: none;
532 Added: padding: 0.1em 0.2em;
533 Added: border-radius: 0.25rem;
534 Added: }
535 Added:
536 Added: .path-pill-link:hover {
537 Added: background-color: rgba(255, 255, 255, 0.15);
538 Added: text-decoration: underline;
539 Added: }
540 Added:
541 Added: .path-pill-sep {
542 Added: color: #6ab0d4;
543 Added: margin: 0 0.1em;
544 Added: }
545 Added:
529 546 /* Tree directory expand/collapse */
530 547
531 548 .tree-dir {
@@ -544,7 +561,7 @@
544 561 padding: 0.75em;
545 562 min-height: 44px;
546 563 box-sizing: border-box;
547 Removed: gap: 0.25em;
564 Added: gap: 0;
548 565 }
549 566
550 567 details[open] > .tree-toggle {
@@ -562,36 +579,41 @@
562 579 content: "";
563 580 }
564 581
565 Removed: .tree-toggle::before {
566 Removed: content: "\203a";
567 Removed: font-size: 1.3em;
582 Added: .tree-chevron {
583 Added: display: inline-flex;
584 Added: align-items: center;
585 Added: justify-content: center;
586 Added: width: 1.6em;
587 Added: height: 1.6em;
588 Added: font-size: 1.1em;
568 589 color: #b0b0b0;
569 Removed: transition: transform 0.15s ease;
590 Added: background-color: #2a2a2a;
591 Added: border-radius: 0.25rem;
570 592 flex-shrink: 0;
571 Removed: width: 1em;
572 Removed: text-align: center;
593 Added: margin-right: 0.5em;
594 Added: transition: transform 0.15s ease;
573 595 }
574 596
575 Removed: details[open] > .tree-toggle::before {
597 Added: details[open] > .tree-toggle > .tree-chevron {
576 598 transform: rotate(90deg);
577 599 }
578 600
579 Removed: .tree-toggle a {
601 Added: .tree-chevron:hover {
602 Added: background-color: #444;
580 603 color: white;
604 Added: }
605 Added:
606 Added: .tree-link {
607 Added: color: skyblue;
581 608 text-decoration: none;
609 Added: padding: 0.2em 0.4em;
610 Added: border-radius: 0.25rem;
582 611 }
583 612
584 Removed: .tree-toggle:hover {
613 Added: .tree-link:hover {
585 614 background-color: white;
586 615 color: black;
587 Removed: }
588 Removed:
589 Removed: .tree-toggle:hover a {
590 Removed: color: black;
591 Removed: }
592 Removed:
593 Removed: .tree-toggle:hover::before {
594 Removed: color: black;
616 Added: text-decoration: underline;
595 617 }}
596 618
597 619 .tree-nested {
lib/views/repo.ml
index d076abfd..52338f29 100644..100644
@@ -169,7 +169,12 @@
169 169 [
170 170 summary
171 171 [ class_ "tree-toggle" ]
172 Removed: [ Routes.link_to route (txt "%s/" entry.name) ];
172 Added: [
173 Added: span [ class_ "tree-chevron" ] [ txt "\xe2\x80\xba" ];
174 Added: Routes.link_to route
175 Added: ~other_attrs:[ class_ "tree-link" ]
176 Added: (txt "%s/" entry.name);
177 Added: ];
173 178 ul
174 179 [ class_ "tree-nested" ]
175 180 [
@@ -193,7 +198,12 @@
193 198 [
194 199 summary
195 200 [ class_ "tree-toggle" ]
196 Removed: [ Routes.link_to route (txt "%s/" entry.name) ];
201 Added: [
202 Added: span [ class_ "tree-chevron" ] [ txt "\xe2\x80\xba" ];
203 Added: Routes.link_to route
204 Added: ~other_attrs:[ class_ "tree-link" ]
205 Added: (txt "%s/" entry.name);
206 Added: ];
197 207 ul
198 208 [ class_ "tree-nested" ]
199 209 (List.map (li_of_preloaded repo) children);
@@ -307,12 +317,17 @@
307 317 match trail with
308 318 | [] -> HTML.null []
309 319 | _ ->
310 Removed: let path_text = String.concat "/" (List.map fst trail) in
311 Removed: let deepest_hash = snd (List.nth trail (List.length trail - 1)) in
312 Removed: HTML.(
313 Removed: span
314 Removed: [ class_ "path-pill" ]
315 Removed: [ Routes.link_to (File (repo, deepest_hash)) (txt "%s" path_text) ])
320 Added: let segment i (entry_name, hash) =
321 Added: let anchor =
322 Added: Routes.link_to
323 Added: (File (repo, hash))
324 Added: ~other_attrs:[ HTML.class_ "path-pill-link" ]
325 Added: (txt "%s" entry_name)
326 Added: in
327 Added: if i = 0 then anchor
328 Added: else HTML.(null [ span [ class_ "path-pill-sep" ] [ txt "/" ]; anchor ])
329 Added: in
330 Added: HTML.(span [ class_ "path-pill" ] (List.mapi segment trail))
316 331
317 332 let files context trail (entries : Resolvers.Tree.preloaded_entry list) =
318 333 let pill = breadcrumb_pill context.repo trail in