Skip to content

fix: improve the catalogue interface - #130

Merged
HarryRandall merged 17 commits into
mainfrom
fix/ui-improvements
Sep 22, 2026
Merged

HarryRandall merged 17 commits into
mainfrom
fix/ui-improvements

Conversation

@HarryRandall

Copy link
Copy Markdown
Owner

Summary

This is stack 15 of 16. It contains the focused commits between feat/landing-redesign and fix/ui-improvements and targets the branch immediately below it.

Verification

  • GitHub Quality gate
  • GitHub Route gate
  • GitHub Database gate
  • GitHub Browser gate

Deployment notes

Do not deploy this branch independently. Merge the stack from the bottom upwards. The one-time hosted database rebuild remains a separate approved operation after the complete stack reaches main.

@vercel

vercel Bot commented Sep 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
coursemap Ready Ready Preview Sep 22, 2026 12:53pm UTC

`pnpm dev` ran against whatever `.env.local` held, which is the hosted
project carrying the pre-redesign schema, so the obvious command failed
with PGRST205 while the working one, `dev:local`, had to be learned. The
default is now the local stack for dev, build and start alike, and the
hosted credentials are read only by the explicit `:prod` variants.

Build and start gained the same treatment because a production build
against the local stack had no command at all: the publishable key is
baked in at build time, so a build reading hosted values cannot be served
against local Postgres. `preview:local` does both steps in one.

The Supabase status parsing that dev-preview owned moves to
supabase-environment.mjs, shared by the three preview entry points, and
each of them now spawns the Next binary directly rather than recursing
through a pnpm script that would re-read the ambient environment.
The sign-in form only existed as a submit handler, so a password typed
before hydration finished posted nowhere and the page appeared to swallow
it. The form now posts to /auth/password, which signs in on the server and
redirects, and the client handler still takes over once it is live.

The route only accepts same-origin posts, takes a single value for each
field, and sends a failed attempt back to /login?error=invalid-login
rather than naming which half was wrong. Both handler paths reach the same
message, so the server-rendered failure and the client one read alike.

/auth/password and /auth/sign-in join the handler paths that
safeInternalRedirect refuses as a destination, so a next= parameter
cannot aim the redirect back into the sign-in machinery.
A failure under /admin dropped the operator onto the bare error layout
with a "Back to home" button aimed at the student dashboard, so recovering
from a broken admin page meant navigating back in from the outside. The
error boundary now recognises an admin path, renders inside AppShell, and
points home at /admin/dashboard.

Catalogue activity gets its own boundary as well, so a failure there keeps
its breadcrumbs and tabs and says which screen could not load rather than
falling back to the generic page error.
"Operations" was a section of one item sitting beside Catalogue while
describing the same records, so an administrator had two places to look
for catalogue work. It becomes "Activity" inside Catalogue, and the
breadcrumbs drop the operations segment they could not link anywhere.

The catalogue links carry the year the operator is already reading, so
moving between Courses and Programmes keeps the year rather than bouncing
back to the profile default, while the active-state match still uses the
yearless path so every year under a section highlights it.

Discovery checks gained the filter bar the other catalogue tables have:
listing, year, status and completeness, with an empty state that offers
to clear them. The page titles students see now match their navigation
labels: Academic history, Calendar, Profile.
Opening a catalogue record created its draft, so every record anyone had
looked at reported itself as a draft, and discarding one brought it back on
the next render. A draft is now created by the first change worth keeping:
the editor reads the publication, or an empty record, and saving content
that matches it writes nothing.

A draft is also only reported while it still says something the record did
not, so restoring the published version or editing a draft back by hand
returns the record to Published or Not published. The directory rows carry
the draft revision, so publishing, discarding and unpublishing can be done
from the row menu against the draft the list actually read.

The rows the old behaviour left behind are deleted by migration: a draft at
revision 0 that was never restored is byte-identical to its base, so nothing
authored is lost.
A record with no draft opened as a page of live inputs, so the published
version could be changed by a stray keystroke and there was nothing to say
whether it was being read or written. The fields are now read until Edit is
pressed, and Edit only unlocks them - the draft still arrives with the first
change worth keeping.

The session behind those fields moved into a provider so the toolbar that
reports and commits it can lead the page instead of sitting above the
fields, where it was below the title it described. The toolbar now names the
record's state in words - Published, Draft, Not published - and what that
means for students, and Cancel backs out of editing while nothing has been
saved.

The header drops "Listed by ANU": it was true of nearly every record and so
said nothing, while the delisting warning it shared a line with is worth
keeping. View on ANU moves up beside the title it links to.
Popovers shared the page background, so in dark the border and shadow that
carry elevation in light were both nearly invisible and a menu read as a
hole rather than a card. The popover surface is now the lighter one, and the
menu's own hover tone is mixed from the foreground rather than --accent,
which was mixed against the page and vanished against it.

