Skip to content

feat(theme): dark mode and a redesign of the student and staff pages - #192

Closed
Clupai8o0 wants to merge 305 commits into
11.0.xfrom
theme/status-and-globals
Closed

Clupai8o0 wants to merge 305 commits into
11.0.xfrom
theme/status-and-globals

Conversation

@Clupai8o0

@Clupai8o0 Clupai8o0 commented Sep 11, 2026 •

Copy link
Copy Markdown

What this lands

Dark mode and a redesign of the student and staff pages, on one design language: token colours in both themes, cards with even spacing, page headers with actions on the right, and real loading, empty and error states. It also carries Maple Fox's reviewed mobile integration (#217), which was merged into this branch, so merging this PR brings #217 with it.

1. Theme and dark mode

  • Semantic tokens for every surface, status, chart, meter and chat bubble, in src/styles/tokens/_light.scss and _dark.scss, recorded with their contrast ratios in docs/theme/THEME-CONTRACT.md.
  • Status circles are 36px with an 18px white icon on fills that clear 4.5:1 in both themes.
  • Material's dark tables, toggles, paginators, expansion panels and date pickers read the slate tokens instead of M2's neutral #424242.
  • One focus ring everywhere (Material form fields no longer draw a second one).

2. Student pages

  • Home: each unit a student studies gets the same card as a unit staff teach (colour band, target grade, teaching week, links to tasks and the Task Planner).
  • Cross-unit dashboard, project dashboard, Task Planner and portfolio wizard redesigned.
  • Task list: grade, quality and deadline badges beside the status circle, not on it; filters as a search pill and a status pill.
  • Task page: a dated timeline instead of the Complete By slider; compact submission card; the submission PDF scrolls to its end and takes the panel; Task Statuses shows the four main statuses plus any in use.
  • PDF viewer (Integration: connect reviewed web fixes with mobile API support #217's component, themed): icon buttons with tooltips, a search pill, full screen, and it fills a phone screen.
  • Chat: visible bubbles in light mode; in a narrow panel the message box gets its own row.

3. Staff pages

Inbox and marking, the unit task viewer, Students, Groups, portfolio marking, Analytics, unit administration (all tabs), Rollover, the staff home and unit lists, and Discussion and check-in. The task tabs (History, Similarities, Student notes, Mod notes) and the Moderation notes and Assess task quality dialogs follow the same pattern. Each area fixes the bugs found in it, most with a spec that fails without the fix.

4. Fixes along the way

  • Staff on a student's page never had the student: the API refuses them /users/:id, so the student now comes from the unit's student list. The Mod notes tab threw on every render because of it, which also stopped the header menus opening.
  • A wrong or stale project link now shows an error with Try again instead of an endless skeleton.
  • Sign out finishes when no service worker controls the page.
  • Dark mode after Integration: connect reviewed web fixes with mobile API support #217: the notification menu, the start-up screens, every loading skeleton and a few dialogs had light-only colours again. They are back on the tokens.
  • Model fixes: the progress bar's grey share, Unit.refreshStudents never subscribing, Project.isEnrolledIn and Tutorial.description throwing on missing data, Project.assignGrade keeping a failed rationale.

Relationship to other PRs

Built against

Scope

703 files, +53,590 / -16,917 (GitHub's count), 254 non-merge commits ahead of 11.0.x, including #217's.

Testing

On Node 22 at a5c9d5618:

  • npm run test:ci: exit 0, 233 files, 1721 tests passed
  • npx ng lint --max-warnings 0: exit 0
  • npm run typecheck: exit 0
  • npx ng build: exit 0, no warnings
  • npx prettier --check on the files changed in each round: exit 0

Checked by hand on the local demo stack: as a convenor in light and dark (inbox, task tabs with seeded history and similarity data, unit administration, the student task page as staff, the PDF viewer at desktop and phone width); student-only views through their specs and earlier live passes.

Known open items

  • Tutor notifications for extension requests and Need Help: API in doubtfire-api Integration: feature/notifications into 11.0.x #161; this PR already shows both events in the notification list and settings.
  • The student home cards have not been seen live as a student yet.
  • Discussion check-in camera flow still wants a real phone.
  • Unit Hub was checked in both themes against an API without its endpoint (the error state and Study essentials). Its feed and editor need the matching API. Its Calendar settings icon (calendar_add_on) is not in the icon font, so it shows blank.

maplefoxgit and others added 10 commits September 1, 2026 09:38
…ile-feedback-batches-01-15-20260831

fix(mobile): complete OnTrack feedback remediation
…rence-sync

fix(theme): sync account preference across devices
Swap the hard-coded colours the M01 shell work left behind to the --ot-*
tokens so the shell and global list surfaces flip in dark mode:

- notification dropdown message, time, stale and empty text, the unread
  dot, and the bell badge cut-out ring
- task-dropdown breadcrumb icons (were hard black on the dark toolbar)
- footer action bar and its status-chip resting and disabled states
- the global selectable-table row hover (.f-table and .table-selectable)
- the group-selector row hover

Footer bar uses --ot-color-page so it keeps contrast with the white status
buttons in light mode. Tokens verified present in both palettes. Web suite
800 pass 0 fail, lint clean, build green on Node 22.
…tokens

Retarget the runtime-themed surfaces the shell PR did not cover so they flip
with data-ot-theme:

- status-icon chips read var(--ot-status-<key>) and the AA-fixed
  var(--ot-status-<key>-on) instead of the compile-time light-only map, so
  every task-status chip across the app themes correctly
- footer status chips drive --status-chip-bg from the same token
- grade/quality task-list badges invert against the list surface
  (--ot-color-inverse-surface/-inverse-text) instead of a black circle that
  vanished on the dark list
- custom scrollbars and the global progress spinner use border/divider tokens

Note: adopting the contract's -on foregrounds shifts a few status labels in
LIGHT mode too (e.g. working-on-it goes to dark text on amber), which is the
contract's WCAG AA choice, not a regression. The status colour used inside the
d3/canvas charts still reads the light-only TS map and is a separate follow-up.
Build, lint and web suite (800 pass, 0 fail) green on Node 22.
…agement passport

Register the theme tokens as Tailwind colours (bg-ot-surface, text-ot-muted,
border-ot-divider, ...) so literal colour utilities can be swapped for token-backed
ones that flip with data-ot-theme, no dark: variant needed. First use: the
engagement passport card that showed as a white slab on the dark dashboard.
…me tokens

Complete the per-screen dark-mode migration (THM-M02 / THM-M04) and the browser
chrome (THM-W01):

- ~600 hard-coded colour swaps across ~90 screen files: student dashboards, task
  and comment views, unit editors, modals, viewers and calendars now use the
  bg-ot-*/text-ot-*/border-ot-* Tailwind utilities and var(--ot-*) SCSS tokens, so
  they flip with data-ot-theme.
- ThemeColorService resolves --ot-* tokens to concrete colours for ngx-charts and
  other JS that cannot consume a CSS var. The status pie, task-status cards and
  burndown series read status/chart tokens; the pie re-renders on a live theme flip.
- THM-W01: the browser/PWA address-bar colour follows the resolved theme.

Web suite 800 pass 0 fail, lint clean, build green on Node 22.
…olish

Re-base dark mode on a GitHub-blue ground and finish dark-mode coverage
and polish across the app:

- Dark tokens re-based on a GitHub-blue ramp; softened light-mode whites
- Vivid, AA-verified status colours; the task-status card wraps in its
  status colour (solid header, tinted body, coloured border); the status
  header icon is enlarged with tighter padding
- Outlined buttons given a clear control fill and border so they read as
  buttons in both themes
- ngx-gantt Task Planner themed dark by overriding --gantt-color-* on the
  <ngx-gantt-root> host (its inline light defaults beat a :root rule)
- Reusable f-empty-state component; empty states added to 21 tables and
  the timeout/unauthorised pages centred and tokenised
- Portfolio wizard fills the viewport with tightened spacing; body fills
  the viewport so short pages no longer show a page-colour seam
- Global keyboard :focus-visible ring and prefers-reduced-motion support
- Even, comfortable header icon-button and avatar hover targets
- Remaining screens/charts migrated to the --ot-* theme tokens
The profile form's main container used justify-center on a full-width flex row.
When the content was wider than a phone, centring pushed the overflow off BOTH
edges, so headings clipped on the left ('otification Settings') and fields bled
off the right. Switch to justify-start and let the First/Second Name pair stack
below the small breakpoint. Verified at 390px in both themes: no clipping, no
horizontal page scroll.
The task Details sub-tabs centred their overflowing strip, which clipped the
active first and last labels at the viewport edges ('ask Details' /
'Submissic'). Left-align the strip and drop the auto margins so the active tab
stays whole and the rest scrolls into view. Verified at 390px in both themes.
The unit switcher menu was pinned to a hard 400px, wider than a phone viewport;
cap it to calc(100vw - 16px). And the global 2px outward focus ring was clipped
by menu/option panel edges into two harsh bars on the focused row; inset it for
menu items and options so it frames the row. Verified at 390px in both themes.
The global keyboard focus ring uses :is(a[href], ...):focus-visible, which lands
at (0,2,1) specificity because of the a[href] arm, so the earlier menu-item inset
rule (0,2,0) never won and the ring stayed a clipped pair of bars. Scope the inset
under the panel ancestors to clear it. Verified under keyboard focus in dark: the
ring now frames the focused row.
@Clupai8o0 Clupai8o0 changed the title feat(theme): status colours, global surfaces and dark-mode completion feat(theme): status colours, dark mode and dashboard redesign Sep 13, 2026
Clupai8o0 and others added 4 commits September 14, 2026 00:48
…stence

THM-F01. Adds the --ot-* design-token layer and the ThemeService that
resolves and persists the user's theme choice.

- Token layer: src/styles/tokens/_light.scss and _dark.scss declare every
  --ot-* token, light as the bare :root default and dark under
  [data-ot-theme=dark]. These two files are the only place a colour value is
  declared. Wired into styles.scss.
- ThemeService: preference (light/dark/system) vs resolved (light/dark),
  system detection via matchMedia, persistence in localStorage under the
  ontrack.theme.preference key, writes the data-ot-theme marker and
  colorScheme. Junk or blocked storage falls back to system, never a hard
  dark. One matchMedia listener, removed on destroy.
- Proof page at /theme-demo demonstrating the section-13 component-state
  matrix and the token set, repainting in both themes with no reload.

Out of scope by contract: the accessible toggle (THM-F02), the Tailwind dark
variant (THM-F03), the no-flash <head> script and its drift test (THM-F04),
and the theme-color browser chrome (THM-W01).

Built against doubtfire-web 11.0.x @ 0ba9fd7. 17 unit tests pass, ng build
clean, eslint --max-warnings 0 clean.
Build the app shell and shared components on top of the THM-F01 token
foundation so they follow the resolved-theme marker, with no visual change
in light mode.

- Dark Angular Material M2 theme keyed to :root[data-ot-theme='dark'],
  built from a desaturated indigo palette (500 = #5457e5) so filled
  buttons, the fab and progress bars read as one accent with white labels
  (white measures 5.4:1). Repoint card, menu, dialog and select container
  colours onto the raised surface token via the --mat-* variables.
- Rework the dark token ramp to a cohesive cool-slate palette with a single
  indigo accent (was warm greys sitting too close together).
- Bind Tailwind's dark: variant to the marker (THM-F03).
- Add a single-button theme toggle that flips light and dark from the
  resolved theme (THM-F02), registered in the module and placed in the
  header; eagerly inject ThemeService in AppComponent so the marker is
  stamped on every device, not only where the toggle renders.
- Migrate hardcoded colours to tokens in the header, home dashboard, the
  f-chip shared component, the edit-profile form, the hero sidebar, and the
  two places that set the body background inline in JS. Each token resolves
  to the original light value and carries a legacy var() fallback.

Tests: theme service 17, toggle 2, app/f-chip/header/edit-profile updated
and green. Lint clean at --max-warnings 0.
11.0.x took about twenty PRs today (the theme foundation, the calendar batch, the
header and profile fixes). Four files conflicted, and each keeps this branch's
reviewed redesign:

- header: the notification bell stays on the phone toolbar; the test for the
  toolbar is adapted to that, with the theme toggle still leaving it on a phone.
- calendar dialog: the redesigned template, which already binds every method the
  merged component has. Of the base branch's calendar tests, the reviewed spec
  already covers ten; the two clipboard ones it lacked move to their own spec.
…tions

The API now tells a tutor when a student asks for help or for an extension. The notification list gives each its own icon and label, and the task notifications setting says it covers submissions, help requests and extension requests for staff, since the extension request is filed under task notifications so a tutor can switch it off.
- Bell menu: the type and time line was black and the icons used light-only
  colours. It now uses the same tokens as the notifications page. Feedback
  icons take the link blue in both, since dark primary is about 2:1 as a glyph.
- Splash, startup progress bar and recovery screen: hard-coded white since the
  mobile merge. They now use the page, surface and text tokens.
- ngx-skeleton-loader paints a fixed light grey on its own element, so every
  loading placeholder stayed light. It now uses the divider token, like the
  hand-built skeletons.
- Dialog error boxes, the demo group note, the burndown summary tiles, the Task
  Planner prerequisites dialog and the demo PPI preview drop their light-only
  colours for tokens.
- Calendar dialog: kept the redesigned dialog and added #220's HelpHubs,
  lectures and classes option, its demo-mode block, its privacy note and the
  new intro and Google wording. Each option now sits on its own line. Both new
  calendar tests moved into the redesigned spec.
- Demo controls: kept the scenario-based page and added #220's Unit Hub
  section, hosted HelpHub links form and Teams draft, with a spec for them.
  The README keeps the scenario text and adds the Unit Hub notes.
- Home: kept the redesigned page and added the Unit Hub entry above the unit
  cards, in theme tokens.
- This branch's demo store only turns on for a loaded scenario, so the Unit
  Hub and meeting-link specs configure one first.
- Dark mode: the Teams meeting composer moves to theme tokens, and the text
  fields in it, in the demo links form and in Unit Hub get an edge that shows
  in dark.
…e task list

The count hangs off the circle's top right corner, and the circle sits against
the list's scroll edge, which clips overflow. On a phone the list has no right
padding, so the count was cut in half.
The Material Icons font the app loads has no event_upcoming ligature, so it
drew the calendar glyph for event and then a second upcoming glyph that spilled
out of the icon box over the message. The task list's start date icon had the
same name and was only hidden by its clipping. Both use event now, which is the
glyph they were already showing.
@Clupai8o0

Copy link
Copy Markdown
Author

Hi Maple, going back through your three reviews against the current head (1ec29ef).

node_modules symlink (review at 0c6ecf4). Fixed by your commit db6e6e9 "fix: remove machine-specific node_modules symlink". git ls-tree 1ec29efb node_modules returns nothing, so a clean checkout resolves dependencies normally. On the overlap you raised, #188 merged into 11.0.x, #195, #198 and #200 merged into this branch, and #193, #194, #196, #197 and #199 were closed.

group_set_id sent as -1 (review at db6e6e9). Fixed by your commit 070ffae "fix(tasks): preserve unresolved group assignments on save". An unresolved cache entry is now undefined and gets left out of the payload. Only an explicit null from the individual work control (task-definition-who.component.ts sets groupSet = null) becomes -1. It has two tests in task-definition.service.spec.ts, one for group_set_id 17 with an empty group cache (field omitted) and one for explicit individual work (-1). That spec passes 4/4 locally under Node 22, and CI test is green on 1ec29ef.

Live device checks (review at 18b54cd). No commit covers this one. The phone, tablet and desktop smoke evidence in light and dark mode, and the installed PWA and provider checks, still need a real browser and device run. So I'm not claiming them here and I understand the request for changes stays until that evidence is attached.

Two small fixes went on since your last review: 8e122e6 keeps the new comment count on the task list status circle from being clipped at the list edge, and 1ec29ef swaps the due soon icon to one the Material Icons font has, because event_upcoming drew a second glyph over the message.

This head merges cleanly with 11.0.x as of e9935b6, and test, lint and build are green on it. Once #217 lands on 11.0.x this branch will need 11.0.x merged in again.

Brings the branch up to date and resolves two template conflicts.

staff-task-list: 11.0.x fixed the empty state so it is not a lone unlabelled
icon, adding role="status", aria-hidden on the icon and an sr-only message for
the narrow rail. This branch had already replaced that block with per-state
f-empty-state panels, which carry a real message and hide their own icon, but it
rendered nothing at all when narrow and so dropped the announcement. The resolved
version keeps the richer panels and restores the narrow case: each state is
wrapped in role="status" and renders an sr-only message when there is no room to
draw one. The five tests that came with the upstream fix are retargeted at the
new markup rather than relaxed, and they assert the same things: descriptive
text, a decorative icon, nothing shown while loading or before a result or when
results exist, and the message still reachable in the narrow sidebar. They now
read the message from the component so the per-state wording is covered too, and
the spec imports the real empty-state component so it asserts rendered output.

task-viewer-state: 11.0.x replaced the ng-flex-layout directives in this file
with Tailwind classes. This branch had already rewritten the same file and
removed those directives by other means, so the incoming change has nothing left
to convert. Took this branch's version after confirming no fx* directives remain
in the file, which is what that work set out to achieve.

Pre-existing on both sides of this merge and not touched here: pdf-viewer (three
specs) and tutor-discussion fail the same way before and after, verified by
running them at 1ec29ef and at the merge result.
…lved to

The five tests that arrived with the 11.0.x empty-state fix assert the old
always-rendered .center-task-list element toggled with [hidden]. The resolved
template renders a separate block per list state, so "hidden" is now "not in the
DOM" and the wrapper is found by role="status".

The assertions are the same ones, not weaker: descriptive text, a decorative
icon, nothing rendered while loading or before a result or when results exist,
and the message still reachable when the rail is too narrow to draw it. Two of
them now read the expected wording from the component instead of a fixed string,
which covers the per-state messages rather than a single generic one, and the
spec imports the real empty-state component so it asserts rendered output rather
than an unknown element.

Should have gone in with the merge commit. Only the two templates were staged.
@Clupai8o0

Copy link
Copy Markdown
Author

Closing: this work is now on integration/t2-2026 (web 8fcf4135a), together with #224, the rest of the UI stack and feature/ui-polish. PR intake has closed for the trimester, so the integration branch is the base for the demo and for the upstream PRs to thoth-tech. The full web suite (2235 tests), lint and build pass on it.

@Clupai8o0 Clupai8o0 closed this Sep 19, 2026
@Clupai8o0

Copy link
Copy Markdown
Author

Update, 27 Sep: in the end the upstream PRs to thoth-tech were cut from org 11.0.x, not from integration/t2-2026. This redesign never reached 11.0.x, so it is not in them. It stays on this branch and on integration/t2-2026 as a reference, together with #195, #198 and #200.

The upstream PRs are thoth-tech/doubtfire-api#119 to thoth-tech/doubtfire-api#127, thoth-tech#534 to thoth-tech#544, and thoth-tech/doubtfire-deploy#43.

Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Davie <W196n8y@gmail.com>
Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings the dark mode page redesign (#280, which relands
#192)
to the files this PR already carries, so every file stays in exactly one PR.
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.

2 participants