fix(ci): fix rust/frontend/security job failures and expand local checks

This commit is contained in:
2026-01-30 21:22:01 -06:00
parent 3494341e3f
commit dd148e08a0
39 changed files with 331 additions and 481 deletions
+4 -4
View File
@@ -1,4 +1,7 @@
#!/usr/bin/env bun
import { extname, join } from "path";
import { constants, brotliCompressSync, gzipSync } from "zlib";
import { $ } from "bun";
/**
* Pre-compress static assets with maximum compression levels.
* Run after `bun run build`.
@@ -7,10 +10,7 @@
* These are embedded alongside originals by rust-embed and served via
* content negotiation in src/web/assets.rs.
*/
import { readdir, stat, readFile, writeFile } from "fs/promises";
import { join, extname } from "path";
import { gzipSync, brotliCompressSync, constants } from "zlib";
import { $ } from "bun";
import { readFile, readdir, stat, writeFile } from "fs/promises";
// Must match COMPRESSION_MIN_SIZE in src/web/encoding.rs
const MIN_SIZE = 512;
+11 -11
View File
@@ -1,31 +1,31 @@
<script lang="ts">
import type { CourseResponse } from "$lib/api";
import { useClipboard } from "$lib/composables/useClipboard.svelte";
import {
formatTime,
RMP_CONFIDENCE_THRESHOLD,
formatCreditHours,
formatDate,
formatMeetingDaysLong,
formatTime,
isMeetingTimeTBA,
isTimeTBA,
ratingStyle,
rmpUrl,
RMP_CONFIDENCE_THRESHOLD,
} from "$lib/course";
import { themeStore } from "$lib/stores/theme.svelte";
import { useClipboard } from "$lib/composables/useClipboard.svelte";
import { cn, tooltipContentClass, formatNumber } from "$lib/utils";
import { Tooltip } from "bits-ui";
import SimpleTooltip from "./SimpleTooltip.svelte";
import { cn, formatNumber, tooltipContentClass } from "$lib/utils";
import {
Info,
Copy,
Calendar,
Check,
Copy,
Download,
ExternalLink,
Info,
Star,
Triangle,
ExternalLink,
Calendar,
Download,
} from "@lucide/svelte";
import { Tooltip } from "bits-ui";
import SimpleTooltip from "./SimpleTooltip.svelte";
let { course }: { course: CourseResponse } = $props();
+22 -22
View File
@@ -1,6 +1,10 @@
<script lang="ts">
import type { CourseResponse } from "$lib/api";
import { FlexRender, createSvelteTable } from "$lib/components/ui/data-table/index.js";
import { useClipboard } from "$lib/composables/useClipboard.svelte";
import { useOverlayScrollbars } from "$lib/composables/useOverlayScrollbars.svelte";
import {
RMP_CONFIDENCE_THRESHOLD,
abbreviateInstructor,
concernAccentColor,
formatLocationDisplay,
@@ -13,40 +17,36 @@ import {
isMeetingTimeTBA,
isTimeTBA,
openSeats,
seatsColor,
seatsDotColor,
ratingStyle,
rmpUrl,
RMP_CONFIDENCE_THRESHOLD,
seatsColor,
seatsDotColor,
} from "$lib/course";
import { themeStore } from "$lib/stores/theme.svelte";
import { useClipboard } from "$lib/composables/useClipboard.svelte";
import { useOverlayScrollbars } from "$lib/composables/useOverlayScrollbars.svelte";
import CourseDetail from "./CourseDetail.svelte";
import { fade, fly, slide } from "svelte/transition";
import { flip } from "svelte/animate";
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
import { cn, formatNumber, tooltipContentClass } from "$lib/utils";
import {
getCoreRowModel,
getSortedRowModel,
type ColumnDef,
type SortingState,
type VisibilityState,
type Updater,
} from "@tanstack/table-core";
import {
ArrowUp,
ArrowDown,
ArrowUp,
ArrowUpDown,
Columns3,
Check,
Columns3,
ExternalLink,
RotateCcw,
Star,
Triangle,
ExternalLink,
} from "@lucide/svelte";
import { DropdownMenu, ContextMenu, Tooltip } from "bits-ui";
import { cn, tooltipContentClass, formatNumber } from "$lib/utils";
import {
type ColumnDef,
type SortingState,
type Updater,
type VisibilityState,
getCoreRowModel,
getSortedRowModel,
} from "@tanstack/table-core";
import { ContextMenu, DropdownMenu, Tooltip } from "bits-ui";
import { flip } from "svelte/animate";
import { fade, fly, slide } from "svelte/transition";
import CourseDetail from "./CourseDetail.svelte";
import SimpleTooltip from "./SimpleTooltip.svelte";
let {
@@ -1,6 +1,6 @@
<script lang="ts">
import { page } from "$app/state";
import { TriangleAlert, RotateCcw } from "@lucide/svelte";
import { RotateCcw, TriangleAlert } from "@lucide/svelte";
interface Props {
/** Heading shown in the error card */
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from "$app/state";
import { Search, User, Clock } from "@lucide/svelte";
import { authStore } from "$lib/auth.svelte";
import { Clock, Search, User } from "@lucide/svelte";
import ThemeToggle from "./ThemeToggle.svelte";
const staticTabs = [
+3 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { Select } from "bits-ui";
import { ChevronUp, ChevronDown } from "@lucide/svelte";
import type { Action } from "svelte/action";
import { formatNumber } from "$lib/utils";
import { ChevronDown, ChevronUp } from "@lucide/svelte";
import { Select } from "bits-ui";
import type { Action } from "svelte/action";
const slideIn: Action<HTMLElement, number> = (node, direction) => {
if (direction !== 0) {
+2 -2
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import type { Term, Subject } from "$lib/api";
import type { Subject, Term } from "$lib/api";
import SimpleTooltip from "./SimpleTooltip.svelte";
import TermCombobox from "./TermCombobox.svelte";
import SubjectCombobox from "./SubjectCombobox.svelte";
import TermCombobox from "./TermCombobox.svelte";
let {
terms,
+1 -1
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { onMount } from "svelte";
import SimpleTooltip from "$lib/components/SimpleTooltip.svelte";
import { relativeTime } from "$lib/time";
import { formatNumber } from "$lib/utils";
import { onMount } from "svelte";
export interface SearchMeta {
totalCount: number;
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { Tooltip } from "bits-ui";
import type { Snippet } from "svelte";
import { cn } from "$lib/utils";
let {
text,
@@ -1,9 +1,9 @@
<script lang="ts">
import { Combobox } from "bits-ui";
import { Check, ChevronsUpDown } from "@lucide/svelte";
import { fly } from "svelte/transition";
import type { Subject } from "$lib/api";
import { formatNumber } from "$lib/utils";
import { Check, ChevronsUpDown } from "@lucide/svelte";
import { Combobox } from "bits-ui";
import { fly } from "svelte/transition";
let {
subjects,
+3 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { Combobox } from "bits-ui";
import { Check, ChevronsUpDown } from "@lucide/svelte";
import { fly } from "svelte/transition";
import type { Term } from "$lib/api";
import { Check, ChevronsUpDown } from "@lucide/svelte";
import { Combobox } from "bits-ui";
import { fly } from "svelte/transition";
let {
terms,
+2 -2
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { tick } from "svelte";
import { Moon, Sun } from "@lucide/svelte";
import { themeStore } from "$lib/stores/theme.svelte";
import { Moon, Sun } from "@lucide/svelte";
import { tick } from "svelte";
import SimpleTooltip from "./SimpleTooltip.svelte";
/**
+42 -42
View File
@@ -1,60 +1,60 @@
<script lang="ts">
import { scaleLinear, scaleTime } from "d3-scale";
import { onMount } from "svelte";
import { scaleTime, scaleLinear } from "d3-scale";
import type { TimeSlot, ChartContext } from "$lib/timeline/types";
import {
PADDING,
DEFAULT_AXIS_RATIO,
CHART_HEIGHT_RATIO,
MIN_SPAN_MS,
MAX_SPAN_MS,
DEFAULT_SPAN_MS,
ZOOM_FACTOR,
ZOOM_KEY_FACTOR,
ZOOM_EASE,
ZOOM_SETTLE_THRESHOLD,
PAN_FRICTION,
PAN_STOP_THRESHOLD,
PAN_STOP_THRESHOLD_Y,
VELOCITY_SAMPLE_WINDOW,
VELOCITY_MIN_DT,
PAN_STEP_RATIO,
PAN_STEP_CTRL_RATIO,
PAN_EASE,
PAN_SETTLE_THRESHOLD_PX,
YRATIO_STEP,
YRATIO_MIN,
YRATIO_MAX,
YRATIO_SETTLE_THRESHOLD,
FOLLOW_EASE,
MIN_MAXY,
MAX_DT,
DEFAULT_DT,
TAP_MAX_DURATION_MS,
TAP_MAX_DISTANCE_PX,
} from "$lib/timeline/constants";
import { createTimelineStore } from "$lib/timeline/store.svelte";
import {
createAnimMap,
syncAnimTargets,
stepAnimations,
pruneAnimMap,
stepAnimations,
syncAnimTargets,
} from "$lib/timeline/animation";
import {
getVisibleSlots,
findSlotByTime,
snapToSlot,
enabledTotalClasses,
} from "$lib/timeline/viewport";
CHART_HEIGHT_RATIO,
DEFAULT_AXIS_RATIO,
DEFAULT_DT,
DEFAULT_SPAN_MS,
FOLLOW_EASE,
MAX_DT,
MAX_SPAN_MS,
MIN_MAXY,
MIN_SPAN_MS,
PADDING,
PAN_EASE,
PAN_FRICTION,
PAN_SETTLE_THRESHOLD_PX,
PAN_STEP_CTRL_RATIO,
PAN_STEP_RATIO,
PAN_STOP_THRESHOLD,
PAN_STOP_THRESHOLD_Y,
TAP_MAX_DISTANCE_PX,
TAP_MAX_DURATION_MS,
VELOCITY_MIN_DT,
VELOCITY_SAMPLE_WINDOW,
YRATIO_MAX,
YRATIO_MIN,
YRATIO_SETTLE_THRESHOLD,
YRATIO_STEP,
ZOOM_EASE,
ZOOM_FACTOR,
ZOOM_KEY_FACTOR,
ZOOM_SETTLE_THRESHOLD,
} from "$lib/timeline/constants";
import {
drawGrid,
drawHoverColumn,
drawStackedArea,
drawNowLine,
drawStackedArea,
drawTimeAxis,
stackVisibleSlots,
} from "$lib/timeline/renderer";
import { createTimelineStore } from "$lib/timeline/store.svelte";
import type { ChartContext, TimeSlot } from "$lib/timeline/types";
import {
enabledTotalClasses,
findSlotByTime,
getVisibleSlots,
snapToSlot,
} from "$lib/timeline/viewport";
import TimelineDrawer from "./TimelineDrawer.svelte";
import TimelineTooltip from "./TimelineTooltip.svelte";
+2 -2
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { Filter, X } from "@lucide/svelte";
import { getSubjectColor } from "$lib/timeline/data";
import { DRAWER_WIDTH } from "$lib/timeline/constants";
import { getSubjectColor } from "$lib/timeline/data";
import { Filter, X } from "@lucide/svelte";
interface Props {
open: boolean;
@@ -1,8 +1,8 @@
<script lang="ts">
import { timeFormat } from "d3-time-format";
import { getSubjectColor } from "$lib/timeline/data";
import type { TimeSlot } from "$lib/timeline/types";
import { enabledTotalClasses } from "$lib/timeline/viewport";
import { timeFormat } from "d3-time-format";
interface Props {
visible: boolean;
@@ -1,6 +1,6 @@
import { onMount } from "svelte";
import { OverlayScrollbars, type PartialOptions } from "overlayscrollbars";
import { themeStore } from "$lib/stores/theme.svelte";
import { OverlayScrollbars, type PartialOptions } from "overlayscrollbars";
import { onMount } from "svelte";
/**
* Set up OverlayScrollbars on an element with automatic theme reactivity.
+9 -9
View File
@@ -1,22 +1,22 @@
import { describe, it, expect } from "vitest";
import type { CourseResponse, DbMeetingTime, InstructorResponse } from "$lib/api";
import {
formatTime,
formatTimeRange,
abbreviateInstructor,
formatCreditHours,
formatDate,
formatDateShort,
formatMeetingDays,
formatMeetingDaysLong,
formatMeetingDaysVerbose,
formatMeetingTime,
formatMeetingTimeTooltip,
formatMeetingTimesTooltip,
abbreviateInstructor,
formatCreditHours,
formatTime,
formatTimeRange,
getPrimaryInstructor,
isMeetingTimeTBA,
isTimeTBA,
formatDate,
formatDateShort,
formatMeetingDaysLong,
} from "$lib/course";
import type { DbMeetingTime, CourseResponse, InstructorResponse } from "$lib/api";
import { describe, expect, it } from "vitest";
function makeMeetingTime(overrides: Partial<DbMeetingTime> = {}): DbMeetingTime {
return {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { DbMeetingTime, CourseResponse, InstructorResponse } from "$lib/api";
import type { CourseResponse, DbMeetingTime, InstructorResponse } from "$lib/api";
/** Convert "0900" to "9:00 AM" */
export function formatTime(time: string | null): string {
+2 -2
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { termToFriendly, termToBanner } from "./term-format";
import { describe, expect, it } from "vitest";
import { termToBanner, termToFriendly } from "./term-format";
describe("termToFriendly", () => {
it("converts spring term correctly", () => {
+1 -1
View File
@@ -5,7 +5,7 @@
* targets. This module owns the AnimMap lifecycle: syncing targets,
* stepping current values, and pruning offscreen entries.
*/
import { VALUE_EASE, MAXY_EASE, SETTLE_THRESHOLD, MIN_MAXY } from "./constants";
import { MAXY_EASE, MIN_MAXY, SETTLE_THRESHOLD, VALUE_EASE } from "./constants";
import type { AnimEntry, TimeSlot } from "./types";
export type AnimMap = Map<number, Map<string, AnimEntry>>;
+14 -14
View File
@@ -4,28 +4,28 @@
* Every function takes a {@link ChartContext} plus any data it needs.
* No Svelte reactivity, no side-effects beyond drawing on the context.
*/
import { stack, area, curveMonotoneX, type Series } from "d3-shape";
import { type Series, area, curveMonotoneX, stack } from "d3-shape";
import { timeFormat } from "d3-time-format";
import { getSubjectColor } from "./data";
import type { AnimMap } from "./animation";
import { getStackSubjects } from "./viewport";
import type { ChartContext, TimeSlot } from "./types";
import {
GRID_ALPHA,
HOUR_GRID_ALPHA,
NOW_LINE_WIDTH,
NOW_LINE_COLOR,
NOW_TRIANGLE_HEIGHT,
NOW_TRIANGLE_HALF_WIDTH,
NOW_LABEL_FONT,
HOVER_HIGHLIGHT_ALPHA,
AREA_FILL_ALPHA,
AREA_STROKE_ALPHA,
SLOT_INTERVAL_MS,
SETTLE_THRESHOLD,
AXIS_FONT,
GRID_ALPHA,
HOUR_GRID_ALPHA,
HOVER_HIGHLIGHT_ALPHA,
NOW_LABEL_FONT,
NOW_LINE_COLOR,
NOW_LINE_WIDTH,
NOW_TRIANGLE_HALF_WIDTH,
NOW_TRIANGLE_HEIGHT,
SETTLE_THRESHOLD,
SLOT_INTERVAL_MS,
} from "./constants";
import { getSubjectColor } from "./data";
import type { ChartContext, TimeSlot } from "./types";
import { getStackSubjects } from "./viewport";
// ── Formatters (allocated once) ─────────────────────────────────────
const fmtHour = timeFormat("%-I %p");
+1 -1
View File
@@ -5,7 +5,7 @@
* the missing segments when the view expands into unloaded territory.
* Fetches are throttled so rapid panning/zooming doesn't flood the API.
*/
import { client, type TimelineRange } from "$lib/api";
import { type TimelineRange, client } from "$lib/api";
import { SLOT_INTERVAL_MS } from "./constants";
import type { TimeSlot } from "./types";
+1 -1
View File
@@ -2,7 +2,7 @@
* Pure viewport utility functions: binary search, visible-slot slicing,
* hit-testing, and snapping for the timeline canvas.
*/
import { SLOT_INTERVAL_MS, RENDER_MARGIN_SLOTS } from "./constants";
import { RENDER_MARGIN_SLOTS, SLOT_INTERVAL_MS } from "./constants";
import type { TimeSlot } from "./types";
/**
+1 -1
View File
@@ -1,4 +1,4 @@
import { clsx, type ClassValue } from "clsx";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
+1 -1
View File
@@ -2,8 +2,8 @@
import { goto } from "$app/navigation";
import { page } from "$app/state";
import { authStore } from "$lib/auth.svelte";
import PageTransition from "$lib/components/PageTransition.svelte";
import ErrorBoundaryFallback from "$lib/components/ErrorBoundaryFallback.svelte";
import PageTransition from "$lib/components/PageTransition.svelte";
import {
Activity,
ClipboardList,
+2 -2
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from "svelte";
import { client, type AdminStatus } from "$lib/api";
import { type AdminStatus, client } from "$lib/api";
import { formatNumber } from "$lib/utils";
import { onMount } from "svelte";
let status = $state<AdminStatus | null>(null);
let error = $state<string | null>(null);
@@ -1,13 +1,12 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { slide, fade } from "svelte/transition";
import {
client,
type CandidateResponse,
type InstructorDetailResponse,
type InstructorListItem,
type InstructorStats,
type InstructorDetailResponse,
type CandidateResponse,
client,
} from "$lib/api";
import SimpleTooltip from "$lib/components/SimpleTooltip.svelte";
import { formatInstructorName, isRatingValid, ratingStyle } from "$lib/course";
import { themeStore } from "$lib/stores/theme.svelte";
import {
@@ -19,7 +18,8 @@ import {
Search,
X,
} from "@lucide/svelte";
import SimpleTooltip from "$lib/components/SimpleTooltip.svelte";
import { onDestroy, onMount } from "svelte";
import { fade, slide } from "svelte/transition";
import CandidateCard from "./CandidateCard.svelte";
// --- State ---
+1 -1
View File
@@ -3,6 +3,7 @@ import { type ScrapeJob, client } from "$lib/api";
import { FlexRender, createSvelteTable } from "$lib/components/ui/data-table/index.js";
import { formatAbsoluteDate } from "$lib/date";
import { formatDuration } from "$lib/time";
import { type ConnectionState, ScrapeJobsStore } from "$lib/ws";
import { ArrowDown, ArrowUp, ArrowUpDown, TriangleAlert } from "@lucide/svelte";
import {
type ColumnDef,
@@ -12,7 +13,6 @@ import {
getSortedRowModel,
} from "@tanstack/table-core";
import { onMount } from "svelte";
import { type ConnectionState, ScrapeJobsStore } from "$lib/ws";
let jobs = $state<ScrapeJob[]>([]);
let connectionState = $state<ConnectionState>("disconnected");
@@ -1,8 +1,8 @@
<script lang="ts">
import { onMount } from "svelte";
import { client } from "$lib/api";
import type { User } from "$lib/bindings";
import { Shield, ShieldOff } from "@lucide/svelte";
import { onMount } from "svelte";
let users = $state<User[]>([]);
let error = $state<string | null>(null);
+3 -3
View File
@@ -2,14 +2,14 @@
import "overlayscrollbars/overlayscrollbars.css";
import "./layout.css";
import { page } from "$app/state";
import PageTransition from "$lib/components/PageTransition.svelte";
import { authStore } from "$lib/auth.svelte";
import ErrorBoundaryFallback from "$lib/components/ErrorBoundaryFallback.svelte";
import NavBar from "$lib/components/NavBar.svelte";
import PageTransition from "$lib/components/PageTransition.svelte";
import { useOverlayScrollbars } from "$lib/composables/useOverlayScrollbars.svelte";
import { initNavigation } from "$lib/stores/navigation.svelte";
import { themeStore } from "$lib/stores/theme.svelte";
import { authStore } from "$lib/auth.svelte";
import { Tooltip } from "bits-ui";
import ErrorBoundaryFallback from "$lib/components/ErrorBoundaryFallback.svelte";
import { onMount } from "svelte";
let { children } = $props();
+6 -6
View File
@@ -1,20 +1,20 @@
<script lang="ts">
import { untrack } from "svelte";
import { goto } from "$app/navigation";
import {
type Subject,
type SearchResponse,
type SortColumn,
type SortDirection,
type Subject,
client,
} from "$lib/api";
import type { SortingState } from "@tanstack/table-core";
import CourseTable from "$lib/components/CourseTable.svelte";
import Footer from "$lib/components/Footer.svelte";
import Pagination from "$lib/components/Pagination.svelte";
import SearchFilters from "$lib/components/SearchFilters.svelte";
import SearchStatus, { type SearchMeta } from "$lib/components/SearchStatus.svelte";
import CourseTable from "$lib/components/CourseTable.svelte";
import Pagination from "$lib/components/Pagination.svelte";
import Footer from "$lib/components/Footer.svelte";
import { termToBanner, termToFriendly } from "$lib/term-format";
import type { SortingState } from "@tanstack/table-core";
import { untrack } from "svelte";
let { data } = $props();
+1 -1
View File
@@ -1,5 +1,5 @@
import type { PageLoad } from "./$types";
import { BannerApiClient } from "$lib/api";
import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ url, fetch }) => {
const client = new BannerApiClient(undefined, fetch);
+6 -6
View File
@@ -1,5 +1,9 @@
<script lang="ts">
import { onMount } from "svelte";
import { type ServiceInfo, type ServiceStatus, type StatusResponse, client } from "$lib/api";
import Footer from "$lib/components/Footer.svelte";
import SimpleTooltip from "$lib/components/SimpleTooltip.svelte";
import { relativeTime } from "$lib/time";
import { formatNumber } from "$lib/utils";
import {
Activity,
Bot,
@@ -12,11 +16,7 @@ import {
WifiOff,
XCircle,
} from "@lucide/svelte";
import SimpleTooltip from "$lib/components/SimpleTooltip.svelte";
import Footer from "$lib/components/Footer.svelte";
import { type ServiceStatus, type ServiceInfo, type StatusResponse, client } from "$lib/api";
import { relativeTime } from "$lib/time";
import { formatNumber } from "$lib/utils";
import { onMount } from "svelte";
const REFRESH_INTERVAL = import.meta.env.DEV ? 3000 : 30000;
const REQUEST_TIMEOUT = 10000;
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import TimelineCanvas from "$lib/components/TimelineCanvas.svelte";
import { onMount } from "svelte";
// Prevent body scroll while this page is mounted via a CSS class
// (avoids conflicting with other components that may manage overflow).
+2 -2
View File
@@ -1,8 +1,8 @@
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { sveltekit } from "@sveltejs/kit/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import { resolve } from "node:path";
import { readFileSync, existsSync } from "node:fs";
function getVersion() {
const filename = "Cargo.toml";