From af1a0718f3404bb438b81992af2f1d4892d2aa0b Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 16 Oct 2026 16:35:28 +1100 Subject: [PATCH] Let students sort search results by lowest rated Lowest rated uses the same score as Highest rated, reversed: each entity counts as if it had two extra 3-star reviews, so one bad review does not put it on top. Ties go to the entity with more reviews, and entities with no reviews still go last. Also quote file paths in the pre-commit hook, so it works in a checkout whose path has spaces. --- .lintstagedrc.mjs | 8 ++++++-- docs/handbook-pages.md | 1 + packages/webapp/components/handbook/sort-select.tsx | 1 + packages/webapp/lib/db/handbook.ts | 2 ++ packages/webapp/lib/handbook/search-url.test.ts | 1 + packages/webapp/lib/handbook/search-url.ts | 3 ++- 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index c25569f..04fed52 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,6 +1,10 @@ +// Quote each path: lint-staged splits a command string on spaces, and +// a checkout can live under a folder with spaces in its name. +const quoted = (files) => files.map((f) => JSON.stringify(f)).join(" ") + export default { "packages/webapp/**/*.{ts,tsx}": (files) => [ - `pnpm --filter webapp exec prettier --write --ignore-unknown ${files.join(" ")}`, - `pnpm --filter webapp exec eslint --fix ${files.join(" ")}`, + `pnpm --filter webapp exec prettier --write --ignore-unknown ${quoted(files)}`, + `pnpm --filter webapp exec eslint --fix ${quoted(files)}`, ], } diff --git a/docs/handbook-pages.md b/docs/handbook-pages.md index c29c771..ada3581 100644 --- a/docs/handbook-pages.md +++ b/docs/handbook-pages.md @@ -71,6 +71,7 @@ The `sort` parameter picks the order of /search results: | Default | With a query, relevance first (the tiers above). Without one, a score: `3·ln(1+reviews) + 2·ln(1+core) + ln(1+views) + ln(1+planners)`. | | A to Z | Title. | | Highest rated | Mean overall stars, counted as if every entity had 2 extra 3-star reviews. Entities with no reviews go last. | +| Lowest rated | The same score, lowest first. Entities with no reviews still go last. | | Easiest first | Mean review difficulty, with the same 2 extra 3s. Entities with no difficulty ratings go last. | | Most reviews | Published review count. | | Most planned | Distinct students with it on a plan. | diff --git a/packages/webapp/components/handbook/sort-select.tsx b/packages/webapp/components/handbook/sort-select.tsx index 18eaaab..e2fb30d 100644 --- a/packages/webapp/components/handbook/sort-select.tsx +++ b/packages/webapp/components/handbook/sort-select.tsx @@ -16,6 +16,7 @@ export const SORT_LABEL: Record = { default: "Default", alpha: "A to Z", rating: "Highest rated", + lowest: "Lowest rated", easiest: "Easiest first", reviews: "Most reviews", planned: "Most planned", diff --git a/packages/webapp/lib/db/handbook.ts b/packages/webapp/lib/db/handbook.ts index c2ca9e4..64bf3be 100644 --- a/packages/webapp/lib/db/handbook.ts +++ b/packages/webapp/lib/db/handbook.ts @@ -858,6 +858,8 @@ function searchOrder(sort: SearchSort, hasQuery: boolean): SQL { return sql`title, kind_order, code` case "rating": return sql`rating_score DESC NULLS LAST, reviews DESC, ${tail}` + case "lowest": + return sql`rating_score ASC NULLS LAST, reviews DESC, ${tail}` case "easiest": return sql`ease_score ASC NULLS LAST, difficulty_n DESC, ${tail}` case "reviews": diff --git a/packages/webapp/lib/handbook/search-url.test.ts b/packages/webapp/lib/handbook/search-url.test.ts index 796ef3c..2f72e17 100644 --- a/packages/webapp/lib/handbook/search-url.test.ts +++ b/packages/webapp/lib/handbook/search-url.test.ts @@ -21,6 +21,7 @@ test("unknown tab, study and period values are ignored", () => { assert.equal(parse({ type: "units", level: "12" }).level, null) assert.equal(parse({ sort: "views" }).sort, "default") assert.equal(parse({ sort: "planned" }).sort, "planned") + assert.equal(parse({ sort: "lowest" }).sort, "lowest") }) test("page numbers below 2 or not whole give page 1, and cap at 500", () => { diff --git a/packages/webapp/lib/handbook/search-url.ts b/packages/webapp/lib/handbook/search-url.ts index 3192f02..e568677 100644 --- a/packages/webapp/lib/handbook/search-url.ts +++ b/packages/webapp/lib/handbook/search-url.ts @@ -14,13 +14,14 @@ export type StudyLevel = "Undergraduate" | "Honours" | "Postgraduate" | "Research" export type SearchSort = - "default" | "alpha" | "rating" | "easiest" | "reviews" | "planned" + "default" | "alpha" | "rating" | "lowest" | "easiest" | "reviews" | "planned" export const SEARCH_TABS: SearchTab[] = ["all", "courses", "aos", "units"] export const SEARCH_SORTS: SearchSort[] = [ "default", "alpha", "rating", + "lowest", "easiest", "reviews", "planned",