Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit2289dc6unknown_key

feat(settings): repo settings sidebar nav with links to all sub-pages

feat(settings): repo settings sidebar nav with links to all sub-pages

Adds a sticky left-hand sidebar to the repo settings page that lists
all 13 sub-settings destinations (General, Collaborators, Automation,
Branch Protection, Protected Tags, Rulesets, Webhooks, Environments,
Pages, Workflow Secrets, Mirrors, Dep Updater, Audit Log) so users
can navigate directly instead of typing URLs.

Uses .rsettings-* CSS class namespace, matches the existing dark theme
via CSS vars, highlights the active item, and collapses to a horizontal
strip on narrow viewports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1Wr1gducAJ51tSHJuJ9tY
Claude committed on June 17, 2026Parent: 5ce80b3
1 file changed+13802289dc6d2fe35ff783de8e2e1633d8b376fec302
1 changed file+138−0
Modifiedsrc/routes/repo-settings.tsx+138−0View fileUnifiedSplit
499499 .repo-settings-danger-head,
500500 .repo-settings-danger-body { padding-left: var(--space-4); padding-right: var(--space-4); }
501501 }
502
503 /* ─── Settings sidebar layout ─── */
504 .rsettings-layout {
505 display: flex;
506 gap: var(--space-5);
507 align-items: flex-start;
508 }
509 .rsettings-sidebar {
510 width: 220px;
511 flex-shrink: 0;
512 position: sticky;
513 top: var(--space-4);
514 background: var(--bg-elevated);
515 border: 1px solid var(--border);
516 border-radius: 12px;
517 overflow: hidden;
518 }
519 .rsettings-sidebar-label {
520 padding: 10px 14px 8px;
521 font-size: 11px;
522 font-weight: 700;
523 letter-spacing: 0.07em;
524 text-transform: uppercase;
525 color: var(--text-subtle, var(--text-muted));
526 border-bottom: 1px solid var(--border);
527 }
528 .rsettings-nav {
529 display: flex;
530 flex-direction: column;
531 padding: 6px;
532 gap: 2px;
533 }
534 .rsettings-nav-item {
535 display: flex;
536 align-items: center;
537 gap: 8px;
538 padding: 7px 10px;
539 border-radius: 8px;
540 font-size: 13.5px;
541 font-weight: 500;
542 color: var(--text-muted);
543 text-decoration: none;
544 transition: background 100ms ease, color 100ms ease;
545 line-height: 1.3;
546 }
547 .rsettings-nav-item:hover {
548 background: var(--bg-surface, rgba(255,255,255,0.04));
549 color: var(--text);
550 }
551 .rsettings-nav-item.is-active {
552 background: var(--bg-surface, rgba(255,255,255,0.06));
553 color: var(--text);
554 font-weight: 600;
555 }
556 .rsettings-nav-divider {
557 height: 1px;
558 background: var(--border);
559 margin: 4px 6px;
560 }
561 .rsettings-content {
562 flex: 1;
563 min-width: 0;
564 }
565 @media (max-width: 860px) {
566 .rsettings-layout { flex-direction: column; }
567 .rsettings-sidebar {
568 width: 100%;
569 position: static;
570 }
571 .rsettings-nav { flex-direction: row; flex-wrap: wrap; }
572 }
502573`;
503574
504575/** Hero header for the repo settings page. */
547618 );
548619}
549620
621/** Left-hand settings navigation sidebar. */
622function RepoSettingsSidebar(props: { owner: string; repo: string; currentPath: string }) {
623 const { owner, repo, currentPath } = props;
624 const base = `/${owner}/${repo}`;
625
626 type NavItem =
627 | { kind: "link"; label: string; href: string }
628 | { kind: "divider" };
629
630 const items: NavItem[] = [
631 { kind: "link", label: "General", href: `${base}/settings` },
632 { kind: "link", label: "Collaborators", href: `${base}/settings/collaborators` },
633 { kind: "link", label: "Automation", href: `${base}/settings/automation` },
634 { kind: "divider" },
635 { kind: "link", label: "Branch Protection", href: `${base}/gates` },
636 { kind: "link", label: "Protected Tags", href: `${base}/settings/protected-tags` },
637 { kind: "link", label: "Rulesets", href: `${base}/settings/rulesets` },
638 { kind: "divider" },
639 { kind: "link", label: "Webhooks", href: `${base}/settings/webhooks` },
640 { kind: "link", label: "Environments", href: `${base}/settings/environments` },
641 { kind: "link", label: "Pages", href: `${base}/settings/pages` },
642 { kind: "link", label: "Workflow Secrets", href: `${base}/settings/secrets` },
643 { kind: "divider" },
644 { kind: "link", label: "Mirrors", href: `${base}/settings/mirror` },
645 { kind: "link", label: "Dep Updater", href: `${base}/settings/dep-updater` },
646 { kind: "link", label: "Audit Log", href: `${base}/settings/audit` },
647 ];
648
649 return (
650 <nav class="rsettings-sidebar" aria-label="Settings navigation">
651 <div class="rsettings-sidebar-label">Settings</div>
652 <ul class="rsettings-nav" role="list" style="list-style:none;margin:0;padding:6px;">
653 {items.map((item) => {
654 if (item.kind === "divider") {
655 return <li class="rsettings-nav-divider" role="separator" />;
656 }
657 // Exact match for "General" (base settings URL), prefix match for sub-pages
658 const isActive =
659 item.href === `${base}/settings`
660 ? currentPath === `${base}/settings` || currentPath === `${base}/settings/`
661 : currentPath === item.href || currentPath.startsWith(item.href + "/");
662 return (
663 <li>
664 <a
665 href={item.href}
666 class={`rsettings-nav-item${isActive ? " is-active" : ""}`}
667 aria-current={isActive ? "page" : undefined}
668 >
669 {item.label}
670 </a>
671 </li>
672 );
673 })}
674 </ul>
675 </nav>
676 );
677}
678
550679// Settings page
551680repoSettings.get("/:owner/:repo/settings", requireAuth, requireRepoAccess("admin"), async (c) => {
552681 const { owner: ownerName, repo: repoName } = c.req.param();
596725 <style dangerouslySetInnerHTML={{ __html: repoSettingsStyles }} />
597726 <div class="repo-settings-container">
598727 <RepoSettingsHero owner={ownerName} repo={repoName} />
728 <div class="rsettings-layout">
729 <RepoSettingsSidebar
730 owner={ownerName}
731 repo={repoName}
732 currentPath={`/${ownerName}/${repoName}/settings`}
733 />
734 <div class="rsettings-content">
599735
600736 {success && (
601737 <Banner kind="success" text={decodeURIComponent(success)} />
13081444 </form>
13091445 </div>
13101446 </section>
1447 </div>{/* .rsettings-content */}
1448 </div>{/* .rsettings-layout */}
13111449 </div>
13121450 </Layout>
13131451 );
13141452