diff --git a/web/src/lib/components/CourseTable.svelte b/web/src/lib/components/CourseTable.svelte
index 5386167..a7e954f 100644
--- a/web/src/lib/components/CourseTable.svelte
+++ b/web/src/lib/components/CourseTable.svelte
@@ -551,20 +551,31 @@ const table = createSvelteTable({
{@const primary = getPrimaryInstructor(
course.instructors,
)}
+ {@const display = primaryInstructorDisplay(course)}
+ {@const commaIdx = display.indexOf(", ")}
-
+ {#if display === "Staff"}
{primaryInstructorDisplay(
- course,
- )}Staff
-
+ {:else}
+
+ {#if commaIdx !== -1}
+ {display.slice(0, commaIdx)},
+ {display.slice(commaIdx + 1)}
+ {:else}
+ {display}
+ {/if}
+
+ {/if}
{#if primaryRating(course)}
{@const r =
primaryRating(course)!}
diff --git a/web/src/lib/course.test.ts b/web/src/lib/course.test.ts
index cb8cba0..6aaac34 100644
--- a/web/src/lib/course.test.ts
+++ b/web/src/lib/course.test.ts
@@ -101,11 +101,29 @@ describe("formatMeetingTime", () => {
});
describe("abbreviateInstructor", () => {
- it("abbreviates standard name", () =>
- expect(abbreviateInstructor("Heaps, John")).toBe("Heaps, J."));
+ it("returns short names unabbreviated", () =>
+ expect(abbreviateInstructor("Li, Bo")).toBe("Li, Bo"));
+ it("returns names within budget unabbreviated", () =>
+ expect(abbreviateInstructor("Heaps, John")).toBe("Heaps, John"));
it("handles no comma", () => expect(abbreviateInstructor("Staff")).toBe("Staff"));
- it("handles multiple first names", () =>
- expect(abbreviateInstructor("Smith, Mary Jane")).toBe("Smith, M."));
+
+ // Progressive abbreviation with multiple given names
+ it("abbreviates trailing given names first", () =>
+ expect(abbreviateInstructor("Ramirez, Maria Elena")).toBe("Ramirez, Maria E."));
+ it("abbreviates all given names when needed", () =>
+ expect(abbreviateInstructor("Ramirez, Maria Elena", 16)).toBe("Ramirez, M. E."));
+ it("falls back to first initial only", () =>
+ expect(abbreviateInstructor("Ramirez, Maria Elena", 12)).toBe("Ramirez, M."));
+
+ // Single given name that exceeds budget
+ it("abbreviates single given name when over budget", () =>
+ expect(abbreviateInstructor("Bartholomew, Christopher", 18)).toBe("Bartholomew, C."));
+
+ // Respects custom maxLen
+ it("keeps full name when within custom budget", () =>
+ expect(abbreviateInstructor("Ramirez, Maria Elena", 30)).toBe("Ramirez, Maria Elena"));
+ it("always abbreviates when budget is tiny", () =>
+ expect(abbreviateInstructor("Heaps, John", 5)).toBe("Heaps, J."));
});
describe("getPrimaryInstructor", () => {
diff --git a/web/src/lib/course.ts b/web/src/lib/course.ts
index 82498a9..a402e3c 100644
--- a/web/src/lib/course.ts
+++ b/web/src/lib/course.ts
@@ -54,13 +54,45 @@ export function formatMeetingTime(mt: DbMeetingTime): string {
return `${days} ${begin}–${end}`;
}
-/** Abbreviate instructor name: "Heaps, John" → "Heaps, J." */
-export function abbreviateInstructor(name: string): string {
+/**
+ * Progressively abbreviate an instructor name to fit within a character budget.
+ *
+ * Tries each level until the result fits `maxLen`:
+ * 1. Full name: "Ramirez, Maria Elena"
+ * 2. Abbreviate trailing given names: "Ramirez, Maria E."
+ * 3. Abbreviate all given names: "Ramirez, M. E."
+ * 4. First initial only: "Ramirez, M."
+ *
+ * Names without a comma (e.g. "Staff") are returned as-is.
+ */
+export function abbreviateInstructor(name: string, maxLen: number = 18): string {
+ if (name.length <= maxLen) return name;
+
const commaIdx = name.indexOf(", ");
if (commaIdx === -1) return name;
+
const last = name.slice(0, commaIdx);
- const first = name.slice(commaIdx + 2);
- return `${last}, ${first.charAt(0)}.`;
+ const parts = name.slice(commaIdx + 2).split(" ");
+
+ // Level 2: abbreviate trailing given names, keep first given name intact
+ // "Maria Elena" → "Maria E."
+ if (parts.length > 1) {
+ const abbreviated = [parts[0], ...parts.slice(1).map((p) => `${p[0]}.`)].join(" ");
+ const result = `${last}, ${abbreviated}`;
+ if (result.length <= maxLen) return result;
+ }
+
+ // Level 3: abbreviate all given names
+ // "Maria Elena" → "M. E."
+ if (parts.length > 1) {
+ const allInitials = parts.map((p) => `${p[0]}.`).join(" ");
+ const result = `${last}, ${allInitials}`;
+ if (result.length <= maxLen) return result;
+ }
+
+ // Level 4: first initial only
+ // "Maria Elena" → "M." or "John" → "J."
+ return `${last}, ${parts[0][0]}.`;
}
/** Get primary instructor from a course, or first instructor */
|