Skip to content

feat: land integration/t2-2026 (panels, notification links, a11y, Unit Hub rework) - #281

Merged
Clupai8o0 merged 155 commits into
11.0.xfrom
land/integration-t2-2026
Sep 27, 2026
Merged

Clupai8o0 merged 155 commits into
11.0.xfrom
land/integration-t2-2026

Conversation

@Clupai8o0

@Clupai8o0 Clupai8o0 commented Sep 27, 2026 •

Copy link
Copy Markdown

What this is

This lands integration/t2-2026 on 11.0.x. That branch holds eight PRs that closed unmerged on 19 Sep when PR intake closed: #223, #224, #230, #231, #232, #233, #234 and #235. It's a merge of 11.0.x @ 192078b93 (with #280, the redesign) and integration/t2-2026 @ 8fcf4135a, so every original commit is kept.

It needs ontrack-features-t2-2026/doubtfire-api#178 for the notification deep links and the new settings. Merge that one first.

What is in it

How the conflicts were resolved

45 files conflicted. The rule: keep every functional change from both sides, keep the #280 look, and where the integration change is the feature (the panels, the celebration, the Unit Hub rework) take it and style it with the theme tokens so it works in dark mode.

Calls worth knowing about:

  • Contrast: took fix(a11y): contrast tokens, accessible names, target sizes and chart theming #232's values where text reaches 4.5:1 on page, surface and raised cards in both themes. The dark border stays #798698 (3:1 on raised cards).
  • Upload dialog: integration's layout with 11.0.x's focus handling and its required comment hint. There is now one screen reader live region. The title is now "Submit task" and no longer shows the task code and name.
  • The comments panel is named "Comments" (was "Task comments") and is still a focusable region.
  • Students list, portfolios list, staff task list and the mentor editor keep the 11.0.x versions.
  • EmptyStateComponent had dropped out of the module in the merge. It is added back.
  • Test and lint fixes after the merge are inside the merge commit, not separate commits.

