- {(
- issue.reportedByUser?.name ??
- issue.invitedReporter?.name ??
- issue.reporterName ??
- "A"
- )
- .slice(0, 1)
- .toUpperCase()}
+ {reporter.initial}
- {issue.reportedByUser?.name ??
- issue.invitedReporter?.name ??
- issue.reporterName ??
- "Anonymous"}
+ {reporter.name}
- {isUserMachineOwner(
- issue,
- issue.reportedByUser?.id ??
- issue.invitedReporter?.id ??
- null
- ) && }
+ {isUserMachineOwner(issue, reporter.id) && (
+
+ )}
- {(issue.reportedByUser?.email ??
- issue.invitedReporter?.email ??
- issue.reporterEmail) && (
+ {reporter.email && (
- {issue.reportedByUser?.email ??
- issue.invitedReporter?.email ??
- issue.reporterEmail}
+ {reporter.email}
)}
diff --git a/src/components/issues/IssueTimeline.tsx b/src/components/issues/IssueTimeline.tsx
index 65207a09..b2fb5cbb 100644
--- a/src/components/issues/IssueTimeline.tsx
+++ b/src/components/issues/IssueTimeline.tsx
@@ -6,6 +6,7 @@ import { OwnerBadge } from "~/components/issues/OwnerBadge";
import { isUserMachineOwner } from "~/lib/issues/owner";
import { type IssueWithAllRelations } from "~/lib/types";
import { cn } from "~/lib/utils";
+import { resolveIssueReporter } from "~/lib/issues/utils";
// ----------------------------------------------------------------------
// Types
@@ -144,26 +145,16 @@ export function IssueTimeline({
issue,
}: IssueTimelineProps): React.JSX.Element {
// 1. Normalize Issue as the first event
- const reporterName =
- issue.reportedByUser?.name ??
- issue.invitedReporter?.name ??
- issue.reporterName ??
- "Anonymous";
- const reporterEmail =
- issue.reportedByUser?.email ??
- issue.invitedReporter?.email ??
- issue.reporterEmail;
- const reporterId =
- issue.reportedByUser?.id ?? issue.invitedReporter?.id ?? null;
+ const reporter = resolveIssueReporter(issue);
const issueEvent: TimelineEvent = {
id: `issue-${issue.id}`,
type: "issue",
author: {
- id: reporterId,
- name: reporterName,
- avatarFallback: reporterName.slice(0, 2).toUpperCase(),
- email: reporterEmail,
+ id: reporter.id ?? null,
+ name: reporter.name,
+ avatarFallback: reporter.initial,
+ email: reporter.email,
},
createdAt: new Date(issue.createdAt),
content: issue.description,
diff --git a/src/lib/issues/utils.test.ts b/src/lib/issues/utils.test.ts
new file mode 100644
index 00000000..7027039d
--- /dev/null
+++ b/src/lib/issues/utils.test.ts
@@ -0,0 +1,51 @@
+import { describe, it, expect } from "vitest";
+import { resolveIssueReporter } from "./utils";
+
+describe("resolveIssueReporter", () => {
+ it("resolves reportedByUser", () => {
+ const issue = {
+ reportedByUser: { id: "user-1", name: "User", email: "user@example.com" },
+ };
+ expect(resolveIssueReporter(issue)).toEqual({
+ id: "user-1",
+ name: "User",
+ email: "user@example.com",
+ initial: "U",
+ });
+ });
+
+ it("resolves invitedReporter if no reportedByUser", () => {
+ const issue = {
+ invitedReporter: { id: "invited-1", name: "Invited", email: "invited@example.com" },
+ };
+ expect(resolveIssueReporter(issue)).toEqual({
+ id: "invited-1",
+ name: "Invited",
+ email: "invited@example.com",
+ initial: "I",
+ });
+ });
+
+ it("resolves reporterName/Email if no user/invited", () => {
+ const issue = {
+ reporterName: "Legacy",
+ reporterEmail: "legacy@example.com",
+ };
+ expect(resolveIssueReporter(issue)).toEqual({
+ id: null,
+ name: "Legacy",
+ email: "legacy@example.com",
+ initial: "L",
+ });
+ });
+
+ it("falls back to Anonymous", () => {
+ const issue = {};
+ expect(resolveIssueReporter(issue)).toEqual({
+ id: null,
+ name: "Anonymous",
+ email: null,
+ initial: "A",
+ });
+ });
+});
diff --git a/src/lib/issues/utils.ts b/src/lib/issues/utils.ts
index 19ca243d..99b54462 100644
--- a/src/lib/issues/utils.ts
+++ b/src/lib/issues/utils.ts
@@ -1,3 +1,39 @@
export function formatIssueId(initials: string, number: number): string {
return `${initials.toUpperCase()}-${number.toString().padStart(2, "0")}`;
}
+
+export interface IssueReporterInfo {
+ reportedByUser?: { id?: string; name: string; email?: string | null } | null;
+ invitedReporter?: { id?: string; name: string; email?: string | null } | null;
+ reporterName?: string | null;
+ reporterEmail?: string | null;
+}
+
+export function resolveIssueReporter(issue: IssueReporterInfo): {
+ id?: string | null;
+ name: string;
+ email?: string | null;
+ initial: string;
+} {
+ const name =
+ issue.reportedByUser?.name ??
+ issue.invitedReporter?.name ??
+ issue.reporterName ??
+ "Anonymous";
+
+ const email =
+ issue.reportedByUser?.email ??
+ issue.invitedReporter?.email ??
+ issue.reporterEmail ??
+ null;
+
+ const id =
+ issue.reportedByUser?.id ?? issue.invitedReporter?.id ?? null;
+
+ return {
+ id,
+ name,
+ email,
+ initial: (name[0] ?? "A").toUpperCase(),
+ };
+}
diff --git a/src/services/issues.ts b/src/services/issues.ts
index cd89b207..7f2b14c0 100644
--- a/src/services/issues.ts
+++ b/src/services/issues.ts
@@ -133,8 +133,15 @@ export async function createIssue({
if (!issue) throw new Error("Issue creation failed");
// 3. Create Timeline Event
- const reporterDesc =
- reporterName ?? reporterEmail ?? (reportedBy ? "Member" : "Guest");
+ let reporterDesc = reporterName ?? reporterEmail ?? "Guest";
+ if (reportedBy) {
+ const user = await tx.query.userProfiles.findFirst({
+ where: eq(userProfiles.id, reportedBy),
+ columns: { name: true },
+ });
+ reporterDesc = user?.name ?? "Member";
+ }
+
await createTimelineEvent(
issue.id,
`Issue reported by ${reporterDesc}`,
diff --git a/src/test/integration/supabase/issue-services.test.ts b/src/test/integration/supabase/issue-services.test.ts
index b6b1cbf8..3834c2af 100644
--- a/src/test/integration/supabase/issue-services.test.ts
+++ b/src/test/integration/supabase/issue-services.test.ts
@@ -298,7 +298,7 @@ describe("Issue Service Functions (Integration)", () => {
const reportEvent = events.find(
(e: any) => e.isSystem && e.content.includes("Issue reported by")
);
- expect(reportEvent?.content).toBe("Issue reported by Member");
+ expect(reportEvent?.content).toBe("Issue reported by Test User");
});
});
});
diff --git a/supabase/seed-users.mjs b/supabase/seed-users.mjs
index cef76347..a2743635 100644
--- a/supabase/seed-users.mjs
+++ b/supabase/seed-users.mjs
@@ -247,14 +247,23 @@ async function seedUsersAndData() {
`;
// Add "Issue reported by..." system comment to match service logic
- const reporterDesc =
- issue.reporterName ??
- issue.reporterEmail ??
- (issue.reportedBy
- ? "Member"
- : issue.invitedUserId
- ? "Invited User"
- : "Guest");
+ let reporterDesc = issue.reporterName ?? issue.reporterEmail ?? "Guest";
+
+ if (issue.reportedBy) {
+ // In real app, we fetch the user name. Here we can check the user map.
+ // Or just default to "Member" if we don't want to lookup.
+ // But to be consistent with new service logic, let's try to be specific if possible.
+ // For seed simplicity, "Member" is often used, but let's try a realistic fallback.
+ const memberRole = Object.keys(userIds).find(
+ (role) => userIds[role] === issue.reportedBy
+ );
+ reporterDesc = memberRole
+ ? `${memberRole.charAt(0).toUpperCase() + memberRole.slice(1)} User`
+ : "Member";
+ } else if (issue.invitedUserId) {
+ // For seeding, we know the invited user is 'Jane Doe'
+ reporterDesc = "Invited User";
+ }
await sql`
INSERT INTO issue_comments (issue_id, author_id, content, is_system, created_at, updated_at)
From 3ec427102697e6b57d87070e6c54961e39ea5ff1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 19 Jan 2026 18:12:29 +0000
Subject: [PATCH 5/5] Change "Owner" to "Game Owner" in badge and labels
Co-authored-by: timothyfroehlich <5819722+timothyfroehlich@users.noreply.github.com>
---
src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx | 2 +-
src/components/issues/OwnerBadge.test.tsx | 2 +-
src/components/issues/OwnerBadge.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx b/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx
index 20931038..43c941a0 100644
--- a/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx
+++ b/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx
@@ -184,7 +184,7 @@ export default async function IssueDetailPage({
{ownerName && (
•
- Owner:
+ Game Owner:
{ownerName}
diff --git a/src/components/issues/OwnerBadge.test.tsx b/src/components/issues/OwnerBadge.test.tsx
index 6a26745d..7962ee01 100644
--- a/src/components/issues/OwnerBadge.test.tsx
+++ b/src/components/issues/OwnerBadge.test.tsx
@@ -8,7 +8,7 @@ describe("OwnerBadge", () => {
const badge = screen.getByTestId("owner-badge");
expect(badge).toBeInTheDocument();
- expect(badge).toHaveTextContent("Owner");
+ expect(badge).toHaveTextContent("Game Owner");
});
it("renders with default size", () => {
diff --git a/src/components/issues/OwnerBadge.tsx b/src/components/issues/OwnerBadge.tsx
index ac492eda..9e5bd278 100644
--- a/src/components/issues/OwnerBadge.tsx
+++ b/src/components/issues/OwnerBadge.tsx
@@ -30,7 +30,7 @@ export function OwnerBadge({
data-testid="owner-badge"
>
- Owner
+ Game Owner
);
}