vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Add repository search, list pagination, and theme/a11y polish
5cab7b18548e5263c01549cb61415fd7777d1758 · Keenan Nemetz · 15d ago · parent 0b26547e
Add repository search, list pagination, and theme/a11y polish The chrome and the index page search repository names and descriptions after the same read filter, so a private repository cannot appear. Issues and changes paginate at 25 rows with filters and HTMX navigation preserved. Polish from the UI plan: a light token block follows prefers-color-scheme, a skip link and focus-visible outlines help keyboard users, panels scroll horizontally on narrow screens, and the empty index/search states explain what is visible. Also fixes a flaky key assertion in the admin users test.
server/web/static/vole.css +70
| @@ -1633,3 +1633,73 @@ form.htmx-request .label-tokens { opacity: 0.6; } | |||
| 1633 | 1633 | .danger-zone h3 { margin: 14px 0 4px; } | |
| 1634 | 1634 | .state.active { color: var(--good); } | |
| 1635 | 1635 | .state.retired { color: var(--fg-faint); } | |
| 1636 | |||
| 1637 | /* Global repository search lives in the chrome on wide screens; the index page | ||
| 1638 | keeps its own visible form so the control is still reachable on mobile. */ | ||
| 1639 | .topsearch { margin-left: auto; } | ||
| 1640 | .topsearch input[type="search"] { width: 12rem; max-width: 28vw; } | ||
| 1641 | .topright { margin-left: 0; } | ||
| 1642 | @media (max-width: 720px) { | ||
| 1643 | .topsearch { display: none; } | ||
| 1644 | .topright { margin-left: auto; } | ||
| 1645 | } | ||
| 1646 | |||
| 1647 | /* Keyboard users get out of the chrome before the tabs. */ | ||
| 1648 | .skip-link { | ||
| 1649 | position: absolute; | ||
| 1650 | left: -999px; | ||
| 1651 | top: 8px; | ||
| 1652 | z-index: 100; | ||
| 1653 | padding: 6px 10px; | ||
| 1654 | background: var(--bg-raised); | ||
| 1655 | border: 1px solid var(--accent); | ||
| 1656 | border-radius: var(--radius); | ||
| 1657 | } | ||
| 1658 | .skip-link:focus { left: 12px; } | ||
| 1659 | |||
| 1660 | a:focus-visible, | ||
| 1661 | button:focus-visible, | ||
| 1662 | select:focus-visible, | ||
| 1663 | input:focus-visible, | ||
| 1664 | textarea:focus-visible { | ||
| 1665 | outline: 2px solid var(--accent); | ||
| 1666 | outline-offset: 2px; | ||
| 1667 | } | ||
| 1668 | |||
| 1669 | @media (prefers-reduced-motion: reduce) { | ||
| 1670 | *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; } | ||
| 1671 | } | ||
| 1672 | |||
| 1673 | @media (max-width: 720px) { | ||
| 1674 | .panel { overflow-x: auto; } | ||
| 1675 | .panel > table { min-width: 34rem; } | ||
| 1676 | } | ||
| 1677 | |||
| 1678 | /* A light theme is the same layout with a second set of tokens. It follows the | ||
| 1679 | operating system until a real theme switch exists. */ | ||
| 1680 | @media (prefers-color-scheme: light) { | ||
| 1681 | :root { | ||
| 1682 | color-scheme: light; | ||
| 1683 | --bg: #f7f7f8; | ||
| 1684 | --bg-panel: #ffffff; | ||
| 1685 | --bg-raised: #f1f2f4; | ||
| 1686 | --bg-hover: #eceef2; | ||
| 1687 | --bg-active: #e4e7ec; | ||
| 1688 | --border: #dfe2e7; | ||
| 1689 | --border-strong: #c7ccd5; | ||
| 1690 | --fg: #1b1e23; | ||
| 1691 | --fg-muted: #5d646e; | ||
| 1692 | --fg-faint: #858c96; | ||
| 1693 | --accent: #4f5bd5; | ||
| 1694 | --accent-fg: #ffffff; | ||
| 1695 | --accent-soft: rgba(79, 91, 213, 0.12); | ||
| 1696 | --good: #247f55; | ||
| 1697 | --good-soft: rgba(36, 127, 85, 0.12); | ||
| 1698 | --bad: #bd3b3b; | ||
| 1699 | --bad-soft: rgba(189, 59, 59, 0.10); | ||
| 1700 | --warn: #946200; | ||
| 1701 | --warn-soft: rgba(148, 98, 0, 0.12); | ||
| 1702 | } | ||
| 1703 | } | ||
| 1704 | .pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 12px 0; } | ||
| 1705 | .pager .button { font-size: 12px; } | ||