The directory drops the columns nobody read and gives identity the spare
width, a running sync reports itself through a task toast whose icon holds
the first line, and a loading toast keeps the room a close button would have
taken.
One sync was a single scroll, so a failing stage sat below several screens of
contract versions and the artefact viewer never had the page to itself. The
record now divides into Overview, Stages, Extractions and Artefacts, with the
failure count on the Stages tab and sections that hold nothing disabled
rather than shown empty. The status and any error stay above the tabs,
because they are true of the sync whichever part of it is being read.

Facts sit in cards and the content keeps a reading measure: highlighted
source and long fact grids became unreadable when a wide screen stretched
them edge to edge, so tables and artefacts scroll inside that measure instead
of widening past it.
Reading a sparse record showed every field the form could hold, so a course
with six facts filled a screen with labels above em dashes and the six facts
were lost in it. A read now carries only what was recorded: empty fields,
empty collections and rules nobody wrote are left out, and a section with
nothing in it says so once instead of listing its blanks.

Labels drop to muted and values take the foreground, because the value is
the content and it was the fainter of the two.
A sync and a listing check answer the same kinds of question - what ran,
against what, and what came back - but each had invented its own furniture,
so the discovery check floated facts on the page while the sync put them in
cards. Both now draw on one set of layout pieces, and the check's title names
the kind properly rather than printing the database value.
Everything on the Changes tab is the output of a sync, and every source entry
in the changelog was written by one, but neither said which. Both now name
the check behind them and link to its diagnostics, for readers who hold the
catalogue operations permission.

The admin overview drew its import model as loose page furniture beside a
card, so the two tiles in one grid did not look like they belonged to each
other. It is a card now, with the same measure, padding and icon treatment as
the tile it sits next to.
--accent is mixed against the page ground, so in dark it resolves to
oklch(~0.204) while the popover surface a menu opens on is oklch(0.205): any
highlight drawn with it disappeared on exactly the surface it was needed on.
Nova's translucent menu preset already avoids this for dropdowns and selects,
but three places did not - the notifications panel, the import model picker,
which overrode the preset with an --accent utility of its own, and command
items, which the preset does not cover.

All three now mix from the foreground, which reads on whatever ground it is
laid over, in both themes. Measured in a browser rather than reasoned about:
the hovered item background is now a foreground mix at 8-10% on both.
A source page is the record of one exact set of bytes, and
`catalogue_source_pages_reject_mutation` raises on every update. The upsert's
`do update set fetched_at` therefore failed the moment ANU returned a listing
whose bytes had not changed. Refetching an unchanged listing now reuses the
page already recorded; when the refresh happened is on the discovery check
that asked for it.
`toast.loading` withholds sonner's close button, so work that carries on
server-side left a toast nobody could put away. The running toast is now an
ordinary one with a spinner for an icon, which gets the close button back, and
the close button overlaps the title alone so descriptions and the progress bar
keep the full width.

The bar also caught up to reported progress by easing, which only ever
approaches a value and stranded it short of the phase it had been told about.
It now closes the reported distance briskly, then drifts through the rest of
the phase. A task that reports nothing for longer than either endpoint's cap
is assumed to have lost whatever was driving it and says so.
`SectionTabs` took an icon as a rendered node, so every caller sized it itself
and a bar of tabs came out uneven. It takes the component now and sizes it
once. The route map gains the catalogue activity sections and the four record
sections, so a tab and the breadcrumb above it read from one list; the
breadcrumb names its trailing icon by key rather than by component, which is
what lets a server page ask for one.

Sync diagnostics also gains what it was missing to be read by someone who has
not written the pipeline: each artefact tab explains what that artefact is,
and the viewer scrolls inside a window rather than setting the page's length.
A draft appearing on the first saved change meant backing out of the editor
was sometimes discarding a draft and sometimes nothing at all, and a record
someone had started work on stopped reading as theirs the moment they
navigated away. Opening the editor now creates the draft deliberately, through
`beginCatalogueDraft`, so the act has a row behind it.

That splits one flag into two questions the directory was conflating: whether
a draft is open, and whether it says anything the publication does not. An
untouched draft can be discarded but not published, and only the second
question answers that.

`catalogueRecordState` collapses the badge's cascade and the filter's options
into one list, so what an operator can select is exactly what they can see,
and a broken sync outranks a delisting outranks waiting changes.
Base automatically changed from feat/landing-redesign to main September 22, 2026 12:39
@HarryRandall
HarryRandall merged commit 25ab8ba into main Sep 22, 2026
4 of 6 checks passed
@HarryRandall
HarryRandall deleted the fix/ui-improvements branch September 22, 2026 12:39

This branch was successfully deployed

1 active deployment
Preview 25ab8ba3 Deployed Sep 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant