Repository navigation
feat: land integration/t2-2026 (panels, notification links, a11y, Unit Hub rework) - #281
Merged
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
This lands
integration/t2-2026on11.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 of11.0.x@192078b93(with #280, the redesign) andintegration/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
localStoragein the test setup on newer Node.11.0.xset the shared slide toggle defaults inside the calendar modal, so toggles across the app could stop responding. It now only changes its own toggle, and still listens fortoggleChange.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:
#798698(3:1 on raised cards).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.11.0.xversions.EmptyStateComponenthad dropped out of the module in the merge. It is added back.Built against
land/integration-t2-2026@5f21dfd4f, on11.0.x@192078b93land/notification-links@11e800b4(api#178), on11.0.x@5de649d711.0.x@1ca0413Testing
On Node 22 at
b8a936725:npm run test:ci: exit 0, 290 files, 2602 tests passednpm run lint: exit 0, no warningsnpm run typecheck: exit 0npx ng build: exit 05f21dfd4fonly fixes one spec.additional-email-verification-callback.spec.tsfailed in CI because another spec in the same worker lefthistory.stateset, so it now resets it before each test. CI is green on it.Known open items
11.0.x's users api does not accept them.