Detailed changes
@@ -14,7 +14,6 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/lib/auth";
-import { useRepo } from "@/lib/repo";
import { LabelBadge } from "./LabelBadge";
@@ -23,6 +22,7 @@ type TimelineNode = NonNullable<
>;
interface TimelineProps {
+ repo: string | null;
bugPrefix: string;
items: TimelineNode[];
}
@@ -30,20 +30,20 @@ interface TimelineProps {
// Ordered sequence of events on a bug: comments (create and add-comment) and
// inline events (label changes, status changes, title edits). Comment items
// support inline editing for the logged-in user.
-export function Timeline({ bugPrefix, items }: TimelineProps) {
+export function Timeline({ repo, bugPrefix, items }: TimelineProps) {
return (
<div className="space-y-4">
{items.map((item) => {
switch (item.__typename) {
case "BugCreateTimelineItem":
case "BugAddCommentTimelineItem":
- return <CommentItem key={item.id} item={item} bugPrefix={bugPrefix} />;
+ return <CommentItem key={item.id} item={item} bugPrefix={bugPrefix} repo={repo} />;
case "BugLabelChangeTimelineItem":
- return <LabelChangeItem key={item.id} item={item} />;
+ return <LabelChangeItem key={item.id} item={item} repo={repo} />;
case "BugSetStatusTimelineItem":
- return <StatusChangeItem key={item.id} item={item} />;
+ return <StatusChangeItem key={item.id} item={item} repo={repo} />;
case "BugSetTitleTimelineItem":
- return <TitleChangeItem key={item.id} item={item} />;
+ return <TitleChangeItem key={item.id} item={item} repo={repo} />;
default:
return null;
}
@@ -59,9 +59,16 @@ type CommentItem = Extract<
{ __typename: "BugCreateTimelineItem" | "BugAddCommentTimelineItem" }
>;
-function CommentItem({ item, bugPrefix }: { item: CommentItem; bugPrefix: string }) {
+function CommentItem({
+ item,
+ bugPrefix,
+ repo,
+}: {
+ item: CommentItem;
+ bugPrefix: string;
+ repo: string | null;
+}) {
const { user } = useAuth();
- const repo = useRepo();
const [editing, setEditing] = useState(false);
const [editValue, setEditValue] = useState(item.message ?? "");
@@ -172,8 +179,7 @@ function EventRow({ icon, children }: { icon: React.ReactNode; children: React.R
);
}
-function LabelChangeItem({ item }: { item: LabelChangeItem }) {
- const repo = useRepo();
+function LabelChangeItem({ item, repo }: { item: LabelChangeItem; repo: string | null }) {
return (
<EventRow icon={<Tag className="size-4" />}>
<span>
@@ -206,8 +212,7 @@ function LabelChangeItem({ item }: { item: LabelChangeItem }) {
);
}
-function StatusChangeItem({ item }: { item: StatusChangeItem }) {
- const repo = useRepo();
+function StatusChangeItem({ item, repo }: { item: StatusChangeItem; repo: string | null }) {
const isOpen = item.status === Status.Open;
return (
<EventRow
@@ -234,8 +239,7 @@ function StatusChangeItem({ item }: { item: StatusChangeItem }) {
);
}
-function TitleChangeItem({ item }: { item: TitleChangeItem }) {
- const repo = useRepo();
+function TitleChangeItem({ item, repo }: { item: TitleChangeItem; repo: string | null }) {
return (
<EventRow icon={<Pencil className="size-4" />}>
<span>
@@ -10,7 +10,6 @@ import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
const COMMITS_QUERY = gql`
query CommitList($repo: String, $ref: String!, $path: String, $after: String, $first: Int) {
@@ -44,6 +43,7 @@ interface CommitListQueryData {
}
interface CommitListProps {
+ repo: string | null;
ref_: string;
path?: string;
}
@@ -56,8 +56,7 @@ type CommitNode = {
date: string;
};
-export function CommitList({ ref_, path }: CommitListProps) {
- const repo = useRepo();
+export function CommitList({ repo, ref_, path }: CommitListProps) {
const [cursor, setCursor] = useState<string | null>(null);
const [allCommits, setAllCommits] = useState<CommitNode[]>([]);
@@ -6,7 +6,6 @@ import { useLazyQuery } from "@apollo/client/react";
import { ChevronRight, FilePlus, FileMinus, FileEdit } from "lucide-react";
import { useState } from "react";
-import { useRepo } from "@/lib/repo";
import { cn } from "@/lib/utils";
const DIFF_QUERY = gql`
@@ -53,6 +52,7 @@ interface DiffQueryData {
}
interface FileDiffViewProps {
+ repo: string | null;
hash: string;
path: string;
oldPath?: string;
@@ -72,8 +72,7 @@ const statusBadge: Record<string, string> = {
RENAMED: "R",
};
-export function FileDiffView({ hash, path, oldPath, status }: FileDiffViewProps) {
- const repo = useRepo();
+export function FileDiffView({ repo, hash, path, oldPath, status }: FileDiffViewProps) {
const [open, setOpen] = useState(false);
const [fetchDiff, { data, loading, error }] = useLazyQuery<DiffQueryData>(DIFF_QUERY);
@@ -4,7 +4,6 @@ import { Folder, File } from "lucide-react";
import { GitObjectType, type GitTreeEntry } from "@/__generated__/graphql";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
export interface TreeEntryWithCommit extends GitTreeEntry {
lastCommit?: {
@@ -16,6 +15,7 @@ export interface TreeEntryWithCommit extends GitTreeEntry {
}
interface FileTreeProps {
+ repo: string | null;
entries: TreeEntryWithCommit[];
path: string;
loading?: boolean;
@@ -25,7 +25,14 @@ interface FileTreeProps {
// Directory listing table for the code browser. Shows each entry's icon,
// name, last-commit message (linked to commit detail), and relative date.
-export function FileTree({ entries, path, loading, onNavigate, onNavigateUp }: FileTreeProps) {
+export function FileTree({
+ repo,
+ entries,
+ path,
+ loading,
+ onNavigate,
+ onNavigateUp,
+}: FileTreeProps) {
// Directories first, then files — each group alphabetical
const sorted = entries.toSorted((a, b) => {
if (a.type !== b.type) return a.type === GitObjectType.Tree ? -1 : 1;
@@ -49,7 +56,7 @@ export function FileTree({ entries, path, loading, onNavigate, onNavigateUp }: F
</tr>
)}
{sorted.map((entry) => (
- <FileTreeRow key={entry.name} entry={entry} onNavigate={onNavigate} />
+ <FileTreeRow key={entry.name} entry={entry} repo={repo} onNavigate={onNavigate} />
))}
</tbody>
</table>
@@ -59,13 +66,14 @@ export function FileTree({ entries, path, loading, onNavigate, onNavigateUp }: F
function FileTreeRow({
entry,
+ repo,
onNavigate,
}: {
entry: TreeEntryWithCommit;
+ repo: string | null;
onNavigate: (entry: TreeEntryWithCommit) => void;
}) {
const isDir = entry.type === GitObjectType.Tree;
- const repo = useRepo();
return (
<tr className="hover:bg-muted/40 cursor-pointer" onClick={() => onNavigate(entry)}>
@@ -1,12 +0,0 @@
-// Returns the resolved repo ref from the router context.
-// Returns null when rendered outside of a /$repo route (e.g. the picker page).
-//
-// The $repo route's beforeLoad normalizes the slug ("_" → null) and provides
-// it as context.ref, so callers don't need to handle the "_" case.
-
-import { useRouteContext } from "@tanstack/react-router";
-
-export function useRepo(): string | null {
- const context = useRouteContext({ strict: false });
- return (context as { ref?: string | null }).ref ?? null;
-}
@@ -9,7 +9,6 @@ import { ArrowLeft, GitCommit } from "lucide-react";
import { FileDiffView } from "@/components/code/FileDiffView";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
const COMMIT_QUERY = gql`
query CommitPageDetail($repo: String, $hash: String!) {
@@ -65,7 +64,7 @@ export const Route = createFileRoute("/$repo/commit/$hash")({
});
function RouteComponent() {
- const repo = useRepo();
+ const { ref: repo } = Route.useRouteContext();
const { commitRef } = Route.useLoaderData();
const { data } = useReadQuery(commitRef);
@@ -137,6 +136,7 @@ function RouteComponent() {
{files.map((file: { path: string; oldPath?: string | null; status: string }) => (
<FileDiffView
key={file.path}
+ repo={repo}
hash={commit.hash}
path={file.path}
oldPath={file.oldPath ?? undefined}
@@ -24,7 +24,6 @@ import { RefSelector } from "@/components/code/RefSelector";
import { Markdown } from "@/components/content/Markdown";
import { ButtonLink } from "@/components/ui/button-link";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
const REFS_QUERY = gql`
query CodePageRefs($repo: String) {
@@ -134,7 +133,7 @@ export const Route = createFileRoute("/$repo/")({
});
function RouteComponent() {
- const repo = useRepo();
+ const { ref: repo } = Route.useRouteContext();
const navigate = useNavigate({ from: "/$repo/" });
const { ref: currentRef, path: currentPath, type: viewMode } = useSearch({ from: "/$repo/" });
@@ -257,10 +256,11 @@ function RouteComponent() {
</div>
{viewMode === "commits" ? (
- <CommitList ref_={currentRef} path={currentPath || undefined} />
+ <CommitList repo={repo} ref_={currentRef} path={currentPath || undefined} />
) : viewMode === "tree" || !blob ? (
<>
<FileTree
+ repo={repo}
entries={entriesWithCommits}
path={currentPath}
loading={treeLoading}
@@ -12,7 +12,6 @@ import { TitleEditor } from "@/components/bugs/TitleEditor";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
export const Route = createFileRoute("/$repo/issues/$id")({
component: RouteComponent,
@@ -28,7 +27,7 @@ export const Route = createFileRoute("/$repo/issues/$id")({
// Issue detail page (/:repo/issues/:id). Shows title, status, timeline of
// comments and events, and a sidebar with labels and participants.
function RouteComponent() {
- const repo = useRepo();
+ const { ref: repo } = Route.useRouteContext();
const { bugDetailRef } = Route.useLoaderData();
const { labelsRef } = Route.useRouteContext();
const { data } = useReadQuery(bugDetailRef);
@@ -76,7 +75,7 @@ function RouteComponent() {
<div className="flex gap-8">
{/* Timeline + comment box */}
<div className="min-w-0 flex-1 space-y-4">
- <Timeline bugPrefix={bug.humanId} items={bug.timeline.nodes} />
+ <Timeline repo={repo} bugPrefix={bug.humanId} items={bug.timeline.nodes} />
<CommentBox bugPrefix={bug.humanId} bugStatus={bug.status} ref_={repo} />
</div>
@@ -12,7 +12,6 @@ import { QueryInput } from "@/components/bugs/QueryInput";
import { Button } from "@/components/ui/button";
import { ButtonLink } from "@/components/ui/button-link";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
import { cn } from "@/lib/utils";
const issuesSearchSchema = v.object({
@@ -47,7 +46,7 @@ const PAGE_SIZE = 25;
type StatusFilter = "open" | "closed";
function RouteComponent() {
- const repo = useRepo();
+ const { ref: repo } = Route.useRouteContext();
const navigate = useNavigate({ from: "/$repo/issues/" });
const { q, after } = Route.useSearch();
@@ -8,7 +8,6 @@ import { Button } from "@/components/ui/button";
import { ButtonLink } from "@/components/ui/button-link";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
-import { useRepo } from "@/lib/repo";
export const Route = createFileRoute("/$repo/issues/new")({
component: RouteComponent,
@@ -17,7 +16,7 @@ export const Route = createFileRoute("/$repo/issues/new")({
// New issue form (/:repo/issues/new). Title + body with write/preview tabs.
function RouteComponent() {
const navigate = useNavigate();
- const repo = useRepo();
+ const { ref: repo } = Route.useRouteContext();
const [title, setTitle] = useState("");
const [message, setMessage] = useState("");
const [preview, setPreview] = useState(false);
@@ -24,7 +24,6 @@ import { LabelBadge } from "@/components/bugs/LabelBadge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
-import { useRepo } from "@/lib/repo";
import { cn } from "@/lib/utils";
export const Route = createFileRoute("/$repo/user/$id")({
@@ -35,7 +34,7 @@ const PAGE_SIZE = 25;
function RouteComponent() {
const { id } = useParams({ strict: false });
- const repo = useRepo();
+ const { ref: repo } = Route.useRouteContext();
const [statusFilter, setStatusFilter] = useState<"open" | "closed">("open");
// Cursor-stack pagination: cursors[i] is the `after` value to fetch page i.