feat: bring Courseweave's features into CMU Courses #10

Open
jgtsao wants to merge 242 commits from courseweave into main
Member

Brings Courseweave's features into CMU Courses, so CMU Courses does everything Courseweave does, plus friends and the registrar's final exam schedule. Decisions 14-43 in sites/docs/src/decisions/ record the architecture choices; sites/docs/src/courseweave.md maps each feature and the contracts between the pieces, and sites/docs/src/features.md lists where each feature lives.

Catalog (Rust)

  • courses-index build turns the scraper's output (Stellic courses and degree programs, Schedule of Classes captures, data/fces.csv, the syllabus registry, the registrar's final exam PDFs) into one catalog.sqlite in about 4 seconds, byte-identical across rebuilds (decisions 15, 18-22). It replaces scripts/import_catalog.py.
  • courses-catalog defines the file: JSON documents per audience plus relational tables for server-side queries. Public documents leave out instructors, rooms, FCEs, syllabi and Stellic degree programs (decision 20).
  • courses-scraper --mode soc captures CMU's public Schedule of Classes.

Web API (Rust)

  • Serves catalog documents by audience with ETags (decision 16), and a public course API over the relational tables.
  • Keeps personal data on the server (decision 17): schedules, degree plans, the academic record, preferences and saved courses, with revisions, limits and a v3 migration.
  • Sessions persist in the user database (23); syllabi come from private object storage (24); course pages get head tags and there's a sitemap (25); --features dev-auth signs in locally without Keycloak (26).
  • Friends: sharing settings, a friend's schedules, friends taking a course, invite previews (38). OpenAPI document in sites/web/src/lib/api/openapi.json.

Website (Svelte)

  • Design system from the Figma; screens borrow Courseweave's patterns where they fit the features better (decision 40).
  • Courses page: ranked search, filters (requirements instead of a gen eds page; saved as a filter), cards or compact rows on every list, a detail panel beside the list, all semesters view, course pages, requisite explorer, course previews on every course link.
  • Schedules: calendar, section pickers, generator, exports, share links, friend overlay, and final exams in a dialog on each schedule (34).
  • Degree plan with requirement audit and drag and drop; academic record with SIO and Stellic PDF import parsed in the browser.
  • Instructors, friends, settings and account deletion.
  • The browser no longer downloads a SQLite catalog; it loads JSON documents (16).

Before deploying

  • Build and publish a new catalog release (catalog.sqlite) and commit deployments/catalog.json. Startup refuses the old six-file release; see sites/docs/src/deployment.md.
  • Move syllabi to a private bucket and set the SYLLABI_* secrets; the current cdn-courses bucket is publicly readable (decision 24).