Built against

  • web: land/integration-t2-2026 @ 5f21dfd4f, on 11.0.x @ 192078b93
  • api: land/notification-links @ 11e800b4 (api#178), on 11.0.x @ 5de649d7
  • deploy: 11.0.x @ 1ca0413

Testing

On Node 22 at b8a936725:

  • npm run test:ci: exit 0, 290 files, 2602 tests passed
  • npm run lint: exit 0, no warnings
  • npm run typecheck: exit 0
  • npx ng build: exit 0

5f21dfd4f only fixes one spec. additional-email-verification-callback.spec.ts failed in CI because another spec in the same worker left history.state set, so it now resets it before each test. CI is green on it.

Known open items

  • Without api#178 the new Unit Hub and digest settings show their defaults but are not saved, because 11.0.x's users api does not accept them.
  • Announcement read state is kept in the browser only.
  • I haven't checked the merged pages by eye yet. I'll do that on the local stack before this goes to thoth-tech.

maplefoxgit and others added 30 commits September 14, 2026 16:27
The student Groups page only rendered the group set manager when the selected
set already had groups. A new group set that allows student-created groups
starts empty, so students saw the no-groups message and never reached the
Create form, and group work could not start without staff.

The manager now also renders when the selected set is unlocked and allows
student-created groups. When no set has groups yet, the page selects a set
students can create groups in before falling back to the first set. Locked
or staff-only empty sets still show the no-groups message.
The dialog's close predicate runs canClose() on every close, including the
ones the modal makes itself. canClose() refused while the uploader reported
isUploading, and the uploader leaves that set after a response. So a
successful upload, an invalid 2xx response and Cancel after a failed upload
were all dropped, leaving the dialog stuck and the caller's promise unsettled.

Closes the modal makes once the server has answered now skip the guard. A
failed response hands control back, so Cancel, Escape and the backdrop go
through the usual discard confirm while the kept files are still selected.
Dismissal stays blocked while the request is in flight and while a
successful response is applied. The new specs drive the real dialog, close
predicate and file uploader through each path.
The composer form listened for ngSubmit and Send was its submit button.
The emoji picker and the feedback template picker both render a search
input inside that form, so pressing Enter in either one triggered implicit
submission and posted the half-written message. The recorder buttons have
no type, so clicking them sent it too.

Send is now a plain button with a click handler and the form no longer
listens for submit. Enter in the message field still adds a new line.
The inbox and the project dashboard reuse one composer while the open task
changes. The send, upload and edit callbacks read this.task, the textarea
and stagedAttachments when the response arrived, and isSending carried over
to the next task. Sending on task A and opening task B before the response
could clear B's draft and staged files, post B's unsent text to B, upload
A's remaining attachments to B, and leave B's Send disabled.

Each send now captures its task, draft context and reply target when it is
pressed. Uploads, the follow-up text post and the draft clear all use that
capture, and the view is only reset when it still shows the same draft.
isSending follows the open task, and a task with a request still in flight
shows as sending again when it is reopened.
While editing, the field holds the comment's text and the draft is set
aside in draftBeforeEdit. Several save paths read the field anyway:
changing task, destroying the composer, adding an emoji or a template, and
pasting. Each one stored the edit text as the task's draft. Saving an edit
cleared the field and threw the set-aside draft away, and the save that
followed removed the stored draft. Choosing Reply during an edit saved the
edit text as the reply draft, and editing a second comment replaced the
set-aside draft with the first comment's text.

Draft saves now use the set-aside text while an edit is open. The draft is
set aside once when editing starts, and it goes back in the field when the
edit is saved, cancelled or replaced by a reply.
Send copies the staged list into an upload queue. Remove stays enabled for
items that are still waiting, but the queue kept uploading its copy, so a
file removed during a multi-file send was still posted to the task.

Before each upload the queue now looks the item up in the live draft and
skips it when it is gone.
A text draft kept its client_request_id from the first attempt until a send
succeeded. The API answers a repeated id with the comment it already
stored and ignores the new text. So when the first attempt was saved but
its response was lost, and the user corrected the message or its reply
target and sent again, the correction was dropped and the draft was
cleared as if it had been sent.

The id is now paired with the reply target and text it was issued for,
and that pairing is stored with the draft. Resending the same message
still reuses the id, including after a reload. Changing either one gets a
new id.
…is rebuilt

On the phone dashboard the composer is destroyed and built again when the pane
changes. A send still uploading its attachments then read the stored draft,
which the new composer may have filled with newer words, posted those and
cleared them. The send now keeps the text that was in the field when its draft
stopped showing, and a newer stored draft is left alone.
AppLifecycleService paused all media and emitted a media pause on every
window blur. A file picker, a microphone permission prompt, the address
bar or another window all blur a page that is still on screen, so a
recording in the composer ended mid-take and clicking Allow on the
microphone prompt could cancel the first recording before it started.

Only real backgrounding now pauses media: visibilitychange to hidden,
pagehide and the Page Lifecycle freeze event. Focus still publishes the
active state.
…tial reply

When the app was backgrounded during an intelligent discussion, the base
recorder's lifecycle handler called stopRecording(). For the discussion
recorder a stop posts the take straight to the reply endpoint, and the
API marks the discussion complete, so part of an answer was submitted as
the finished reply. The same pause left the prompt audio paused, its
onended never fired and Next prompt / Finish discussion stayed disabled.

The base recorder now routes lifecycle pauses through an overridable
onLifecyclePause(). The discussion recorder overrides it to cancel the
take, which never produces a recording, so nothing is posted. The dialog
drops the unfinished attempt: it cancels the recorder, stops the
countdown, timer and prompt, ignores a prompt download that lands late,
and shows that the discussion was interrupted so the student can start
again. A finished discussion that is still uploading is left alone.
Pressing play on an audio comment that had not downloaded yet queued the
play. A lifecycle pause cleared the playing state but kept the queue, so
the audio started on its own once the download finished, after the app
had been backgrounded or the route had changed.

The pause handler now clears the queued actions. The download still
loads the source, and a later play works as normal.
…hange

Save profile is disabled while the profile form is pristine. The task,
feedback and portfolio checkboxes live in f-notification-settings with
standalone ngModels, so toggling them never marked the form dirty. A user
who only changed a notification category saw a disabled "Profile saved"
button and could not save the preference.

The settings component now emits preferencesChange on each toggle and the
profile form marks itself dirty in response. A spec renders the real form
and checks that a category change alone enables Save profile and sends it.
…ield

The additional notification email block sits inside the profile form, and
Save profile is that form's submit button. Pressing Enter in the email
field submitted the whole profile and never requested verification.

Enter in that field now prevents the implicit submit and requests
verification, and does nothing when the request button would be disabled.
…ives

The verification page is OnPush by default under Angular 22, and both HTTP
callbacks only assigned plain fields. A response that arrived after the first
render never refreshed the view, so the page stayed on "Verifying" with no
next step, whether the link worked or not.

Mark the view for check in both callbacks, as the settings form already does.
The spec now renders the real template and resolves the request after the
first render, so it checks what the user actually sees.
Startup deliberately skips the refresh-token login on the verification route,
so the page always opens signed out. The next step then sent every signed-out
user to /sign_in, and sign-in never retries the refresh token, so a user who
was already signed in in that browser had to enter their credentials again.

Load /edit_profile as a full page instead. Normal startup then restores the
saved session, or saves the profile as the return URL and sends the user to
sign in as before. The in-app route is kept for a session that is already in
memory. The return URL service is no longer needed here.
…status

hasSubmissionHistory() was driving three things it could not answer well.

Loading submission details mapped submission_date with mapDate, so the null
the API sends for an unsubmitted task became the 1970 epoch, and a missing
key became an Invalid Date. Both are truthy, so every task counted as having
history once details loaded and the Upload submission button disappeared.
The date is now parsed with parseApiDate, kept when the key is absent, and
an invalid date no longer counts.

The status card also used history to pick the upload action. Resubmit and
Redo tasks have history, so they lost Upload submission and only got Upload
new files, which opens New Evidence mode and skips group contributions. The
card now follows inSubmittedState(), the same rule the upload modal uses for
New Evidence.

Tasks that take no uploads counted as having history in submitted and marked
states, which showed the Your Submission card with "Not submitted" and
"unavailable until conversion succeeds" copy, enabled the tab, and jumped to
it after a status-only Ready for Feedback. hasSubmissionHistory() now returns
false for those tasks and the dashboard only jumps when there is history.
The chart is a flex item on the plan page. saveImage sets its full height
before capturing, but a shrinking flex item ignores that height, so the
chart stayed at its on-screen size during the download. Hold it at its full
size with flex none while capturing and restore it afterwards.
The plan page sized the planner box from the row count, and the toolbar
moved inside that box, so its height came out of the chart. With more than a
few tasks the last rows sat behind a scroll area inside the page. The page
now passes the chart height as a variable, the chart takes it, and the
toolbar adds to the page height instead.
The banner moved to the raised surface colour, which is the same white as the
page in light mode, so only a thin border marked demo mode. Tint the fill from
the warning colour so the banner reads as a warning strip in both themes.
The profile form painted the account card and the sticky save bar in fixed
light greys and white without setting a text colour. In dark theme the heading
and the username, student ID and email values inherited the light text colour
on the light card, and the save bar's label disappeared on its white strip.
Use the theme surface, divider, muted, success and error tokens instead.
…e label

The type label and separator under each notification used a fixed black at 60%,
which is unreadable on the dark menu panel. Both copies of the shared row style
now use the muted text token, as the time beside them already did.
The merge kept both hover rules for the legend buttons. The unconditional one
stays applied after a tap on a phone and covers the hidden look of a series
just switched off. Keep only the pointer media query, with the theme hover
colour.
Between the phone breakpoint and a wide screen the toolbar holds two toggles
and up to three buttons in one row that could not wrap, so the actions were
squeezed or ran off the edge. Let the toolbar and both groups wrap.
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings #281 to the files this PR
already carries, so every file stays in exactly one PR. It carries the
burndown hover crosshair and shared tooltip, and the clearer peer
progress card.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings #281 and
#282 to the files this PR
already carries, so every file stays in exactly one PR. It carries the
submission, extension and portfolio dialog changes and the test clean up
that landed with them. The task spec now uses the shared AppInjector
stub, so it no longer breaks other specs in the same test worker.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings #281 to the files this PR
already carries, so every file stays in exactly one PR. It carries the
upload dialog that confirms the submission in place, and the profile
page cards and save bar.
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings #281 to the files this PR
already carries, so every file stays in exactly one PR. It carries the
contrast fixes for both themes, the new startup splash and the themed
planner Gantt.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings #281 to the files this PR
already carries, so every file stays in exactly one PR. It carries the
shared layout, extension and profile fixes, and the initials fallback in
the user icon.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
Clupai8o0 pushed a commit that referenced this pull request Sep 27, 2026
Brings #281 to the files this PR
already carries, so every file stays in exactly one PR. It carries the
task status guide, announcement read tracking, session timing and simple
markdown in the hub.

Co-authored-by: Maple Fox <s223932052@deakin.edu.au>
This was referenced Sep 27, 2026
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.

3 participants