CLAUDE.md
+0
-80
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index b920156..0000000
@@ -1,80 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
GitBrowser is an ASP.NET Core Blazor web application that provides single-user GitHub-like views for self-hosted git repositories. The project targets .NET 10.0 (preview) and uses Blazor for the frontend with a familiar GitHub-style interface for browsing files, commits, and repository contents.
## Build and Development Commands
### Build the project
```bash
dotnet build
```
### Run the application (development)
```bash
dotnet run --project GitBrowser
```
The application runs on:
- HTTP: http://localhost:5186
- HTTPS: https://localhost:7270
### Format code
Code formatting is handled automatically via CSharpier.MsBuild during build. To manually format:
```bash
dotnet csharpier format .
```
## Project Configuration
### SDK and Framework
- Targets .NET 10.0 (RC2, preview SDK)
- Uses Central Package Management (CPM) - package versions are defined in `Directory.Packages.props`
- Package lock files are enabled (`RestorePackagesWithLockFile`)
- Implicit usings are disabled - all namespaces must be explicitly imported
### Code Style
- EditorConfig enforces consistent formatting:
- C# files: 4 spaces, max 120 chars per line
- XML/JSON/config: 2 spaces
- HTML/Razor: 2 spaces
- CSS: 2 spaces
- Line endings: LF (Unix-style)
## Architecture
### Project Structure
- **GitBrowser/** - Main web application project
- **Components/** - Blazor components
- **Pages/** - Routable page components (Home, Error, NotFound)
- **Layout/** - Layout components (MainLayout, NavMenu)
- **Routes.razor** - Routing configuration
- **App.razor** - Root application component
- **_Imports.razor** - Global Razor imports
- **wwwroot/** - Static assets (CSS, favicon)
- **Program.cs** - Application entry point and configuration
### Application Setup (Program.cs)
- Uses ASP.NET Core minimal hosting model
- Razor Components are registered as services
- Static Assets pipeline enabled (replaces traditional static files)
- Antiforgery protection enabled
- Status code pages configured for 404 handling
- HSTS enabled in production
- Blazor navigation exceptions disabled via `BlazorDisableThrowNavigationException`
### Blazor Configuration
- Uses static rendering (no interactive render modes configured yet)
- Routes are defined in `Components/Routes.razor`
- Main layout in `Components/Layout/MainLayout.razor`
- Scoped CSS is supported (e.g., `MainLayout.razor.css`)
## Important Notes
- No implicit usings: Always add explicit `using` statements for all namespaces
- Package versions must be managed in `Directory.Packages.props` (CPM enabled)
- When adding new packages, update `packages.lock.json` via `dotnet restore`
- The project uses .NET 10.0 RC which requires the specific SDK version in `global.json`
GitBrowser/Components/Pages/Repo.razor
+21
-2
diff --git a/GitBrowser/Components/Pages/Repo.razor b/GitBrowser/Components/Pages/Repo.razor
index 9c9bc05..8fcb7cd 100644
@@ -11,8 +11,27 @@
<div class="container">
<nav class="breadcrumb">
<a href="/@Uri.EscapeDataString(RepoName)/tree/@Uri.EscapeDataString(currentBranch)"
class="branch-indicator">@currentBranch</a>
@if (availableBranches.Count > 1)
{
<div class="branch-dropdown">
<input type="checkbox" id="branch-dropdown-toggle" class="branch-dropdown-checkbox" />
<label for="branch-dropdown-toggle" class="dropdown-overlay"></label>
<label for="branch-dropdown-toggle" class="branch-indicator">
<span class="branch-name">@currentBranch</span>
<span class="dropdown-arrow">▼</span>
</label>
<div class="dropdown-list">
@foreach (var branch in availableBranches.Where(b => b != currentBranch))
{
<a href="/@Uri.EscapeDataString(RepoName)/tree/@Uri.EscapeDataString(branch)" class="dropdown-item">@branch</a>
}
</div>
</div>
}
else
{
<span class="branch-indicator">@currentBranch</span>
}
@if (!string.IsNullOrEmpty(currentPath))
{
var pathParts = currentPath.Split('/');
GitBrowser/Components/Pages/Repo.razor.cs
+8
-0
diff --git a/GitBrowser/Components/Pages/Repo.razor.cs b/GitBrowser/Components/Pages/Repo.razor.cs
index 8b661ae..770b343 100644
@@ -33,6 +33,7 @@ public sealed partial class Repo(
private bool isReadme = false;
private Repository? repo = null;
private LibGit2Sharp.Commit? currentCommit = null;
private List<string> availableBranches = [];
private record CommitInfo(string MessageShort, DateTimeOffset When, string Sha);
@@ -67,6 +68,13 @@ public sealed partial class Repo(
repo = repoInfo.Repository;
// Enumerate and sort available branches by commit date (newest first)
availableBranches = repo
.Branches.Where(b => !b.IsRemote)
.OrderByDescending(b => b.Tip.Author.When)
.Select(b => b.FriendlyName)
.ToList();
// Determine which branch/commit to use
var branchOrCommit = Branch ?? repo.Head.FriendlyName;
currentPath = Path ?? "";
GitBrowser/Components/Pages/Repo.razor.css
+98
-0
diff --git a/GitBrowser/Components/Pages/Repo.razor.css b/GitBrowser/Components/Pages/Repo.razor.css
index 2070cc0..ea48cb1 100644
@@ -21,15 +21,113 @@
color: var(--gh-color-fg-default);
}
/* Branch dropdown container */
.branch-dropdown {
position: relative;
display: inline-block;
}
/* Hide the checkbox */
.branch-dropdown-checkbox {
display: none;
}
/* Overlay for click-outside-to-close */
.dropdown-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
cursor: default;
display: none;
}
/* Show overlay when dropdown is open */
.branch-dropdown-checkbox:checked ~ .dropdown-overlay {
display: block;
}
/* Branch indicator label (clickable trigger) */
.branch-indicator {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.5rem;
background: var(--gh-color-canvas-subtle);
border: 1px solid var(--gh-color-border-default);
border-radius: 6px;
font-weight: 600;
font-size: 0.75rem;
user-select: none;
}
label.branch-indicator {
cursor: pointer;
}
label.branch-indicator:hover {
background: var(--gh-color-canvas-inset);
border-color: var(--gh-color-border-muted);
}
/* Dropdown arrow */
.dropdown-arrow {
font-size: 0.625rem;
color: var(--gh-color-fg-muted);
transition: transform 0.2s ease;
}
/* Rotate arrow when dropdown is open */
.branch-dropdown-checkbox:checked ~ .branch-indicator .dropdown-arrow {
transform: rotate(180deg);
}
/* Dropdown list */
.dropdown-list {
position: absolute;
top: calc(100% + 0.25rem);
left: 0;
min-width: 200px;
max-height: 400px;
overflow-y: auto;
background: var(--gh-color-canvas-subtle);
border: 1px solid var(--gh-color-border-default);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
z-index: 1000;
opacity: 0;
visibility: hidden;
transform: translateY(-0.5rem);
transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
}
/* Show dropdown when checkbox is checked */
.branch-dropdown-checkbox:checked ~ .dropdown-list {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
/* Dropdown items */
.dropdown-item {
display: block;
padding: 0.5rem 0.75rem;
color: var(--gh-color-fg-default);
text-decoration: none;
font-size: 0.875rem;
border-bottom: 1px solid var(--gh-color-border-default);
transition: background-color 0.1s ease;
}
.dropdown-item:last-child {
border-bottom: none;
}
.dropdown-item:hover {
background: var(--gh-color-canvas-subtle);
color: var(--gh-color-accent-fg);
}
.breadcrumb .separator {