Testing

  • cargo test --workspace: catalog format, builder (including parity tests against Courseweave's output, run with --ignored and the inputs), web API (44 tests).
  • deno task check, deno task test (160 tests), deno task build in sites/web.
  • Manual QA of every page at desktop and phone widths, light and dark, signed in and out, against the dev API with the real catalog.

🤖 Generated with Claude Code

https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

Brings Courseweave's features into CMU Courses, so CMU Courses does everything Courseweave does, plus friends and the registrar's final exam schedule. Decisions 14-43 in `sites/docs/src/decisions/` record the architecture choices; `sites/docs/src/courseweave.md` maps each feature and the contracts between the pieces, and `sites/docs/src/features.md` lists where each feature lives. ## Catalog (Rust) - `courses-index build` turns the scraper's output (Stellic courses and degree programs, Schedule of Classes captures, `data/fces.csv`, the syllabus registry, the registrar's final exam PDFs) into one `catalog.sqlite` in about 4 seconds, byte-identical across rebuilds (decisions 15, 18-22). It replaces `scripts/import_catalog.py`. - `courses-catalog` defines the file: JSON documents per audience plus relational tables for server-side queries. Public documents leave out instructors, rooms, FCEs, syllabi and Stellic degree programs (decision 20). - `courses-scraper --mode soc` captures CMU's public Schedule of Classes. ## Web API (Rust) - Serves catalog documents by audience with ETags (decision 16), and a public course API over the relational tables. - Keeps personal data on the server (decision 17): schedules, degree plans, the academic record, preferences and saved courses, with revisions, limits and a v3 migration. - Sessions persist in the user database (23); syllabi come from private object storage (24); course pages get head tags and there's a sitemap (25); `--features dev-auth` signs in locally without Keycloak (26). - Friends: sharing settings, a friend's schedules, friends taking a course, invite previews (38). OpenAPI document in `sites/web/src/lib/api/openapi.json`. ## Website (Svelte) - Design system from the Figma; screens borrow Courseweave's patterns where they fit the features better (decision 40). - Courses page: ranked search, filters (requirements instead of a gen eds page; saved as a filter), cards or compact rows on every list, a detail panel beside the list, all semesters view, course pages, requisite explorer, course previews on every course link. - Schedules: calendar, section pickers, generator, exports, share links, friend overlay, and final exams in a dialog on each schedule (34). - Degree plan with requirement audit and drag and drop; academic record with SIO and Stellic PDF import parsed in the browser. - Instructors, friends, settings and account deletion. - The browser no longer downloads a SQLite catalog; it loads JSON documents (16). ## Before deploying - Build and publish a new catalog release (`catalog.sqlite`) and commit `deployments/catalog.json`. Startup refuses the old six-file release; see `sites/docs/src/deployment.md`. - Move syllabi to a private bucket and set the `SYLLABI_*` secrets; the current `cdn-courses` bucket is publicly readable (decision 24). ## Testing - `cargo test --workspace`: catalog format, builder (including parity tests against Courseweave's output, run with `--ignored` and the inputs), web API (44 tests). - `deno task check`, `deno task test` (160 tests), `deno task build` in `sites/web`. - Manual QA of every page at desktop and phone widths, light and dark, signed in and out, against the dev API with the real catalog. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Decisions 14-17 bring Courseweave's features into CMU Courses: build the
catalog in Rust as one catalog.sqlite, serve it to the browser as JSON
documents per audience, and keep personal data on the server.
courseweave.md maps each feature and fixes the contracts between the
builder, the web API and the website. courses-catalog is the shared
file format: documents, relational tables and full-text search.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Port Courseweave's build-fce.ts and fce-names.ts to Rust: fce/summary,
fce/courses/<NN>, fce/courses/index and fce/instructors, member-only, with
the same JSON as Courseweave's files. JavaScript number formatting, Math.round
and localeCompare (ICU root collation) are reproduced so the documents match.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Removes the sql.js worker, the local catalog, the data client and the old
JSON contracts (decision 16). Courseweave's UI-free modules move into
src/lib/core with their tests, React hooks turned into plain functions and
the stores' edits into pure functions. Tests run on node:test with
@std/expect; the few that need real catalog files read CATALOG_DATA_DIR.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Port Courseweave's build-programs.ts and concentrations.ts to Rust:
programs/index and programs/<auditId>, member-only, identical in content to
Courseweave's files. Stellic's JSON is read with JavaScript's coercions so
the requirement trees, sharing limits, attached bundles and concentration
matches come out the same. The parity test now covers programs too.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Add the catalog-fce-programs page (inputs, entry points, document shapes,
how aggregates, requirement trees, bundles and concentrations are built,
and the parity check against Courseweave) and decisions 21 and 22: sort
like localeCompare with Unicode collation, and leave build times out of
the FCE and program documents.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Serve catalog documents by audience with ETags, keep a public course API
over the relational tables, and store schedules, plans, the academic
record, preferences and saved courses as revisioned, owner-scoped records
with daily write caps. Persist sessions in the user database, add friends'
schedule and course sharing, gated syllabi, server-rendered course head
tags, the sitemap and robots.txt, a dev-auth feature, and an OpenAPI
document generated without running the server.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Lock icu_collator 2.3.1, which uses the icu 2.3 crates already in the lock,
instead of moving every icu crate to 2.4.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Replace the monolithic page with the Figma redesign's foundation:

- Tokens in layout.css (@theme): the Figma colors plus dark values,
  status text colors, translucent hover fills, the Figma type scale,
  radii and shadows. Dark mode is a class on <html>, applied before
  first paint by an inline script in app.html.
- UI primitives in lib/components/ui: buttons, fields, searchable
  selects, checkbox, switch, range slider with unit presets, chips,
  cards, dialog, sheet, popover, menu, tooltip, toasts, tabs,
  segmented control, view toggle, avatars, star rating, stat box,
  data tables, spinner, skeleton and empty state.
- App shell: header with centered navigation, theme switch and session
  pill; a menu sheet at phone widths; the ScottyLabs footer. The shell
  reads a small session context, stubbed until the data layer lands.
- Layout patterns: the filter rail (a sheet on phones), the page
  toolbar and the profile layout (an accordion on phones).
- Route skeleton for every page in courseweave.md, with placeholders.

Remove the old page, its stylesheet, components and icon assets.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Shows every UI primitive, the color tokens and the type scale in the
light and dark themes side by side, for reviewing the design system.
The route returns 404 outside development builds.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
courses-index build ports Courseweave's build-data, build-catalog and
build-syllabi scripts and writes catalog.sqlite through courses-catalog:
build, terms, term/<id> (public without instructors and rooms),
offerings, catalog/index, catalog/details/<NN>, teaching, syllabi/<NN>
and the relational tables. Built from the same inputs, every document
matches Courseweave's files byte for byte apart from generatedAt, which
now comes from the inputs.

New: finals/<id> from the registrar's final exam PDFs in data/finals
(S26 moved from data/exams.pdf, F26 and its mini-1 schedule added).

data/terms.json ports Courseweave's term list with paths relative to the
exported root. courses-catalog gains Prepared documents so the builder
compresses in parallel.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
- Hiding or showing a rail moves focus to the button that replaces the
  one pressed, instead of dropping it on the page.
- PageToolbar gains an intro slot between the heading and the search,
  so the finals instructions sit above the search and Filter row.
- An empty DataTable shows its message under the header instead of in
  a cell that scrolls sideways with wide tables.
- Long search placeholders end with an ellipsis; the header's controls
  sit closer together below 1280px.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Add design.md (tokens, type scale, components, layout patterns, the
cards/list rule and the shell's session context) and decisions 30 and
31: UI primitives built on native elements and the top layer, and
window scrolling with a sticky header and rail.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Replace the SQLite backend handoff with a web API reference, rewrite the
personal data page, update the deployment runbook for the single
catalog.sqlite release, persisted sessions and syllabus storage, add ADRs
23 to 26, and declare the optional syllabus storage secrets.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
courses-index build now adds the fce/* and programs/* documents to the
catalog, compressed in parallel with the rest. The semester builder
shares the FCE module's JavaScript helpers and ICU collation, and the
parity test also compares the semester and catalog documents with
Courseweave's files, byte for byte apart from generatedAt.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Ports Courseweave's fetch-soc.ts and build-catalog.ts --fetch: per term,
the undergraduate and graduate search pages, detail pages of courses
whose sections need grouping, reservations and special permission, and
meta.json, under exported/soc/<term>/; with --soc-catalog, the parsed
course detail pages in exported/soc/details.json. Requests are serial
with a delay and need no credentials. The parsers live in
courses_index::soc, shared with the builder.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The Rust catalog builder leaves generatedAt out of its documents so builds
are reproducible; nothing depends on it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
src/lib/api is a same-origin JSON client: `{ code, message }` errors as a
typed ApiError (offline, 401, 409 with the stored record, 429 with
Retry-After), one function per route, and the types generated from the
API's openapi.json (`deno task generate-api`).

dev/mock-api.ts answers /api and /auth from memory when MOCK_API=1, with
catalog documents read from a directory of Courseweave-format files. It
is a serve-only Vite plugin loaded only when enabled, so it never reaches
a build.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
src/lib/data loads `GET /api/catalog/<name>` documents with Courseweave's
loaders on top (term(), catalogIndex(), offerings(), FCEs, programs,
syllabi, finals): one request per document however many ask, cached in
memory until the session changes, and a distinct sign-in-required result
for member documents. The session reads /api/me, signs in through
/auth/login with the page to come back to, and signs out with
POST /auth/logout. resource() turns any loader into reactive state for
components, and app-update.ts reloads a tab that outlived a deploy.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
src/lib/state holds the student's own data in Svelte runes stores, server
backed per decision 17: loaded after sign-in, edited at once, written back
after a pause with the revision it was based on. On a 409 the edits since
that revision are replayed onto the stored record (Courseweave's merge
rules when they aren't known) and written again. Signed out, everything is
a draft in sessionStorage that signing in saves to the account; whatever
the server doesn't have yet is also kept there until it does. The shell's
session now comes from the data layer, and signing out saves first.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Rewrites the catalog data page for courses-index build: inputs, the
terms.json sources, options, documents and their audiences, the refresh
procedure and the checks. Adds decisions 18 to 20 (capturing the SOC
with the scraper, parsing the registrar's final exam schedules, and
redacting public term documents).

Removes scripts/import_catalog.py, verify_catalog.py, courseweave.py
and test_courseweave.py, which the builder supersedes, and publishes
the single catalog.sqlite. The scrape workflow no longer runs the old
courses-index CLI, which never wrote a catalog; it gains the soc mode.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
With --term, finals/<id> documents are limited to the selected terms,
like the term documents. The contract page now spells out the finals
fields' values.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Also fixes two things the tests found: "back to plan" never matched a plan
page with a query string, and an unavailable localStorage could throw
while reading device preferences.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

# Conflicts:
#	README.md
#	sites/docs/src/api/data-audit.md
#	sites/docs/src/deployment.md
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Rewrites the frontend page for the new architecture (data flow, session,
stores, the mock API, tests), points the maintainer guide's website rows
at the new modules, and records decisions 27 to 29.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

# Conflicts:
#	sites/docs/src/maintaining.md
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Removes the DuckDB, frontend contract and database requirement pages that decision 16 and the new web API replaced, rewrites the developer usage page, and updates check_deployment.py for catalog documents, redaction, access checks and the SEO files.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The degree plan uses both; the record feature replaces them.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Several development servers can now share one dev API. Production still accepts writes only from APP_URL.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
WeekGrid shows blocks by weekday and hour with overlapping blocks side by
side, conflicts, minis and a list of things without a set time. Blocks
follow the calendar display preferences, phones get a day-by-day view with a
switch to the week, and a hidden list repeats the calendar for screen
readers. The course palette moves into the design tokens, and the calendar
can be drawn as a PNG.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The instructor list and instructor page from the Figma, with Courseweave's
explorer behind them: search by name, course number or title, department,
saved courses and sort filters, the semester picker with all semesters, cards
or rows, and the rail's aggregate FCE settings. Cards load their FCE rows as
they come into view. The instructor page shows the aggregate, evaluations by
section, the sections taught this semester (or by semester), co-instructors
and the evaluation history with its trend chart. Signed out, both pages ask
the visitor to sign in.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Courses Taken holds the whole record from Courseweave: import from SIO or
Stellic PDFs read in the browser, backups, profile and unit totals, courses
taken with grades (cards or a list, filtered by department, level and
semester), exam and transfer credit, placements and overrides. Currently
Taking lists this semester's courses with the sections and times chosen in
the active schedule. New students are asked to add their record once.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Ports Courseweave's degree planner to /plan: several plans, the program
picker (majors, additional majors, minors, concentrations, requirement
versions), the requirement audit with ways to satisfy, the overall
progress bar, the semester grid in cards and detailed layouts, drag and
drop between semesters and from the requirements (with menus to move and
reorder from the keyboard), requisite problems, unit and FCE totals, and
syncing or copying a semester's schedule. Signed out, the plan is a draft
and degree programs ask to sign in.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The instructor pages now share one layout that keeps the list's state while
instructors open. In the list view on wide screens, Courseweave's rows stay
in their own column beside the open instructor, and the search field moves
through them with the arrow keys. Cards and narrower screens keep the
instructor page on its own with a way back to the list.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Several schedules a semester as tabs (create blank or from a degree plan
semester, rename, duplicate, share, delete), a course search that
previews sections, course rows with lecture and recitation pickers,
colors, pass/fail, hiding and reordering, and the week calendar with
conflicts, minis and outlined alternatives for a selected course. The
rail has saved courses offered this semester, the requisite check
against the record, the FCE workload and a friend's schedule to compare.
The toolbar totals units and hours a week and has the schedule
generator, the display menu and export: a calendar file, an image, the
course list, share links with friends visibility, and the Stellic and
SIO scripts. Shared links open read-only at /schedules/shared/<token>
with a copy to your schedules, and the schedule's final exams show
under the calendar.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A plan opened before the semester list loaded never learned which
semesters have a published schedule, so it said "usually offered" for
courses on the schedule. Signed out, the program picker and requirements
now show a compact sign-in note instead of a full-page prompt.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The Courses page searches a semester (or every semester) with Courseweave's
ranked search and filters, the Figma filter rail and removable filter chips,
and shows results as Figma course cards, compact rows or an FCE hours chart,
with a course detail panel beside the list on wide screens. Courses other
semesters offer are listed after the results.

Shared course components (cards, rows, requisite expressions and status,
FCE aggregate, tables and charts, save button, friends taking a course) load
what they show through one shared cache, so they only need a course and a
semester. The course page and the requisites explorer port Courseweave's.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Decision 36 lays the plan out beside its requirements instead of in a
rail; decision 37 lets signed-out visitors draft plans while degree
programs ask for a CMU sign-in.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Courseweave's RequisiteStatus: one course's requisites as met, planned or
missing, with the prerequisite expression as course chips and buttons to
mark requisites waived, checked against the record, the active plan and the
semester's schedule. Also keeps import review rows from overflowing on
phones and gives the profile filters 40px fields there.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Prerequisite expressions no longer show spaces inside their parentheses,
and a semester's header keeps its name on a phone by shortening the issue
count and unit label.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Finals open from the schedules page (Finals in the toolbar, or
/schedules?term=F26&finals=1): the registrar's exam for each course and
section in the schedule, warnings for overlapping and back-to-back exams
and three or more in 24 hours, a calendar file, a search of every exam
in the semester, a clear state while the schedule isn't published, and
the SIO calendar import for exams the registrar's file lacks. /finals
redirects there and leaves the header navigation.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A heading inside a button isn't valid HTML and hides the heading from
screen reader navigation.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Also shows a spinner while the account's record loads, so the profile
doesn't flash the empty record, and lines course card headings up with
their link buttons.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The generator's time selects and preference toggles now stack in full
width rows, the days fill one row, and each course's section counts sit
on their own line. Toolbar actions wrap within the page instead of
widening it on phones.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The Friends section from the Figma: friend cards with what each friend
shares (their schedule and the courses they're taking, with courses in
common first), requests to accept, decline or cancel, adding friends by
Andrew ID, invite links to make, copy, share or revoke, removing a friend
after a confirmation, and the two sharing switches with what each shows.
A friend's schedule opens in a dialog for any semester.

The invite page says who sent an invite before it's accepted, through a new
GET /api/friends/invites/{token} for members (ADR 38), and handles the
student's own link, existing friends, pending requests and used links.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The profile's Saved Courses section shows saved courses as small cards or
rows, filtered by department and level. Course links inside the Courses
page's detail panel open in the panel, in the semester they point to, and
the requisite explorer counts as part of Courses in the navigation.

Decisions 32 and 33 record the shared course data cache and the detail
panel beside the course list.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Each schedule is a card with its semester, units, expected hours a week
and courses, newest semester first, with a semester filter, opening the
schedule on the schedules page. Signed out, the planner says rooms and
instructors need sign-in and the calendar leaves out unknown rooms.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Decision 34 puts final exams in a dialog on each schedule, and decision
35 keeps share links that carry the schedule for signed-out visitors.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Settings now show the account with its save status and sign-out, the theme,
Courseweave's calendar display options, what friends can see with a link to
change it, and what's stored in the account and on the device. "Delete my
data" lists what is removed, then deletes it and signs out everywhere.
The Friends section scrolls to its sharing switches when linked to them.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
ADR 39: an instructor's FCE rows are built in the browser from the course
FCE documents their FCE index entry names, loaded as cards come into view.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

# Conflicts:
#	sites/docs/src/design.md
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A draft schedule or plan saved on signing in takes a name the account's
schedules for that semester (or its plans) don't use, and an empty default
schedule is dropped when the account already has one for the semester.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Between 1024 and 1440px the filter rail collapses while a course is open,
so the detail panel sits beside the list instead of replacing it. Show
still opens the rail (the course then takes the list's place, as before),
and nothing about it is remembered. RailLayout gains autoCollapse and a
bindable railCollapsed.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Course links anywhere on the site show a card with the course's units,
description, prerequisites, requisite status, meeting times and FCE hours,
as Courseweave's course pills do. On touch screens, pressing and holding a
course link opens the card at the bottom of the screen instead.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The presets never break their labels ("6 / Units") and sit closer
together, so all four fit down to a 260px rail.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
On phones the course sort is an icon over the native list of sorts, so
long names aren't cut off, and the semester select takes the rest of the
row. The schedule page's Generate, Finals, Export and Show become icon
buttons (names kept for screen readers) in the Courses button's row.
PageToolbar gains actionsLayout: "inline" puts the actions in the Filter
row while the rail is a sheet, "row" gives them a row of their own on
phones.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The course page and the Courses page's detail panel show the semester's
active schedule as a week calendar, with the course placed or previewed
as the student points at, focuses or taps a section row, and a line
saying what it shows or what it would conflict with. On the full page
the calendar stays in view beside the sections on wide screens and
follows them on narrower ones; the panel shows it before the sections.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Exam, transfer and placement credit and the plan's incoming credit link to
their course pages; codes in the schedule rail, workload and generator show
the course preview.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The instructor page's semester view adds Courseweave's weekly timetable
on the shared week calendar: every section they teach, a color per
course, each class opening its course page, and sections without a set
time listed under it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The friend's schedule dialog shows their chosen sections on the shared
week calendar, in their colors, above the list of courses, with
sections without a set time listed under it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Chip and StatusChip take attrs for their element, so a removable or plain
course chip can be marked for the course preview; CourseChip marks itself
when it isn't a link.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The course page's back link returned only to the plan or the course list.
It now also offers Back to schedule, instructor, instructors, profile and
requisites, whichever page the course was opened from.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Signed-out visitors, often arriving on a course page from a search, met the
record onboarding dialog on their first page. Their record would only last
the tab; the degree plan's record prompt still offers it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Both rails stored their "Aggregate FCEs" choices under the same localStorage key in different shapes, so changing
one reset the other. They now share one store, one settings group and one window.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Import from SIO or Stellic and a More menu (export a backup, import one,
clear the record) sit beside the section title, and the department, level
and semester filters, the count and the cards/list switch sit under it,
as on the Figma's Courses Taken page. The band and the list share the
filters through taken-list.svelte.ts. On phones the filters pack two to a
row and More is an icon button.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Records decision 41 (preview every course link, by hover or press and hold)
and the week calendars the course, instructor and friend pages gained.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Sections hand their actions and filters to ProfileLayout's band with
profileBand(), so Saved Courses, Currently Taking and Schedules show their
filters under the heading as Courses Taken and the Figma do, and the
profile page no longer wires each section's snippets by hand. Codes in the
Currently Taking notice link to their courses, and the schedule cards drop
the divider before hours on phones, where it wrapped.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

# Conflicts:
#	sites/web/src/lib/features/instructors/TeachingSections.svelte
#	sites/web/src/lib/features/record/CoursePicker.svelte
#	sites/web/src/lib/features/schedules/ScheduleWorkload.svelte
The Courses rail uses the shared Aggregate FCEs settings the Instructors
rail has, the record backup uses the shared download helper, and both
course and instructor lists build their semester picker with
semesterOptions() from the term scope.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Signing in saved the tab's drafted schedules, plans, record and saved
courses into whichever account signed in, without a word; on a shared
computer that could be someone else's. They are now held back while the
account loads, and a dialog asks to add them to the account or discard
them; signing out instead gives the tab its drafts back, and the record
onboarding waits for the answer. Decision 42.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The schedule toolbar's course chips show the course preview; a course
card's title, a course row and a small card's link opt out, since the card
already shows what the preview would.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Screen readers heard "Courses ( 3 active)"; the button is now named
"Courses, 3 active", and it's as tall as the toolbar's icon buttons.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The feature pages now name where the merged components live, the Courses
page's panel beside the list from 1024px, Courses Taken's band, the phone
toolbars, and RailLayout's autoCollapse, PageToolbar's actionsLayout,
profileBand() and CopyField in the design system.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The schedules page renames in place, syllabus links come from
syllabusPath() in the API module, and the root layout has used the real
session since the data layer landed.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The first-run plan page put the prompt in a 624px column, where the buttons kept their width and the text wrapped
every two or three words. The buttons now sit beside the text only when the card itself is wide enough.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Form controls turned off their outline and changed only a 1px border color on focus. They now show the global
accent ring: native fields take it directly, and fields drawn by a wrapper (search boxes, the plan's add-course
field) get it from the new focus-ring-within utility.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
With two schedules on a phone, the second tab was cut off at the edge of
the scrolling tab strip; the strip now scrolls to the shown schedule's tab.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
With no courses on the record, a scheduled course made every preview read
"Missing prerequisite" in red, which says nothing about the student.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The tree's list sized itself to its widest row, so on a phone titles ran
past the card's edge, cut off mid-word; they now truncate to fit, and only
a tree too deep for the screen scrolls sideways.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Once a schedule or plan existed, courses checked against an empty record showed red "Missing prerequisite"
errors. While the record is empty, a missing prerequisite now keeps the "add the courses you've taken" hint in
the course panel, on cards and in the schedule's requisite check, and its chips stay neutral. Planned courses and
anti-requisite conflicts are still reported.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
On a phone the calendar came after every section, so tapping a section to
preview it changed a calendar far down the page; it now comes first, as in
the Courses page's panel.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Public term documents leave rooms out, so every section read "Room TBA" on a signed-out schedule. Section rows
and pickers now leave the room out when signed out, and the picker's list says rooms need a sign-in.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The instructor list built itself from a semester's full document and fce/instructors (or, for all semesters, from
teaching, offerings, catalog/index and fce/instructors): about 8 MB of JSON before the first name showed.
courses-index now writes instructors/<termId> and instructors/all, member-only, with only what the list shows and
searches: names, departments, courses, section counts, topic and course titles, the recent teaching rating and the
courses with FCE rows. Instructors are listed in the order the website gives out slugs, so links stay the same.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The list loads instructors/<termId> or instructors/all instead of the semester, fce/instructors, catalog/index and
offerings (decision 43). Cards load their FCE rows from the courses the list names, the frame after they come into
view, without the FCE index; the full documents load only while an instructor is open. With 4x CPU throttling and a
fast 4G network, a semester's first card shows about 0.5 s after the list is requested instead of 1.1 s, after 2.7 MB
of JSON instead of 8.7 MB; all semesters, 0.7 s instead of 1.5 s. Catalogs built without the documents still work.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Signed out, the course list asked for the member-only fce/summary and got a 401; every load also asked for
catalog/details/00 and /01, which didn't exist. Each showed as a console error. The catalog now skips member
documents the server would refuse once the session is known, and courses-index writes an empty details document
for every course-number prefix without details.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Requirement options read "Science/Engineering, Any Department (4 courses) (4 courses)". The target is added only
when the name doesn't already say it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Departments are a searchable multi-select (dept=CS,MSC) and the sort is a toolbar select, as on the Courses page.
Signed out, the rail and toolbar no longer offer filters that do nothing above the sign-in prompt.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
CourseHead added a second description tag next to app.html's (or the server's). It now sets the one tag the page
has and puts the site's description back on leaving.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The card fell back to an empty department, so cards in a row had different heights. It now takes the department of
other courses with the same number prefix, as the course list does.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A course with a W (or another no-credit grade) counted its units in the plan's semester totals and rows, while
Courses Taken counted it as 0 units. The plan now counts it the same way, unlike Courseweave.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The backup import error, the .ics PRODID and the Stellic and SIO scripts' headers named Courseweave.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
"Share link", "Add friends", "Upload SIO calendar", "Export from SIO", and "Visible to friends" in the share
dialog as the sharing settings call it. The registrar's "No course description provided." now reads like every
other missing description.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Stat boxes and evaluation tables showed "4", "4.5" and "14.10" next to tables with two decimals, and the card
table showed the registrar's "CERVESATO, ILIANO". Hours now always have one decimal and ratings two, instructors
are named first name first, and the card header's hours say they cover the course's recent semesters, unlike the
aggregate below. The card's "Expand All" is "Expand all".

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The friends' avatars on course cards had white initials at about 1.5:1 contrast and named people only in a hover
title. Initials are now ink on the pressed fill, and the avatars are a button that lists the friends.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Segmented controls, including the cards/list switch, were 28px tall below 1024px. Sheets now focus their content
instead of their first control, so opening one with a tap doesn't draw a focus ring on a switch nobody chose, and a
labeled switch's ring no longer touches its label.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A new student signing in to accept an invite saw the onboarding dialog first. It now waits for the next page.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The calendar's first hour label sits on its line like the others, and hatched preview blocks use the strong text
color. Hours chart points no longer jitter past the plot, a line chart's reference label moves clear of the last
value, the Session pills fill their row like the other pill groups, the invite dialog's Copy link matches the share
dialog's, and the plan's Starts select keeps its chevron beside the semester.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A semester that doesn't offer the course was added after the others in the semester select; it now takes its place
newest first. The not-offered notice no longer repeats the page's back link.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Courseweave's account menu showed sync status; here only Settings and page banners did. Signed in, the header shows
Saved, Saving, Offline or Not saved beside Sign out, with Retry after a failure; on phones, an icon while something
isn't saved and the full status in the menu sheet.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Courseweave's VAR units chip had no counterpart in the rail; units=var worked only in the URL. The Units section now
has "Variable units only", and its chip reads "Variable units".

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The filter sheet had no result count or way to see the results but Back. RailLayout takes sheetDone, a button at the
sheet's foot; Courses and Instructors use it to show how many results the filters leave. The design notes describe
it and the header's save status.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A tab only reloaded by itself when a page of an older build failed to load. SvelteKit now checks for a new build
every five minutes, and a toast offers to reload; nothing is lost, as unsaved changes survive a reload.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
app.html gets Open Graph tags with a 1200x630 image in the CMU Courses look, an Apple touch icon and WebSite
SearchAction structured data for /?q=. Course pages keep the image at its full address, and static/robots.txt now
keeps crawlers off the same pages as the web API's.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The sort select squeezed the result count to "1,63 instr" at 393px. On phones it now sits beside the semester.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
With the first hour's label centered on its line, the header's rule ran through "8 AM". The rule now spans the
days only, like the hour lines.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Its inline wrapper left room for a text baseline below the switch, lifting it about 3px above Saved and Sign out.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The requisite, options and satisfies cells used different padding from the rest of the top-aligned row, so their text sat 2 to 6px off. Measured equal at desktop and phone widths.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Without a rating chart (one semester of evaluations), the stat boxes sit side by side instead of stacking in the chart's side column, and a single co-instructor spans the full width instead of half of a two-column grid.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
RailGroup takes a count pill after its title and summary lines under it;
RailLayout takes sheetButtonLabel and sheetIcon for the phone button that
opens the rail, for rails that aren't filters (the schedules page).

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Each tab's units sit in a neutral pill centered on the name. The scrolling
tab strip gets padding (offset by negative margins) so the 2px focus ring of
a tab or the rename field isn't clipped.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The course search now heads the rail's Courses list, with its results in the
top layer as wide as the field and each title on its own line. The course
chips and the workload block leave the main area: the Courses heading shows
the count, units, hours a week, conflicts, the factorable split and the
over-54-units warning. The main area is the tabs, one toolbar row and the
calendar; the calendar display menu is a settings icon button, and on wide
screens the session switch joins the toolbar row. While the rail is a sheet,
its button reads "Courses (6)" or "Add courses" and a line under the toolbar
keeps the units, hours and conflicts in view.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Hovering, focusing or tapping the status icon shows its issues (or "Requisites met") instead of opening a row under the course. Waiving and restoring requisites move to the course's options menu. Tooltips keep line breaks.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Keep the global keyboard focus ring, but fix the places where it was out of
place:

- Fields (TextInput, Select, Combobox, MultiSelect, the boxed SearchInput,
  CopyField, inline inputs) drew the ring on top of their accent border. They
  now use one indicator, `focus-field`: the border eases to accent and
  thickens to 2px inside the box; invalid fields stay red.
- The underline search drew a square box ring plus its accent line; it now
  grows a 2px accent rule from the center instead.
- Scroll boxes cut rings off: focused controls scroll into view with 8px of
  room, the plan's requirements column has room for its row chevrons, Tabs
  rings the tab's label inside a padded strip, and full-width rows (profile
  sections, program picker) and segments draw the ring inside.
- The ring eases in over 150ms (`animate-focus-ring`), also for rings drawn
  by wrappers and pseudo-elements; reduced motion makes it instant.
- The FCE sliders ring the thumb instead of boxing the whole track.
- A dialog that opens on its own focuses its content, so the onboarding
  dialog no longer shows a ring on its first option before any keypress.
- Small fits: rounded text-link buttons, a centered start-term ring, the
  plan row on the shared ring, a no-op focus:ring class removed.

Documented the focus rules in design.md.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: give the schedule rename field one focus indicator
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build failed
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
5f27ce14d5
It drew the focus ring over an always-accent border; it now uses focus-field like the other fields, an accent border drawn inside.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: test with Jest's expect from npm so the Nix build can fetch it
Some checks failed
kennel/build build failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
523a64d167
Kennel's Deno build fetches every locked package from an npm registry, and the JSR packages behind @std/expect (std__expect, std__assert, std__path) returned 404, failing kennel/build. The tests' matchers are Jest's, so they now use the expect package.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
"None listed" shared a line with the 32px-tall tree link and dropped below its label; the link now sits on its own line. Counts-toward rows keep their hover padding but line up with the program name.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Course page, panel, not-offered and requisite explorer titles used proportional digits while lists, cards and chips use tabular ones; titles now use tabular digits too, keeping their tighter tracking.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: put the profile rail's title and Hide where every other rail has them
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build failed
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
72f8640bbc
The profile header copied its own Figma frame (55px top padding, Hide 23px down); it now uses RailLayout's geometry, and its avatar row lines up with the title on wide screens.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
523a64d only carried the type declaration rename; the lockfile, package.json and testing.ts changes were left unstaged, so Kennel still fetched the JSR packages.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: declare the npm expect specifier's types
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build failed
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
3a8151e13e
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The Courses page's FCE hours chart no longer sits in a card: it spans the results area and fills the rest of the window. Labels of the student's courses try the right, left, above and below of their point and avoid other labels and marked points, and draw above every point.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: give the web API's Nix build the pinned catalog manifest
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
f6d6d5d52b
courses-web-api embeds deployments/catalog.json with include_str!, but kennel's buildRustService keeps only Cargo sources and secretspec.toml, so kennel/build failed with "couldn't read deployments/catalog.json". The final build now gets a fileset with the manifest; the dependency build keeps the filtered source.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Profile > Schedules gets a Preview view beside the existing cards
(Details), switched with the ViewToggle in the title band and
remembered per device. Preview groups the schedules under their
semesters, each a card with the week in miniature (the meetings in the
courses' colors, no text) over its name and class count; the card is
one link to the schedule.

WeekThumbnail draws the miniature with one SVG grid and a span per
meeting, laid out from core/week-grid.ts. ViewToggle takes a page's own
views through a new options prop.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
chore: merge the schedule previews
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
e3d316c4b8
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Port Courseweave's WorkloadCell to CourseRow: the FCE hours with "mini"
before a mini's, a WorkloadMeter bar under them (tone fill, tick at the
hours the units suggest), the workload description as its title and for
screen readers, and "No FCE" when there are none. The hours column
widens to 4.5rem; narrow lists keep the inline hours and rating.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The requirement field was a native select beside the program combobox.
It is now a Combobox too: type to search, the path in the program under
each requirement, disabled until a program is chosen, and a clear button
that keeps the program. Combobox ellipsizes a long chosen label and keeps
its list inside the viewport on phones.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The day buttons are now round toggles (40px on touch layouts, 34px in the
desktop rail) spread edge to edge, and the Only these days / Any of these
control fills the rail with equal segments through SegmentedControl's new
full prop, instead of leaving empty space on the right.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
"Full semester" wrapped onto two lines in the rail. The chip now reads
"Full" and keeps "Full semester" as its accessible name and tooltip
through ToggleChips' new fullLabel option, and toggle chips no longer wrap
their text.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
chore: merge the courses page polish
Some checks failed
kennel/build build succeeded
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
7c22bb8aec
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

# Conflicts:
#	sites/docs/src/design.md
fix: give toggle pills a 40px tap target on touch layouts
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
29a88c6ce0
Units presets, seasons, session and delivery chips were 34px tall on phones; they now match the day buttons: 40px below lg, the Figma's 34px from lg.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The course page's header put the save star far from the number, the semester select alone at the top right and the
schedule and requisite lines loosely spread on the right. The star now sits beside the big number, and the semester
select, the schedule action (a full-width "Add to Schedule 1", or an "In Schedule 1" chip with Remove and Open
schedule right after it), the requisite line with Details right after its last word, and friends taking it share one
card: beside the title in the calendar's column from 1024px, between the title and the facts below that.

The Courses page's detail panel uses the same card and star placement. Its top bar drops the small code, showing the
code and star only once the big ones scroll under it; the panel clips to its rounded corners, and the bar sticks under
the site header when the panel scrolls with the page.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
chore: merge the course header redesign
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
kennel/build build succeeded
9ad8997799
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Links to an instructor page anywhere on the site now show a card on hover,
focus or press-and-hold, from the same layer as course previews (renamed
PreviewLayer, in lib/components/preview): name, departments, what they teach
in the semester or across semesters, and the FCE aggregate the instructor
cards show. It reads the compact instructor documents and the per-course FCE
rows, each once per session. The section picker marks its instructor names;
instructor cards and rows opt out. Signed out, nothing previews.

Documented as an amendment to ADR 41.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: link FCE table instructors to their all-semesters page
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check-1 (pull_request) Has been cancelled
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
86af6ca3ec
FCE rows have no semester, so their instructor links fell back to the newest one and past instructors landed on "No instructor at this link".

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: pad the requisite status button on both sides
Some checks failed
kennel/build build succeeded
CI / check-1 (pull_request) Has been cancelled
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
512f992d02
Its hover background started at the status icon; it now has the same room before the icon as after the chevron.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: keep a schedule course's code line on one line
Some checks failed
kennel/deploy service 'api' failed healthcheck within startup grace period
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
a68f4bef1c
"not factorable" wrapped beside the row's buttons in the rail; it is now the degree plan's compact P/F tag, with the same explanation as its tooltip and for screen readers.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A pressed Chip appended bg-line to a tone that sets its own fill, so which
one showed depended on the stylesheet's order: chosen concentrations looked
unchosen although the plan saved them and the audit used them. Pressed now
replaces the tone. Concentrations use ToggleChips (any number, pressed again
to clear, as in Courseweave) with a new opt-in check on pressed pills, and
the program's line names the requirements checked alongside it.

The program-type chips move into a panel under the program search that
shows while it's in use; a type with no query lists every program of that
type. The plan header, semester cards and requirements share the other
pages' radii and fills: field selects, cards for the programs, numbers
(StatBoxes in a well) and each audited program, soft Add pills.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
/plan now uses RailLayout like Courses, Schedules and Profile. The rail
holds the programs (version, concentrations, the program search with its
type filter in the results), the degree progress bar and the plan's
numbers as StatBoxes with the first semester, and the courses planned
before their requisites, each jumping to the course (closing the phone
sheet first). The band holds the title, plan picker, New plan and the
plan's options; on phones also the sheet button and the progress bar.

The requirements sit beside the semesters once the main column is 900px
wide (measured, so hiding the rail makes room), a third of its width, and
are a tab otherwise. Narrow requirement panels put a course's number and
Add on one line and its title under them; the Expand dialog keeps one.

The rail on every rail page keeps 340, 380 and 418px up to 1440px wide,
then grows with the window to 560px at 2560px (RAIL_WIDTH), and rail
controls gain close().

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Decision 44 puts the plan's programs and progress in a rail and grows the
rail on wide screens; decision 36 notes it's superseded in part. The
design system gains the radius rule (one radius per role, fills and
spacing rather than nested outlines), ToggleChips' check, Chip's pressed
look and the rail's width; the plan's feature page describes the rail,
the band and the requirements' widths.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The panel opens under the search near the rail's foot; it scrolls into
view when it opens and when a program type fills it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
chore: merge the degree plan rail redesign
Some checks failed
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
16145f4b39
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH

# Conflicts:
#	sites/docs/src/design.md
#	sites/web/src/lib/components/ui/ToggleChips.svelte
The units, the factorable and pass/fail split, "Exceeds 54 units" when it does, and the course count and conflicts now sit in a footer at the bottom of the rail (and of the phone sheet), like Courseweave's, instead of a dense summary under the Courses heading. RailLayout gains a railFooter slot.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: list the plan's figures in one column and flash rows jumped to
Some checks failed
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
kennel/build build succeeded
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
952547af91
The rail's four plan figures are now one column of label and value rows instead of a two-by-two grid of boxes. Jumping to a course from the requisite list flashes its row with the danger tint and a red edge, held still when the student prefers reduced motion.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: keep a program's kind badge beside its name
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
86e977f0cd
The name and badges wrapped as separate flex items, so a long name left "Minor" on a line of its own; the badges now follow the name's text and stay with its last word.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: title the degree plan with its name, renamed in place and switched from a menu
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
3c621c6e57
The plan's name replaces "Degree plan" as the page title; clicking it renames the plan in place, and a chevron beside it lists the student's plans and "New plan", replacing the plan select and the New plan button. The rail no longer repeats "Plan" (RailLayout showTitle), and OptionsMenu takes an icon and placement.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Select is now a select-only combobox (the WAI-ARIA pattern) instead of a
native <select>: a button that opens a listbox in the same popover panel
as Combobox and MultiSelect, with a check on the chosen option, the soft
field border and radius in both variants, and a chevron that turns while
open. Enter, Space and the arrows open it; the arrows, Home, End, Page
Up/Down and typeahead move; Enter selects; Escape closes without closing
a surrounding dialog; Tab closes and moves on. Its props are unchanged
(onchange still reads event.currentTarget.value), so every user switches
over; a trigger snippet draws it as an icon or inline text, which
replaces the transparent native selects on the Courses toolbar's phone
sort button and the plan rail's first semester. Compact selects are 40px
on phones and list rows are 40px below lg. Combobox's chevron matches.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Selects are no longer native: record why (the browser's open list didn't
match the site's dropdowns), the select-only combobox's keys, and what we
now maintain. design.md describes Select's new props and focus, and drops
the 4px radius for compact selects.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Keeps the plan rail's one-column figures and converts its first semester
picker to the custom Select.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
chore: merge the custom selects
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
1e2e359cb6
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: put the plan's title in the semesters' toolbar row
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
6636adec80
The plan's name, its switcher, the Cards/Detailed switch and the plan's options now share one row at the top of the semesters column, level with the requirements heading, instead of a mostly empty band above the page. The switch shows only its icons below 1280px, and the title no longer reserves room for a hidden pencil.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: keep the plan's requirements in the rail below wide screens
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
kennel/build build succeeded
6355abfc35
Below a 1120-pixel main column the requirements are the rail's last group (its sheet on phones) instead of a tab beside the semesters, drawn without cards so they read as part of the rail; the semesters keep the whole main column. Wider, they keep their sticky column. Table scrollers are now positioned, so screen reader text in a wide table no longer widens the page on phones. Decision 44 is amended.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
docs: rewrap the plan title row's description
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
284894a183
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The explorer gets a Graph view beside its list (remembered per device,
?view=graph in links): the course in the middle, its requisite expression
to the left as brackets labelled AND, OR, N OF and dashed WITH for
corequisites, and what it unlocks to the right, by department when there
are many. Courses and brackets show the student's standing from the course
pages' own checks. Clicking a course expands its prerequisites, previews
it, opens it or centers the graph on it. Pan, zoom, fit and arrow-key
navigation; phones read top to bottom. The course page's prerequisite
tree link opens it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The status line offers Sign in where there's no record to check against,
the legend's duplicate note goes, and the legend shows the yellow line of
a requirement met by planned courses.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
docs: list decision 45 in the index
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check-1 (pull_request) Failing after 12m4s
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
bb40993e65
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The rail grows to 480 pixels at 1920, then by 30% of each extra pixel to 672 at 2560 and at most 760; the plan's requirements column grows to 600 pixels (560 beside the detailed table), where its rows fit on one line.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: flash every cell of a plan table row jumped to
Some checks failed
CI / build (pull_request) Has been cancelled
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
kennel/build build succeeded
fefcf56424
The table's sticky course number cell paints its own background, which hid the row's flash. A flashed table row now flashes its cells with an inset shadow over their own backgrounds, the first carrying the danger edge.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: line rails up the same whether or not they scroll
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m54s
CI / check-1 (pull_request) Failing after 18m45s
CI / check (pull_request) Has been cancelled
43d4f5e865
A rail's scrollbar narrowed its content, so a page whose rail scrolls didn't line up with one whose rail doesn't. Rails now keep a stable scrollbar gutter, and their content reaches under it with a negative margin of the measured gutter width, so the scrollbar sits over the right padding.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
style: drop side stripes, eyebrows and stat blocks
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
kennel/build build succeeded
dc2796ddf4
Calendar blocks are a firmer tint of their course's color with a hairline edge instead of a 3px stripe, with deeper text (at least 5.8:1 in both themes). Selected course and instructor rows take a new selected fill instead of an accent bar, and the jump flash is a tint without its edge. The shared schedule's eyebrow became a line under its title, the requisite explorer's header sits on the page with its figures in the text and the List/Graph switch beside it, the graph's brackets read all of, one of and with in sentence case, and the course panel's sticky header is solid instead of frosted.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Over 54 factorable units, a semester's or schedule's factorable count turns the warning color with an icon and says "Exceeds 54 factorable units" on hover; FCE hours well above what the units suggest do the same. loadWarnings becomes loadFlags.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: keep requirement rows on one line with Add on hover
Some checks failed
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check-1 (pull_request) Has been cancelled
kennel/build build succeeded
4b26fb88c5
Each requirement row shows its number, truncated title, the semester it counted in and its units on one line at any width. Add appears over the row's end on hover or focus; on touch it's a + in the row.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
style: cut explanatory copy and put how-to hints behind a (?)
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
e7ad95056d
Removes copy that explains a page or restates what students know (the requisite explorer's intro, FCE hours including class time, what factorable units are, the record sections' descriptions, the college field's note, the calendar settings' note). The graph's controls and dragging requirements onto semesters become HelpTip hints, a new (?) primitive over Tooltip; the waivers description is one short line and a shared schedule is tagged Read only.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: size course number columns by their font
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check (pull_request) Failing after 0s
CI / check-1 (pull_request) Failing after 6m15s
CI / build (pull_request) Successful in 3m12s
d8ec2763dc
Course number columns had pixel widths narrower than "15-122" at their font size (52px for 54px of text in the transfer credit picker, 56px for 62px in the requisite search), so the number broke onto two lines. A code-column utility now sizes them at least 4em wide, unwrapped, in all nine lists.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: fold the footer away and run the plan's requirements down to it
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check (pull_request) Has been cancelled
CI / check-1 (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
kennel/build build succeeded
739fd719fc
The footer gets a hide button that folds it to a one-line bar, remembered on the device. The plan's sticky requirements column reaches through the page's bottom padding to meet the footer, and the note under the semesters is gone.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
docs: describe the folding footer and flagged semester totals
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m46s
CI / check-1 (pull_request) Failing after 19m34s
CI / check (pull_request) Has been cancelled
da9207f94b
Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: line up a chosen section's Added with Switch and reveal Remove on hover
Some checks failed
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
CI / checks (pull_request) Failing after 4s
CI / check (pull_request) Failing after 0s
b5386be7d9
The scraper read every response as Latin-1, but the SOC's detail pages are UTF-8, so catalog descriptions with curly
quotes, dashes or bullets were garbled ("Todayâ\u0080\u0099s"). Captures store each page's text as UTF-8, but
the builder read them back as Latin-1, garbling accented titles in every SOC term ("Pokémon GO") and
leaving a stray character after two. Both now decode by the declared charset and read captures as UTF-8, as
Courseweave does.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix(index): keep the Schedule of Classes' meeting times when merging supplements
Some checks failed
CI / check-1 (pull_request) Failing after 6m36s
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
CI / build (pull_request) Successful in 10m6s
CI / check (pull_request) Failing after 0s
kennel/deploy service 'api' failed healthcheck within startup grace period
2774775af4
A supplement added each meeting whose start and days the primary section didn't list. Next to the SOC's current
times, Stellic's were out of date or the same meetings split by day (MWR beside MW and R), so sections showed extra
meetings and false conflicts: 46 in Fall 2026, 9 each in Spring 2026 and Fall 2025, and 24 in Spring 2027.
`addMissingMeetings` now defaults to `addMissingSections`, which is false when the SOC is primary; rooms and TBA
sections are still filled in.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat(data): build Spring 2027 from the Schedule of Classes
Some checks failed
CI / check-1 (pull_request) Failing after 6m36s
CI / check (pull_request) Failing after 0s
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 2m44s
f93a714c77
The registrar published Spring 2027 on the Schedule of Classes. The term now builds from the SOC capture of
October 26, like the other terms, with the Stellic sections and the registrar's CSV from October 21 as supplements for
instructors and rooms: 3,086 courses and 6,752 sections at Pittsburgh, with seat reservations and special permission.
The data audit lists what changed since October 21 and how the sources compare.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
docs(data): record the October 26 Stellic refresh for Spring 2027
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 1m47s
CI / check-1 (pull_request) Failing after 6m27s
CI / check (pull_request) Failing after 0s
99a1e9021e
Stellic's Spring 2027 sections were refetched on October 26: they cover every course and section the Schedule of
Classes lists and agree with its meeting times, with 406 sections in new rooms and 23 with new instructors.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: name a course card's instructors and hide empty corequisites
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build failed
CI / build (pull_request) Successful in 4m18s
CI / check-1 (pull_request) Failing after 6m24s
CI / check (pull_request) Failing after 0s
af55d186b4
Signed in, a course card says who teaches the course in its semester: up to three names linked to their pages, or two
and how many more. They come from the listing, or from the teaching document when the card's semester select picks
another semester and in the all-semesters view, so no semester's courses load for them. The Corequisites line only
shows when the course has corequisites, and the Expand all pill gets room on both sides. Signed out, list rows no
longer say every course's instructor is TBA: instructors are member data.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
- The instructor page's stat boxes and its two tables (sections taught, evaluation history) used the finals table's
  4px corners and strong border, with oversized headers. They now use the site's cards: 10px corners, the soft card
  border, and headers as in the FCE tables.
- The course card's toggle says Show more, so it doesn't read like the requisite graph's Expand all.
- Course profile shows a chevron that turns when it opens.
- Cut copy that explained the obvious: on friends' sharing, placements and overrides, transfer credit, AP credit, the
  rail's other-semesters switch and the syllabi count.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Each preview card gives the schedule's units and FCE hours a week under its class count, as the Details view does
(hours only when signed in), and its link's name says them too.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: open the full course page on narrow screens and move the panel's star to its bar
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
CI / build (pull_request) Successful in 3m57s
CI / check-1 (pull_request) Has been cancelled
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / check (pull_request) Has been cancelled
85a4917745
- Below 1024px the course panel would take the whole screen, so selecting a course opens the full course page (which
  starts at its top and has Back to courses). A ?course= link still opens the panel.
- The panel's save star sits at the left of its top bar, in the row with Expand and Close, instead of beside the big
  course number.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
style: let the instructor page's stat rows span the full width
Some checks failed
kennel/build build succeeded
CI / build (pull_request) Successful in 4m20s
CI / check-1 (pull_request) Failing after 6m22s
CI / check (pull_request) Failing after 0s
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/deploy service 'api' failed healthcheck within startup grace period
557bc7a084
They stopped at 36rem, short of the tables and cards under them.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
style: give the final exam table the site's card look
Some checks failed
CI / check-1 (pull_request) Failing after 6m31s
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m43s
CI / check (pull_request) Failing after 0s
ab5347179b
The final exam lookup and DataTable's outline variant kept the finals style: 4px corners, the strong border and
large headers. Every outlined table now sits in the site's card (10px corners, the soft card border) with headers as
in the FCE tables, the look the instructor page's tables got, so the separate card class is gone.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: keep a variable-units course's field inside the plan table's Units column
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m58s
CI / check-1 (pull_request) Failing after 6m36s
CI / check (pull_request) Failing after 0s
e63c629143
The detailed plan's Units column was 52px with 8px of padding, but a variable-units course gets a 56px field, which
ran into the Satisfies column and over its first letters. The column is 64px now.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Preferences gain calendarDisplay.title (show each class's course name on the calendar, off unless chosen) and
sectionPicker (dropdown, the default, or compact: section letters to hover through). Both default when missing, so
stored preferences and older clients keep working. The OpenAPI document, the website's types, its preferences store
and the mock API follow.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: course names on the calendar and a compact section picker
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m21s
CI / check-1 (pull_request) Failing after 6m13s
CI / check (pull_request) Failing after 0s
189554851d
- Calendar display gains Course name: each class's course (or topics section) name under its number, before the time
  and room, when there's space, in the PNG export too.
- Sections can be picked with compact letters instead of dropdowns (the calendar display menu and Profile, Settings).
  The letters are a course's recitations, a row per lecture, else its lectures or sections; choosing a recitation sets
  its lecture too. Hovering or focusing one previews it on the calendar and in the lines under the letters: times,
  room, instructors and what it would conflict with. A conflicting lecture is marked on its row, a conflicting letter
  in red with a dot, and arrows move between letters from one tab stop.
- The dropdowns and the fixed lines name the chosen section's instructors (a recitation's only when they aren't its
  lecture's).

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A segmented control's height came from its segments plus padding and border: 34px on desktops (46px on phones)
beside 36 or 40px buttons. The control now has a set height, 40px and 36px from 1024px for sm (the toolbars' buttons
and compact selects) and 40px for md, its segments filling it. The plan's options button and the Courses toolbar's
chart button take the same heights, and a semester's synced-schedule badge is a circle on phones, where only its
icon shows.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
- A handle replaces each course's Move up and Move down menu: drag it up or down the list (the rows it passes make
  room; touch works too), or focus it and press the up and down arrow keys.
- The course's name runs the row's whole width, under the buttons, instead of stopping beside them.
- Compact letters are one run: each lecture's recitations after the last's, split by a rule, wrapping where the rail
  ends. A conflicting lecture's letters are tinted red instead of labeled.
- The schedule's session switch is as tall as the toolbar's buttons.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: let the plan's requirements column scroll from the header to the screen's bottom
Some checks failed
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m47s
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check-1 (pull_request) Failing after 6m11s
CI / check (pull_request) Failing after 0s
50940903e0
Stuck beside the semesters, the requirements column's scroll box stopped 12px under the site header and 36px above
the bottom of the screen, so scrolled rows were cut off along lines with the page showing past them. The box now runs
from the header to the bottom of the screen, padded as before, and fades at both edges.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
- The letters are one line that scrolls sideways when they don't fit, faded at an edge with letters past it. The
  chosen letter is scrolled into view (centered) when it changes or the line first shows, including in the phone
  sheet, and arrow keys keep the focused letter clear of the fades.
- Letters that fit stretch to share the line equally; each is at least 40px (28px from 1024px) or its label's width.
- Letters are 14px, up from 12px.
- A letter's conflict dot sits by the letter rather than in its button's corner.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Over the unit guidance, a schedule's "66 factorable" and a semester's "66 factorable" or "60 FCE hrs/wk" get a warning
icon, and the icon, the count and the word were each an item of the row, so the row's 4px gap came between the count
and its word too (in the schedule, on top of a no-break space). The count and word are now one item, a space apart.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
A course page's Offered pills set their heights with `class` over Chip's md size, whose `h-[25px]` comes after `h-8`
and `h-10` in the stylesheet: the shown semester's pill was 25px beside 32px links, and on phones the links were 25px
rather than 40px to tap. Chip gets an lg size (32px, 40px on phones) that both use.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: let a requirement's unneeded options be dragged onto a semester
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m37s
CI / check-1 (pull_request) Failing after 6m39s
CI / check (pull_request) Failing after 0s
a0f255cce3
The courses under "other options not needed" (a requirement met by another course) couldn't be dragged, so planning
one meant searching for it. They now drag onto a semester like the others; they still have no Add, since the
requirement doesn't need them.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: drop the pass/fail box from schedule courses
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 4m10s
CI / check-1 (pull_request) Failing after 6m39s
CI / check (pull_request) Failing after 0s
cdeed3d05f
Taking a course pass/fail is rare, and its box sat under every course in the schedule. A course shows it only once
it's on (set before, or from the plan's course menu, which a synced semester shares), so it can be turned off there;
pass/fail-only courses still say so.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: keep a sticky scrolling column's end on screen before it sticks
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / check-1 (pull_request) Failing after 6m35s
kennel/build build succeeded
kennel/deploy service 'web-api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 12m1s
CI / check (pull_request) Failing after 0s
1d70cf31ed
The course panel beside the list, the instructors' rows beside an open instructor and the plan's requirements stick
under the header at the screen's height. Until the page scrolls them up there they start lower, under the toolbar or
the import banner, so they ran past the bottom of the screen: scrolling inside one stopped short of its end, and as it
contains its overscroll, the page didn't scroll to show the rest. A new attachment, roomBelow, keeps --room-below at
the room between a column's top and the screen's bottom, and each column's max height stays within it.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
fix: answer a missing website build file with a 404 and say how long website files may be kept
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
CI / build (pull_request) Successful in 2m47s
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / check-1 (pull_request) Failing after 6m32s
CI / check (pull_request) Failing after 0s
8c32e71226
The web API answered any missing file with the website's index.html (200, no Cache-Control), build files under /_app
included. Behind Cloudflare, which keeps .js responses for four hours when the origin says nothing, a request for a
chunk the running build didn't have stored the page under the chunk's name; when a later build brought that name back,
browsers got the page instead of the script and the app failed to start with Error 500. Missing files under /_app are
now 404s, and website responses say how long they may be kept: a year for content-hashed build files, revalidated for
everything else, and never for errors.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
The rows a dragged course passed moved aside to make room. Dropping it put
them in their new places with their offsets still sliding back to zero, so
they jumped a place and slid back. They now skip that slide for the update
that reorders them.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
feat: drag schedule tabs to rearrange them
Some checks failed
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
CI / build (pull_request) Successful in 3m45s
CI / check-1 (pull_request) Failing after 6m20s
CI / check (pull_request) Failing after 0s
1ae95e13a8
A schedule tab drags along the strip, the tabs it passes making room, and
dropping it shows it, as a browser's tabs do. A mouse drags once it moves a
few pixels, so clicks and double-clicks still switch and rename; a finger
holds the tab for 0.4 seconds first, so a swipe still scrolls the strip,
which scrolls along near its ends. The options menu's Move left and Move
right do the same for the shown tab, for the keyboard and screen readers.

The order follows the student between devices as the scheduleOrder
preference (term to schedule ids); schedules it doesn't list, such as new
ones, follow, oldest first.

Claude-Session: https://claude.ai/code/session_01To6tQkufybQpWVMd4tD5UH
Some checks are pending
nixbot/nix-eval evaluation succeeded in 0s (1 warning)
Required
Details
nixbot/nix-build 0 attributes built
kennel/build build succeeded
kennel/deploy service 'api' failed healthcheck within startup grace period
Required
Details
CI / build (pull_request) Successful in 3m45s
CI / check-1 (pull_request) Failing after 6m20s
CI / check (pull_request) Failing after 0s
CI / checks*
Required
This pull request has changes conflicting with the target branch.
  • .forgejo/workflows/scrape.yml
  • README.md
  • crates/courses-web-api/src/auth.rs
  • crates/courses-web-api/src/backend/mod.rs
  • crates/courses-web-api/src/backend/personal.rs
  • crates/courses-web-api/src/backend/tests.rs
  • crates/courses-web-api/src/main.rs
  • flake.nix
  • scripts/check_deployment.py
  • scripts/import_catalog.py
  • secretspec.toml
  • sites/docs/src/api/data-audit.md
  • sites/docs/src/api/sqlite-backend.md
  • sites/docs/src/api/user.md
  • sites/docs/src/deployment.md
  • sites/docs/src/features.md
  • sites/web/deno.json
  • sites/web/deno.lock
  • sites/web/package.json
  • sites/web/src/lib/catalog.ts
  • sites/web/src/lib/components/Footer.svelte
  • sites/web/src/routes/+layout.svelte
  • sites/web/src/routes/+page.svelte
  • sites/web/src/routes/profile/+layout.svelte
  • sites/web/src/routes/profile/+page.svelte
  • sites/web/src/routes/profile/saved/+page.svelte
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 'courseweave:courseweave'
git switch 'courseweave'
Sign in to join this conversation.
No description provided.