diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000..af8ec52c7f
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,14 @@
+# Optional values for development through the legacy root docker-compose.yml.
+# Copy this file to .env. Database authentication remains the safe default.
+# Never commit an institution credential or a populated .env file.
+DF_AUTH_METHOD=database
+
+# To exercise AAF locally, obtain a dedicated non-production registration from
+# the identity owner, change DF_AUTH_METHOD to aaf, and fill every field below.
+DF_AAF_ISSUER_URL=
+DF_AAF_AUDIENCE_URL=http://localhost:4200
+DF_AAF_CALLBACK_URL=http://localhost:4200/api/auth/jwt
+DF_AAF_IDENTITY_PROVIDER_URL=
+DF_AAF_UNIQUE_URL=
+DF_AAF_AUTH_SIGNOUT_URL=
+DF_SECRET_KEY_AAF=
diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 06b5afd845..0000000000
--- a/.eslintignore
+++ /dev/null
@@ -1,4 +0,0 @@
-build/
-coverage/
-dist/
-docs/
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 9bf5e49a86..0000000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,91 +0,0 @@
-{
- "root": true,
- "overrides": [
- {
- "files": ["*.ts"],
- "parserOptions": {
- "project": ["tsconfig.*?.json"],
- "createDefaultProgram": true
- },
- "plugins": ["@typescript-eslint"],
- "extends": [
- "plugin:@angular-eslint/recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:prettier/recommended"
- ],
- "env": {
- "browser": true,
- "jasmine": true
- },
- "rules": {
- "@typescript-eslint/no-unused-vars": [
- "warn",
- {
- "args": "all",
- "argsIgnorePattern": "^_",
- "caughtErrors": "all",
- "caughtErrorsIgnorePattern": "^_",
- "destructuredArrayIgnorePattern": "^_",
- "varsIgnorePattern": "^_",
- "ignoreRestSiblings": true
- }
- ],
- "@angular-eslint/no-empty-lifecycle-method": "warn",
- "@angular-eslint/component-class-suffix": "warn",
- "@angular-eslint/no-output-on-prefix": "warn",
- "@typescript-eslint/no-inferrable-types": "off",
- "@angular-eslint/directive-selector": [
- "warn",
- {
- "type": "attribute",
- "prefix": "f",
- "style": "camelCase"
- }
- ],
- "@angular-eslint/component-selector": [
- "warn",
- {
- "type": "element",
- "prefix": "f",
- "style": "kebab-case"
- }
- ],
- "@typescript-eslint/ban-types": "warn",
- "@typescript-eslint/no-empty-function": "warn",
- "@typescript-eslint/no-inferrable-types": "off",
- "@typescript-eslint/no-this-alias": "warn",
- "no-dupe-class-members": "warn",
- "no-prototype-builtins": "warn",
- "no-unused-vars": "off",
- "no-useless-escape": "warn",
- "no-var": "warn",
- "quotes": [
- "warn",
- "single",
- {
- "allowTemplateLiterals": true
- }
- ],
- "prefer-const": "warn",
- "prettier/prettier": "warn"
- }
- },
- {
- "files": ["*.component.html"],
- "extends": ["plugin:@angular-eslint/template/recommended", "plugin:prettier/recommended"],
- "rules": {
- "max-len": [
- "warn",
- {
- "code": 140
- }
- ],
- "prettier/prettier": "warn"
- }
- },
- {
- "files": ["*.component.ts"],
- "extends": ["plugin:@angular-eslint/template/process-inline-templates"]
- }
- ]
-}
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000000..260dc5e2fd
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,5 @@
+# 8 June 2026: Repository-wide formatting and lint configuration
+26b4962794d16e90587fb179aab966b39459050c
+
+# 17 June 2026: Repository-wide sorting of HTML attributes
+1ffc7ad7b9dc18f22be32bf6313bfde97957dd2f
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..d56abbf304
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto eol=lf
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..1cc4d7d71b
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+# The App key is available only to the default-branch policy job. Keep every
+# workflow and the evaluator itself under lead review.
+/.github/workflows/ @ontrack-features-t2-2026/ontrack-leads
+/.github/review-policy/ @ontrack-features-t2-2026/ontrack-leads
+/.github/CODEOWNERS @ontrack-features-t2-2026/ontrack-leads
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8abca405fb..5c8d9a0ceb 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/review-policy/README.md b/.github/review-policy/README.md
new file mode 100644
index 0000000000..bcf9e76f76
--- /dev/null
+++ b/.github/review-policy/README.md
@@ -0,0 +1,51 @@
+# OnTrack pull-request review policy
+
+The required status context `ontrack/review-policy` passes when the current pull
+request head has either:
+
+- one approval from a current `ontrack-leads` member; or
+- two approvals from distinct current `ontrack-contributors` members.
+
+Approvals from the pull-request author, bots, stale commits, dismissed reviews,
+or reviewers whose latest actionable review requests changes do not count.
+
+## Security model
+
+`ontrack-review-policy-signal.yml` is unprivileged and never checks out pull-request
+code. A completed signal wakes `ontrack-review-policy.yml` through `workflow_run`.
+The evaluator workflow checks out only this directory from the protected default
+branch, then mints a short-lived token for the organization-owned GitHub App.
+
+The App is installed only on the three OnTrack Doubtfire repositories and has:
+
+- organization Members: read;
+- repository Metadata: read (mandatory);
+- repository Pull requests: read; and
+- repository Commit statuses: write.
+
+The App has no contents, workflow, administration, merge, or webhook permission.
+Its private key is held in `ONTRACK_REVIEW_APP_PRIVATE_KEY` in the
+`ontrack-review-policy` environment, which only permits the protected `11.0.x`
+branch. Its numeric App ID is held in `ONTRACK_REVIEW_APP_ID`.
+
+The evaluator reports on the pull-request head commit. GitHub gates on the test merge
+commit whenever that commit carries a status and only falls back to the head when it
+carries none, so reporting on the test merge commit would move the merge gate onto a
+commit that carries none of this repository's other checks. The head is also stable,
+where the test merge commit is recomputed every time the base branch moves.
+A five-minute reconciliation covers team membership and base-branch changes that
+do not emit a pull-request review event. Unchanged results are not republished,
+which avoids GitHub's per-commit status limit.
+
+## Ruleset integration
+
+Keep the native one-overall-approval rule, stale-review dismissal, and conversation
+resolution. Require `ontrack/review-policy` with the OnTrack Review Policy App as
+its expected source. Remove the native `ontrack-leads` required-reviewer entry only
+after the App status has been observed and made required; otherwise GitHub combines
+the native team rules with AND semantics.
+
+Changes to any workflow, the evaluator, or CODEOWNERS should continue to require
+one `ontrack-leads` approval through a path-specific native reviewer rule. This is
+necessary because any default-branch workflow could otherwise reference the App's
+environment secret.
diff --git a/.github/review-policy/evaluate.mjs b/.github/review-policy/evaluate.mjs
new file mode 100644
index 0000000000..f9dfa6fc45
--- /dev/null
+++ b/.github/review-policy/evaluate.mjs
@@ -0,0 +1,529 @@
+import { createSign } from 'node:crypto';
+import { readFile } from 'node:fs/promises';
+import { pathToFileURL } from 'node:url';
+
+const API_VERSION = '2022-11-28';
+const POLICY_CONTEXT = 'ontrack/review-policy';
+const APP_BOT_LOGIN = 'ontrack-review-policy-t2-2026[bot]';
+const PAGE_SIZE = 100;
+const MAX_PAGES = 50;
+const ALLOWED_REPOSITORIES = new Set([
+ 'doubtfire-deploy',
+ 'doubtfire-api',
+ 'doubtfire-web',
+]);
+
+function normalizeLogin(login) {
+ return String(login || '').toLowerCase();
+}
+
+function encodeJson(value) {
+ return Buffer.from(JSON.stringify(value)).toString('base64url');
+}
+
+export function createAppJwt(appId, privateKey, nowSeconds = Math.floor(Date.now() / 1000)) {
+ if (!/^\d+$/.test(String(appId))) {
+ throw new Error('ONTRACK_REVIEW_APP_ID must be a numeric GitHub App ID.');
+ }
+ if (!String(privateKey).includes('PRIVATE KEY')) {
+ throw new Error('ONTRACK_REVIEW_APP_PRIVATE_KEY is missing or invalid.');
+ }
+
+ const header = encodeJson({ alg: 'RS256', typ: 'JWT' });
+ const payload = encodeJson({
+ iat: nowSeconds - 60,
+ exp: nowSeconds + 540,
+ iss: String(appId),
+ });
+ const unsigned = `${header}.${payload}`;
+ const signer = createSign('RSA-SHA256');
+ signer.update(unsigned);
+ signer.end();
+ const signature = signer.sign(privateKey).toString('base64url');
+ return `${unsigned}.${signature}`;
+}
+
+export function approvedReviewers(reviews, headSha, authorLogin) {
+ const author = normalizeLogin(authorLogin);
+ const latestActionableReview = new Map();
+ const actionableStates = new Set(['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED']);
+
+ const ordered = [...reviews].sort((left, right) => {
+ const leftTime = Date.parse(left.submitted_at || 0) || 0;
+ const rightTime = Date.parse(right.submitted_at || 0) || 0;
+ return leftTime - rightTime || Number(left.id || 0) - Number(right.id || 0);
+ });
+
+ for (const review of ordered) {
+ const login = normalizeLogin(review.user?.login);
+ const state = String(review.state || '').toUpperCase();
+ if (!login || login === author || review.user?.type === 'Bot') {
+ continue;
+ }
+ // A comment after an approval does not revoke the approval.
+ if (!actionableStates.has(state)) {
+ continue;
+ }
+ latestActionableReview.set(login, review);
+ }
+
+ return new Set(
+ [...latestActionableReview.entries()]
+ .filter(([, review]) => (
+ String(review.state || '').toUpperCase() === 'APPROVED'
+ && review.commit_id === headSha
+ ))
+ .map(([login]) => login),
+ );
+}
+
+export function evaluatePolicy(approved, leadMembers, contributorMembers) {
+ const leads = new Set([...leadMembers].map(normalizeLogin));
+ const contributors = new Set([...contributorMembers].map(normalizeLogin));
+ let leadApprovals = 0;
+ let contributorApprovals = 0;
+
+ for (const login of approved) {
+ const normalized = normalizeLogin(login);
+ if (leads.has(normalized)) {
+ leadApprovals += 1;
+ }
+ if (contributors.has(normalized)) {
+ contributorApprovals += 1;
+ }
+ }
+
+ return {
+ leadApprovals,
+ contributorApprovals,
+ passes: leadApprovals >= 1 || contributorApprovals >= 2,
+ };
+}
+
+export function pullRequestNumbersFromWorkflowRun(workflowRun) {
+ const numbers = new Set();
+ for (const pullRequest of workflowRun?.pull_requests || []) {
+ const number = Number(pullRequest?.number);
+ if (Number.isSafeInteger(number) && number > 0) {
+ numbers.add(number);
+ }
+ }
+
+ const title = String(workflowRun?.display_title || '');
+ const titleMatch = title.match(/\bPR #([1-9]\d{0,9})\b/);
+ if (titleMatch) {
+ const number = Number(titleMatch[1]);
+ if (Number.isSafeInteger(number)) {
+ numbers.add(number);
+ }
+ }
+ return [...numbers];
+}
+
+function safeError(error) {
+ return String(error?.message || error || 'Unknown error')
+ .replace(/gh[opsu]_[A-Za-z0-9_]+/g, '[redacted token]')
+ .replace(
+ /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/g,
+ '[redacted private key]',
+ )
+ .slice(0, 500);
+}
+
+function repositoryParts(repository) {
+ const match = String(repository || '').match(/^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/);
+ if (!match) {
+ throw new Error('GITHUB_REPOSITORY is invalid.');
+ }
+ return { owner: match[1], repo: match[2] };
+}
+
+class GitHubApi {
+ constructor(apiUrl, token) {
+ this.apiUrl = String(apiUrl || 'https://api.github.com').replace(/\/$/, '');
+ this.token = token;
+ }
+
+ async request(path, { method = 'GET', body, expected = [200] } = {}) {
+ const response = await fetch(`${this.apiUrl}${path}`, {
+ method,
+ headers: {
+ Accept: 'application/vnd.github+json',
+ Authorization: `Bearer ${this.token}`,
+ 'User-Agent': 'ontrack-review-policy',
+ 'X-GitHub-Api-Version': API_VERSION,
+ },
+ body: body === undefined ? undefined : JSON.stringify(body),
+ });
+
+ if (!expected.includes(response.status)) {
+ const requestId = response.headers.get('x-github-request-id');
+ throw new Error(
+ `GitHub API ${method} ${path} returned ${response.status}`
+ + (requestId ? ` (request ${requestId})` : ''),
+ );
+ }
+
+ if (response.status === 204) {
+ return null;
+ }
+ const text = await response.text();
+ return text ? JSON.parse(text) : null;
+ }
+
+ async paginate(path) {
+ const items = [];
+ const separator = path.includes('?') ? '&' : '?';
+ for (let page = 1; page <= MAX_PAGES; page += 1) {
+ const batch = await this.request(
+ `${path}${separator}per_page=${PAGE_SIZE}&page=${page}`,
+ );
+ if (!Array.isArray(batch)) {
+ throw new Error(`Expected a list from GitHub API path ${path}.`);
+ }
+ items.push(...batch);
+ if (batch.length < PAGE_SIZE) {
+ return items;
+ }
+ }
+ throw new Error(`GitHub API pagination exceeded ${MAX_PAGES} pages for ${path}.`);
+ }
+}
+
+async function mintInstallationToken({ apiUrl, owner, repo, appId, privateKey }) {
+ const appJwt = createAppJwt(appId, privateKey);
+ const appApi = new GitHubApi(apiUrl, appJwt);
+ const installation = await appApi.request(
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/installation`,
+ );
+ const tokenResponse = await appApi.request(
+ `/app/installations/${installation.id}/access_tokens`,
+ {
+ method: 'POST',
+ expected: [201],
+ body: {
+ repositories: [repo],
+ permissions: {
+ members: 'read',
+ pull_requests: 'read',
+ statuses: 'write',
+ },
+ },
+ },
+ );
+
+ if (!tokenResponse?.token) {
+ throw new Error('GitHub did not return an installation access token.');
+ }
+ // Generated tokens are not repository secrets, so mask them explicitly.
+ console.log(`::add-mask::${tokenResponse.token}`);
+ return tokenResponse.token;
+}
+
+async function teamMembers(api, owner, teamSlug) {
+ const members = await api.paginate(
+ `/orgs/${encodeURIComponent(owner)}/teams/${encodeURIComponent(teamSlug)}/members`,
+ );
+ return new Set(members.map((member) => normalizeLogin(member.login)));
+}
+
+async function openPullRequests(api, owner, repo) {
+ return api.paginate(
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls?state=open`,
+ );
+}
+
+async function pullRequest(api, owner, repo, number) {
+ return api.request(
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${number}`,
+ );
+}
+
+async function reviewsForPullRequest(api, owner, repo, number) {
+ return api.paginate(
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${number}/reviews`,
+ );
+}
+
+function runUrl(repository, runId) {
+ return `https://github.com/${repository}/actions/runs/${runId}`;
+}
+
+async function setPolicyStatus(api, owner, repo, sha, state, description, targetUrl) {
+ if (!/^[0-9a-f]{40}$/i.test(String(sha || ''))) {
+ throw new Error('Cannot publish the review policy without a valid commit SHA.');
+ }
+ const clippedDescription = description.slice(0, 140);
+ try {
+ const statuses = await api.paginate(
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
+ + `/commits/${encodeURIComponent(sha)}/statuses`,
+ );
+ const latest = statuses.find((status) => (
+ status.context === POLICY_CONTEXT
+ && normalizeLogin(status.creator?.login) === APP_BOT_LOGIN
+ ));
+ if (latest?.state === state && latest?.description === clippedDescription) {
+ return false;
+ }
+ } catch (error) {
+ // Deduplication is only an optimization. Always attempt the fail-closed write
+ // when status history cannot be read but the status endpoint may still work.
+ console.warn(`::warning::Status deduplication failed: ${safeError(error)}`);
+ }
+
+ await api.request(
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/statuses/${sha}`,
+ {
+ method: 'POST',
+ expected: [201],
+ body: {
+ state,
+ context: POLICY_CONTEXT,
+ description: clippedDescription,
+ target_url: targetUrl,
+ },
+ },
+ );
+ return true;
+}
+
+export { setPolicyStatus };
+
+// Report on the pull-request head. GitHub gates on the test merge commit whenever that
+// commit carries a status and only falls back to the head when it carries none, and the
+// test merge commit carries none of this repository's checks. Reporting there would move
+// the whole merge gate onto a commit that CI never sees. The head is also stable while
+// the test merge commit is recomputed every time the base branch moves.
+export function statusShaForPullRequest(pullRequestToReport) {
+ return pullRequestToReport.head?.sha;
+}
+
+function reviewDigest(reviews) {
+ return reviews
+ .map((review) => (
+ `${review.id}:${review.state}:${review.commit_id}:`
+ + `${review.submitted_at}:${review.user?.id}:${review.user?.login}`
+ ))
+ .sort()
+ .join('|');
+}
+
+function samePullRequestVersion(left, right) {
+ return (
+ left.state === right.state
+ && left.draft === right.draft
+ && left.head?.sha === right.head?.sha
+ && left.base?.ref === right.base?.ref
+ && left.base?.sha === right.base?.sha
+ && left.mergeable === right.mergeable
+ );
+}
+
+async function evaluatePullRequest({
+ api,
+ owner,
+ repo,
+ pullRequest: current,
+ leads,
+ contributors,
+ targetUrl,
+ attempt = 0,
+}) {
+ // Re-fetch before evaluating so a delayed workflow_run never trusts its event's
+ // old head or merge SHA.
+ current = await pullRequest(api, owner, repo, current.number);
+ if (current.state !== 'open') {
+ return;
+ }
+
+ const statusSha = statusShaForPullRequest(current);
+ if (current.draft) {
+ await setPolicyStatus(
+ api,
+ owner,
+ repo,
+ statusSha,
+ 'pending',
+ 'Waiting for the pull request to be marked ready for review',
+ targetUrl,
+ );
+ console.log(`PR #${current.number}: draft`);
+ return;
+ }
+
+ const firstReviews = await reviewsForPullRequest(api, owner, repo, current.number);
+ const checked = await pullRequest(api, owner, repo, current.number);
+ const secondReviews = await reviewsForPullRequest(api, owner, repo, current.number);
+ const live = await pullRequest(api, owner, repo, current.number);
+ if (checked.state !== 'open' || live.state !== 'open') {
+ return;
+ }
+ const liveStatusSha = statusShaForPullRequest(live);
+ if (
+ !samePullRequestVersion(current, checked)
+ || !samePullRequestVersion(checked, live)
+ || reviewDigest(firstReviews) !== reviewDigest(secondReviews)
+ || liveStatusSha !== statusSha
+ ) {
+ if (attempt >= 1) {
+ throw new Error('Pull request changed repeatedly during evaluation.');
+ }
+ console.log(`PR #${current.number}: changed during evaluation; retrying once`);
+ return evaluatePullRequest({
+ api,
+ owner,
+ repo,
+ pullRequest: live,
+ leads,
+ contributors,
+ targetUrl,
+ attempt: attempt + 1,
+ });
+ }
+
+ const approved = approvedReviewers(
+ secondReviews,
+ live.head.sha,
+ live.user?.login,
+ );
+ const result = evaluatePolicy(approved, leads, contributors);
+ const state = result.passes ? 'success' : 'pending';
+ const description = result.passes
+ ? `Passed: ${result.leadApprovals}/1 lead or ${result.contributorApprovals}/2 contributors`
+ : `Waiting: ${result.leadApprovals}/1 lead or ${result.contributorApprovals}/2 contributors`;
+
+ await setPolicyStatus(api, owner, repo, liveStatusSha, state, description, targetUrl);
+ console.log(
+ `PR #${current.number}: lead=${result.leadApprovals}, `
+ + `contributors=${result.contributorApprovals}, status=${state}`,
+ );
+}
+
+async function eventPayload() {
+ const payloadPath = process.env.GITHUB_EVENT_PATH;
+ if (!payloadPath) {
+ return {};
+ }
+ return JSON.parse(await readFile(payloadPath, 'utf8'));
+}
+
+async function pullRequestsToEvaluate(api, owner, repo, eventName, payload) {
+ const open = await openPullRequests(api, owner, repo);
+ if (eventName === 'workflow_run') {
+ // Treat workflow_run fields only as untrusted locators. Match them against
+ // live open PRs fetched with the App token before evaluating anything.
+ const numbers = new Set(pullRequestNumbersFromWorkflowRun(payload.workflow_run));
+ const headSha = payload.workflow_run?.head_sha;
+ const linked = open.filter((candidate) => (
+ numbers.has(candidate.number)
+ || candidate.head?.sha === headSha
+ || candidate.merge_commit_sha === headSha
+ ));
+ return linked.length > 0 ? linked : open;
+ }
+ return open;
+}
+
+export async function main() {
+ const repository = process.env.GITHUB_REPOSITORY;
+ const { owner, repo } = repositoryParts(repository);
+ if (owner !== 'ontrack-features-t2-2026' || !ALLOWED_REPOSITORIES.has(repo)) {
+ throw new Error('This evaluator only runs for the three approved OnTrack repositories.');
+ }
+
+ const appId = process.env.ONTRACK_REVIEW_APP_ID;
+ const privateKey = process.env.ONTRACK_REVIEW_APP_PRIVATE_KEY;
+ const apiUrl = process.env.GITHUB_API_URL || 'https://api.github.com';
+ const token = await mintInstallationToken({
+ apiUrl,
+ owner,
+ repo,
+ appId,
+ privateKey,
+ });
+ const api = new GitHubApi(apiUrl, token);
+ const payload = await eventPayload();
+ const eventName = process.env.GITHUB_EVENT_NAME || '';
+ const pullRequests = await pullRequestsToEvaluate(api, owner, repo, eventName, payload);
+
+ if (pullRequests.length === 0) {
+ console.log('No open pull requests require review-policy evaluation.');
+ return;
+ }
+
+ const leadTeam = process.env.ONTRACK_LEAD_TEAM || 'ontrack-leads';
+ const contributorTeam = process.env.ONTRACK_CONTRIBUTOR_TEAM || 'ontrack-contributors';
+ let leads;
+ let contributors;
+ try {
+ [leads, contributors] = await Promise.all([
+ teamMembers(api, owner, leadTeam),
+ teamMembers(api, owner, contributorTeam),
+ ]);
+ } catch (error) {
+ const targetUrl = runUrl(repository, process.env.GITHUB_RUN_ID);
+ for (const current of pullRequests) {
+ try {
+ const sha = statusShaForPullRequest(current);
+ await setPolicyStatus(
+ api,
+ owner,
+ repo,
+ sha,
+ 'error',
+ 'OnTrack team membership could not be verified',
+ targetUrl,
+ );
+ } catch (statusError) {
+ console.error(`::error::${safeError(statusError)}`);
+ }
+ }
+ throw error;
+ }
+
+ const targetUrl = runUrl(repository, process.env.GITHUB_RUN_ID);
+ const failures = [];
+ for (const current of pullRequests) {
+ try {
+ await evaluatePullRequest({
+ api,
+ owner,
+ repo,
+ pullRequest: current,
+ leads,
+ contributors,
+ targetUrl,
+ });
+ } catch (error) {
+ failures.push(error);
+ try {
+ const sha = statusShaForPullRequest(current);
+ await setPolicyStatus(
+ api,
+ owner,
+ repo,
+ sha,
+ 'error',
+ 'OnTrack review policy evaluation failed',
+ targetUrl,
+ );
+ } catch (statusError) {
+ console.error(`::error::${safeError(statusError)}`);
+ }
+ console.error(`::error::PR #${current.number}: ${safeError(error)}`);
+ }
+ }
+
+ if (failures.length > 0) {
+ throw new Error(`${failures.length} pull-request evaluation(s) failed.`);
+ }
+}
+
+const invokedPath = process.argv[1] ? pathToFileURL(process.argv[1]).href : '';
+if (import.meta.url === invokedPath) {
+ main().catch((error) => {
+ console.error(`::error::${safeError(error)}`);
+ process.exitCode = 1;
+ });
+}
diff --git a/.github/review-policy/evaluate.test.mjs b/.github/review-policy/evaluate.test.mjs
new file mode 100644
index 0000000000..0c093524a5
--- /dev/null
+++ b/.github/review-policy/evaluate.test.mjs
@@ -0,0 +1,236 @@
+import assert from 'node:assert/strict';
+import { generateKeyPairSync, verify } from 'node:crypto';
+import { test } from 'node:test';
+
+import {
+ approvedReviewers,
+ createAppJwt,
+ evaluatePolicy,
+ pullRequestNumbersFromWorkflowRun,
+ setPolicyStatus,
+ statusShaForPullRequest,
+} from './evaluate.mjs';
+
+function review({
+ id,
+ login,
+ state = 'APPROVED',
+ commit = 'head',
+ submitted = `2026-08-24T00:00:${String(id).padStart(2, '0')}Z`,
+ type = 'User',
+}) {
+ return {
+ id,
+ state,
+ commit_id: commit,
+ submitted_at: submitted,
+ user: { login, type },
+ };
+}
+
+test('one lead approval passes', () => {
+ const result = evaluatePolicy(
+ new Set(['lead']),
+ new Set(['lead']),
+ new Set(['lead', 'contributor']),
+ );
+ assert.equal(result.passes, true);
+ assert.equal(result.leadApprovals, 1);
+});
+
+test('two distinct contributor approvals pass', () => {
+ const result = evaluatePolicy(
+ new Set(['contributor-a', 'contributor-b']),
+ new Set(['lead']),
+ new Set(['lead', 'contributor-a', 'contributor-b']),
+ );
+ assert.equal(result.passes, true);
+ assert.equal(result.contributorApprovals, 2);
+});
+
+test('one contributor approval does not pass', () => {
+ const result = evaluatePolicy(
+ new Set(['contributor-a']),
+ new Set(['lead']),
+ new Set(['lead', 'contributor-a']),
+ );
+ assert.equal(result.passes, false);
+});
+
+test('duplicate approvals from one reviewer count once', () => {
+ const approved = approvedReviewers([
+ review({ id: 1, login: 'Contributor-A' }),
+ review({ id: 2, login: 'contributor-a' }),
+ ], 'head', 'author');
+ assert.deepEqual([...approved], ['contributor-a']);
+});
+
+test('a later comment does not revoke an approval', () => {
+ const approved = approvedReviewers([
+ review({ id: 1, login: 'contributor-a' }),
+ review({ id: 2, login: 'contributor-a', state: 'COMMENTED' }),
+ ], 'head', 'author');
+ assert.deepEqual([...approved], ['contributor-a']);
+});
+
+test('a later changes-requested review revokes an approval', () => {
+ const approved = approvedReviewers([
+ review({ id: 1, login: 'contributor-a' }),
+ review({ id: 2, login: 'contributor-a', state: 'CHANGES_REQUESTED' }),
+ ], 'head', 'author');
+ assert.deepEqual([...approved], []);
+});
+
+test('stale, author, and bot approvals are ignored', () => {
+ const approved = approvedReviewers([
+ review({ id: 1, login: 'stale', commit: 'old-head' }),
+ review({ id: 2, login: 'author' }),
+ review({ id: 3, login: 'review-bot[bot]', type: 'Bot' }),
+ ], 'head', 'author');
+ assert.deepEqual([...approved], []);
+});
+
+test('workflow run PR numbers are deduplicated and validated', () => {
+ assert.deepEqual(
+ pullRequestNumbersFromWorkflowRun({
+ display_title: 'OnTrack review policy signal for PR #42',
+ pull_requests: [{ number: 42 }, { number: 17 }, { number: 0 }],
+ }),
+ [42, 17],
+ );
+});
+
+test('workflow run ignores unsafe or implausibly large PR numbers', () => {
+ assert.deepEqual(
+ pullRequestNumbersFromWorkflowRun({
+ display_title: 'OnTrack review policy signal for PR #12345678901',
+ pull_requests: [{ number: Number.MAX_SAFE_INTEGER + 1 }, { number: -4 }],
+ }),
+ [],
+ );
+});
+
+test('GitHub App JWT has a valid RSA signature and bounded lifetime', () => {
+ const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
+ const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
+ const now = 1_800_000_000;
+ const jwt = createAppJwt('4699573', privateKeyPem, now);
+ const [header, payload, signature] = jwt.split('.');
+ assert.equal(
+ verify(
+ 'RSA-SHA256',
+ Buffer.from(`${header}.${payload}`),
+ publicKey,
+ Buffer.from(signature, 'base64url'),
+ ),
+ true,
+ );
+ const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
+ assert.equal(claims.iss, '4699573');
+ assert.equal(claims.iat, now - 60);
+ assert.equal(claims.exp, now + 540);
+});
+
+test('unchanged App status is not republished and spoofed sources are ignored', async () => {
+ const posts = [];
+ const api = {
+ async paginate() {
+ return [
+ {
+ context: 'ontrack/review-policy',
+ state: 'success',
+ description: 'Passed',
+ creator: { login: 'not-the-policy-app[bot]' },
+ },
+ {
+ context: 'ontrack/review-policy',
+ state: 'pending',
+ description: 'Waiting',
+ creator: { login: 'ontrack-review-policy-t2-2026[bot]' },
+ },
+ ];
+ },
+ async request(path, options) {
+ posts.push({ path, options });
+ return {};
+ },
+ };
+
+ assert.equal(
+ await setPolicyStatus(
+ api,
+ 'owner',
+ 'repo',
+ 'a'.repeat(40),
+ 'pending',
+ 'Waiting',
+ 'https://example.test/run',
+ ),
+ false,
+ );
+ assert.equal(posts.length, 0);
+
+ assert.equal(
+ await setPolicyStatus(
+ api,
+ 'owner',
+ 'repo',
+ 'a'.repeat(40),
+ 'success',
+ 'Passed',
+ 'https://example.test/run',
+ ),
+ true,
+ );
+ assert.equal(posts.length, 1);
+});
+
+test('status-history failure does not suppress a fail-closed write', async () => {
+ const posts = [];
+ const warnings = [];
+ const originalWarn = console.warn;
+ console.warn = (message) => warnings.push(message);
+ const api = {
+ async paginate() {
+ throw new Error('history unavailable');
+ },
+ async request(path, options) {
+ posts.push({ path, options });
+ return {};
+ },
+ };
+
+ try {
+ assert.equal(
+ await setPolicyStatus(
+ api,
+ 'owner',
+ 'repo',
+ 'b'.repeat(40),
+ 'error',
+ 'Evaluation failed',
+ 'https://example.test/run',
+ ),
+ true,
+ );
+ assert.equal(posts.length, 1);
+ assert.equal(posts[0].options.body.state, 'error');
+ assert.equal(warnings.length, 1);
+ } finally {
+ console.warn = originalWarn;
+ }
+});
+
+test('the status is reported on the head, never on the test merge commit', () => {
+ assert.equal(
+ statusShaForPullRequest({ head: { sha: 'head' }, merge_commit_sha: 'test-merge' }),
+ 'head',
+ );
+});
+
+test('a conflicting pull request with no test merge commit still reports on its head', () => {
+ assert.equal(
+ statusShaForPullRequest({ head: { sha: 'head' }, merge_commit_sha: null }),
+ 'head',
+ );
+});
diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml
new file mode 100644
index 0000000000..1bde0c0d6e
--- /dev/null
+++ b/.github/workflows/actionlint.yml
@@ -0,0 +1,25 @@
+name: Lint workflows
+on:
+ pull_request:
+ paths: [".github/workflows/**"]
+ push:
+ paths: [".github/workflows/**"]
+permissions:
+ contents: read
+jobs:
+ actionlint:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
+ - name: Install and run actionlint
+ env:
+ ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
+ ACTIONLINT_VERSION: 1.7.12
+ run: |
+ curl --fail --location --silent --show-error \
+ "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
+ --output actionlint.tar.gz
+ echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum --check
+ tar -xzf actionlint.tar.gz actionlint
+ ./actionlint -color
diff --git a/.github/workflows/deployment-institution.yml b/.github/workflows/deployment-institution.yml
new file mode 100644
index 0000000000..d326026b90
--- /dev/null
+++ b/.github/workflows/deployment-institution.yml
@@ -0,0 +1,76 @@
+name: create-institution-deployment
+on:
+ push:
+ tags:
+ - 'v*'
+ workflow_dispatch:
+permissions:
+ contents: read
+
+jobs:
+ docker-web-server:
+ if: github.repository_owner == 'doubtfire-lms'
+ environment: deployment-secrets
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
+ with:
+ submodules: recursive
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
+ - name: Check custom institution deployment secrets
+ id: custom_config
+ env:
+ INSTITUTION_DOCKERHUB_USERNAME: ${{ secrets.INSTITUTION_DOCKERHUB_USERNAME }}
+ INSTITUTION_DOCKERHUB_TOKEN: ${{ secrets.INSTITUTION_DOCKERHUB_TOKEN }}
+ INSTITUTION: ${{ secrets.INSTITUTION }}
+ run: |
+ if [ -n "$INSTITUTION_DOCKERHUB_USERNAME" ] && [ -n "$INSTITUTION_DOCKERHUB_TOKEN" ] && [ -n "$INSTITUTION" ]; then
+ echo "enabled=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "enabled=false" >> "$GITHUB_OUTPUT"
+ echo "Skipping custom institution Docker publish because one or more institution secrets are not set."
+ fi
+ - name: Login to DockerHub
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
+ if: github.event_name != 'pull_request' && steps.custom_config.outputs.enabled == 'true'
+ with:
+ username: ${{ secrets.INSTITUTION_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.INSTITUTION_DOCKERHUB_TOKEN }}
+ - name: Setup meta for custom institution web server
+ id: docker_meta
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
+ if: steps.custom_config.outputs.enabled == 'true'
+ with:
+ images: ${{ secrets.INSTITUTION_DOCKERHUB_USERNAME }}/${{ secrets.INSTITUTION }}
+ tags: |
+ type=ref,event=tag
+ type=ref,event=branch
+ type=semver,pattern=prod-{{version}}
+ type=semver,pattern=prod-{{major}}.{{minor}}
+ type=semver,pattern=prod-{{major}}
+ - name: Build and push custom web server
+ id: docker_build
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
+ if: steps.custom_config.outputs.enabled == 'true'
+ with:
+ file: deploy.Dockerfile
+ context: .
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.docker_meta.outputs.tags }}
+ labels: ${{ steps.docker_meta.outputs.labels }}
+ sbom: true
+ provenance: mode=max
+ build-args: |
+ SENTRY_DSN=${{ secrets.SENTRY_DSN }}
+ SENTRY_ORG=${{ secrets.SENTRY_ORG }}
+ SENTRY_PROJECT=${{ secrets.SENTRY_PROJECT }}
+ SENTRY_RELEASE=${{ github.ref_name }}
+ SENTRY_DIST=${{ github.run_number }}
+ UPLOAD_SENTRY_SOURCEMAPS=true
+ secrets: |
+ sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
+ - name: Image digest
+ if: steps.custom_config.outputs.enabled == 'true'
+ run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml
index 124fe4c2ae..507dfb6c2a 100644
--- a/.github/workflows/deployment.yml
+++ b/.github/workflows/deployment.yml
@@ -10,56 +10,60 @@ on:
# deployment:
# workflow_dispatch:
jobs:
- docker-deploy-development-image:
- if: github.repository_owner == 'doubtfire-lms'
- environment: deployment-secrets
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Login to DockerHub
- uses: docker/login-action@v3
- if: github.event_name != 'pull_request'
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Setup meta for development image
- id: docker_meta
- uses: docker/metadata-action@v5
- with:
- images: lmsdoubtfire/doubtfire-web
- tags: |
- type=semver,pattern={{major}}.{{minor}}.x-dev
- - name: Build and push web server
- id: docker_build
- uses: docker/build-push-action@v5
- with:
- context: .
- push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.docker_meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- - name: Image digest
- run: echo ${{ steps.docker_build.outputs.digest }}
+ # docker-deploy-development-image:
+ # if: github.repository_owner == 'doubtfire-lms'
+ # environment: deployment-secrets
+ # runs-on: ubuntu-latest
+ # steps:
+ # - name: Checkout code
+ # uses: actions/checkout@v4
+ # with:
+ # submodules: recursive
+ # - name: Set up Docker Buildx
+ # uses: docker/setup-buildx-action@v3
+ # - name: Login to DockerHub
+ # uses: docker/login-action@v3
+ # if: github.event_name != 'pull_request'
+ # with:
+ # username: ${{ secrets.DOCKERHUB_USERNAME }}
+ # password: ${{ secrets.DOCKERHUB_TOKEN }}
+ # - name: Setup meta for development image
+ # id: docker_meta
+ # uses: docker/metadata-action@v5
+ # with:
+ # images: lmsdoubtfire/doubtfire-web
+ # tags: |
+ # type=semver,pattern={{major}}.{{minor}}.x-dev
+ # - name: Build and push web server
+ # id: docker_build
+ # uses: docker/build-push-action@v5
+ # with:
+ # context: .
+ # push: ${{ github.event_name != 'pull_request' }}
+ # tags: ${{ steps.docker_meta.outputs.tags }}
+ # labels: ${{ steps.docker_meta.outputs.labels }}
+ # - name: Image digest
+ # run: echo ${{ steps.docker_build.outputs.digest }}
docker-web-server:
if: github.repository_owner == 'doubtfire-lms'
environment: deployment-secrets
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
+ with:
+ submodules: recursive
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- name: Login to DockerHub
- uses: docker/login-action@v3
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
if: github.event_name != 'pull_request'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup meta for web server
id: docker_meta
- uses: docker/metadata-action@v5
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: lmsdoubtfire/doubtfire-web
tags: |
@@ -70,12 +74,14 @@ jobs:
type=semver,pattern=prod-{{major}}
- name: Build and push web server
id: docker_build
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
file: deploy.Dockerfile
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
+ labels: ${{ steps.docker_meta.outputs.labels }}
+ sbom: true
+ provenance: mode=max
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000000..053acc04a6
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,23 @@
+name: Lint CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [22]
+
+ steps:
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: npm ci
+ - run: npm run lint
+ - run: npm run typecheck
diff --git a/.github/workflows/nodejs-ci.yml b/.github/workflows/nodejs-ci.yml
index 3ef6877d51..dd2f6e9990 100644
--- a/.github/workflows/nodejs-ci.yml
+++ b/.github/workflows/nodejs-ci.yml
@@ -13,16 +13,16 @@ jobs:
strategy:
matrix:
- node-version: [20]
+ node-version: [22]
steps:
- - uses: actions/checkout@v4
- - uses: browser-actions/setup-chrome@latest
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
+ with:
+ submodules: recursive
+ - uses: browser-actions/setup-chrome@48ad923757ca74d66703209fe939badbdf80f2f4 # v2
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
node-version: ${{ matrix.node-version }}
- - run: npm install -g @angular/cli
- run: npm ci
- # - run: npm run lint
- # - run: npm run test:ci
+ - run: npm run verify:deployment-config
- run: npm run build --if-present
diff --git a/.github/workflows/notify-teams-pr.yml b/.github/workflows/notify-teams-pr.yml
new file mode 100644
index 0000000000..164a85b240
--- /dev/null
+++ b/.github/workflows/notify-teams-pr.yml
@@ -0,0 +1,260 @@
+# NOTE ON THE BASE BRANCH. GitHub loads a `pull_request_target` workflow from the
+# repository DEFAULT branch (11.0.x), not from the pull request's base branch. That
+# changed on 2025-12-08. So this file is merged into 11.0.x deliberately, against the
+# usual CONTRIBUTING rule, and a copy on feature/notifications would be dead code.
+# It fires for pull requests into every base branch, which is what we want.
+name: Notify Teams when a pull request opens
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - ready_for_review
+ # - review_requested # matches "reviewer alerts" most directly, but adds one
+ # # message per requested reviewer. Decide, do not default.
+
+# Removes all GITHUB_TOKEN scopes, which this workflow does not need. It does NOT
+# restrict secrets.* - the webhook is protected by the guards below, not by this.
+permissions: {}
+
+jobs:
+ notify-teams:
+ name: Post pull request notification to Teams
+ # The job guard makes the file inert if it ever travels to thoth-tech or
+ # doubtfire-lms. The step below decides who gets a notification: a pull request
+ # from a branch in this repository always does, and a fork pull request only if
+ # its author is in one of the two OnTrack teams.
+ if: github.repository_owner == 'ontrack-features-t2-2026'
+ runs-on: ubuntu-latest
+ timeout-minutes: 2
+
+ steps:
+ - name: Check pull request author team membership
+ id: team-membership
+ shell: bash
+ env:
+ TEAM_MEMBERSHIP_TOKEN: ${{ secrets.TEAM_MEMBERSHIP_ACCESS }}
+ PR_AUTHOR: ${{ github.event.pull_request.user.login }}
+ PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+ REPOSITORY: ${{ github.repository }}
+ run: |
+ set -euo pipefail
+
+ # A branch in this repository can only be pushed by someone who already
+ # has write access, so those pull requests need no lookup. That is also
+ # the only reason Dependabot notifies: dependabot[bot] is in neither
+ # team and 404s on both. The team check below is for fork pull requests,
+ # which is where the webhook actually needed protecting.
+ if [[ "${PR_HEAD_REPO}" == "${REPOSITORY}" ]]; then
+ echo "eligible=true" >> "${GITHUB_OUTPUT}"
+ exit 0
+ fi
+
+ if [[ -z "${TEAM_MEMBERSHIP_TOKEN:-}" ]]; then
+ echo "::error::The TEAM_MEMBERSHIP_ACCESS secret is not configured."
+ exit 1
+ fi
+
+ # The login goes into a URL path segment, so percent-encode it. curl also
+ # reads [ ] { } * in a URL as a glob and exits before sending anything, so
+ # --globoff below. Between them a login like dependabot[bot] is safe.
+ author_encoded="$(python3 -c 'import os, urllib.parse; print(urllib.parse.quote(os.environ["PR_AUTHOR"], safe=""))')"
+
+ for team in ontrack-contributors ontrack-leads; do
+ http_status="$(
+ curl \
+ --proto '=https' \
+ --tlsv1.2 \
+ --globoff \
+ --silent \
+ --show-error \
+ --connect-timeout 10 \
+ --max-time 30 \
+ --header 'Accept: application/vnd.github+json' \
+ --header "Authorization: Bearer ${TEAM_MEMBERSHIP_TOKEN}" \
+ --header 'X-GitHub-Api-Version: 2022-11-28' \
+ --output "${RUNNER_TEMP}/team-membership.json" \
+ --write-out '%{http_code}' \
+ "https://api.github.com/orgs/ontrack-features-t2-2026/teams/${team}/memberships/${author_encoded}" \
+ )" || http_status="000"
+
+ case "${http_status}" in
+ 200)
+ membership_state="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["state"])' \
+ < "${RUNNER_TEMP}/team-membership.json")"
+ # "pending" is a teammate who has not accepted the org invitation
+ # yet. They are on the team and they review, so notify them too.
+ if [[ "${membership_state}" == "active" || "${membership_state}" == "pending" ]]; then
+ echo "eligible=true" >> "${GITHUB_OUTPUT}"
+ exit 0
+ fi
+ ;;
+ 404) ;;
+ *)
+ echo "::error::GitHub returned HTTP ${http_status} while checking team membership."
+ exit 1
+ ;;
+ esac
+ done
+
+ # A skip is a decision, so say so on the run summary. Without this the job
+ # goes green and looks the same as one that posted a message.
+ echo "::warning::No Teams notification sent. ${PR_AUTHOR} opened this pull request from a fork and is in neither ontrack-contributors nor ontrack-leads."
+ echo "eligible=false" >> "${GITHUB_OUTPUT}"
+
+ - name: Build and send Teams notification
+ if: steps.team-membership.outputs.eligible == 'true'
+ shell: bash
+ env:
+ TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_PR_WEBHOOK_URL }}
+ PAYLOAD_PATH: ${{ runner.temp }}/teams-pr-notification.json
+
+ REPOSITORY: ${{ github.repository }}
+ PR_ACTION: ${{ github.event.action }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ PR_TITLE: ${{ github.event.pull_request.title }}
+ PR_AUTHOR: ${{ github.event.pull_request.user.login }}
+ PR_URL: ${{ github.event.pull_request.html_url }}
+ PR_DRAFT: ${{ github.event.pull_request.draft }}
+ PR_HEAD: ${{ github.event.pull_request.head.label }}
+ PR_BASE: ${{ github.event.pull_request.base.ref }}
+
+ run: |
+ set -euo pipefail
+
+ if [[ -z "${TEAMS_WEBHOOK_URL:-}" ]]; then
+ echo "::error::The TEAMS_PR_WEBHOOK_URL Actions secret is not configured."
+ exit 1
+ fi
+
+ python3 - <<'PY'
+ import json
+ import os
+ import re
+
+
+ def plain(value):
+ """Teams renders these fields as Markdown, and titles and branch names
+ come from anyone who can open a pull request. Removing [ ] < > and
+ backticks stops a title forging a Markdown link or code span.
+ Parentheses are left alone so `feat(scope): ...` still reads
+ properly. Note this does NOT stop Teams auto-linking a bare URL in
+ a title, it only stops the link TEXT being controlled."""
+ value = " ".join(value.split())
+ return re.sub(r"[\[\]<>`]", " ", value)[:200]
+
+
+ headline = {
+ "opened": "New pull request opened",
+ "reopened": "Pull request reopened",
+ "ready_for_review": "Pull request is ready for review",
+ }.get(os.environ["PR_ACTION"], "Pull request updated")
+
+ status = (
+ "Draft"
+ if os.environ.get("PR_DRAFT", "").lower() == "true"
+ else "Ready for review"
+ )
+
+ card = {
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "type": "AdaptiveCard",
+ "version": "1.2",
+ "body": [
+ {
+ "type": "TextBlock",
+ "size": "Medium",
+ "weight": "Bolder",
+ "wrap": True,
+ "text": headline,
+ },
+ {
+ "type": "TextBlock",
+ "wrap": True,
+ "text": f"PR #{os.environ['PR_NUMBER']}: {plain(os.environ['PR_TITLE'])}",
+ },
+ {
+ "type": "FactSet",
+ "facts": [
+ {
+ "title": "Repository",
+ "value": plain(os.environ["REPOSITORY"]),
+ },
+ {
+ "title": "Author",
+ "value": plain(os.environ["PR_AUTHOR"]),
+ },
+ {
+ "title": "Status",
+ "value": status,
+ },
+ {
+ "title": "Branches",
+ "value": (
+ f"{plain(os.environ['PR_HEAD'])} -> "
+ f"{plain(os.environ['PR_BASE'])}"
+ ),
+ },
+ ],
+ },
+ ],
+ "actions": [
+ {
+ "type": "Action.OpenUrl",
+ "title": "Open pull request",
+ "url": os.environ["PR_URL"],
+ }
+ ],
+ }
+
+ payload = {
+ "type": "message",
+ "attachments": [
+ {
+ "contentType": "application/vnd.microsoft.card.adaptive",
+ "contentUrl": None,
+ "content": card,
+ }
+ ],
+ }
+
+ with open(
+ os.environ["PAYLOAD_PATH"],
+ "w",
+ encoding="utf-8",
+ ) as payload_file:
+ json.dump(
+ payload,
+ payload_file,
+ ensure_ascii=False,
+ )
+ PY
+
+ http_status="$(
+ curl \
+ --proto '=https' \
+ --tlsv1.2 \
+ --silent \
+ --show-error \
+ --connect-timeout 10 \
+ --max-time 30 \
+ --header 'Content-Type: application/json' \
+ --data-binary "@${PAYLOAD_PATH}" \
+ --output "${RUNNER_TEMP}/teams-response.txt" \
+ --write-out '%{http_code}' \
+ --url "${TEAMS_WEBHOOK_URL}"
+ )" || http_status="000"
+
+ # Uncomment to debug a failing webhook. The body comes from a third party
+ # and lands in a PUBLIC Actions log, and GitHub only masks an exact
+ # full-value match of a secret. Read it once, then comment it out again.
+ # cat "${RUNNER_TEMP}/teams-response.txt"
+
+ if [[ "${http_status}" != 2* ]]; then
+ echo "::error::Teams webhook returned HTTP ${http_status}."
+ exit 1
+ fi
+
+ echo "Teams webhook returned HTTP ${http_status} for PR #${PR_NUMBER}."
+ echo "That means the request was accepted. It does not prove the message rendered in the channel."
diff --git a/.github/workflows/ontrack-review-policy-signal.yml b/.github/workflows/ontrack-review-policy-signal.yml
new file mode 100644
index 0000000000..d87949cbbc
--- /dev/null
+++ b/.github/workflows/ontrack-review-policy-signal.yml
@@ -0,0 +1,32 @@
+name: OnTrack review policy signal
+run-name: "OnTrack review policy signal for PR #${{ github.event.pull_request.number || 'all' }}"
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ - edited
+ - ready_for_review
+ - converted_to_draft
+ pull_request_review:
+ types:
+ - submitted
+ - edited
+ - dismissed
+ workflow_dispatch:
+
+# This workflow deliberately has no permissions and never checks out pull-request
+# code or secrets. Its completion wakes the trusted evaluator on the default
+# branch; manual runs safely reconcile every open pull request.
+permissions: {}
+
+jobs:
+ signal:
+ if: github.repository_owner == 'ontrack-features-t2-2026'
+ runs-on: ubuntu-24.04
+ timeout-minutes: 1
+ steps:
+ - name: Signal policy evaluation
+ run: ':'
diff --git a/.github/workflows/ontrack-review-policy.yml b/.github/workflows/ontrack-review-policy.yml
new file mode 100644
index 0000000000..61c194b103
--- /dev/null
+++ b/.github/workflows/ontrack-review-policy.yml
@@ -0,0 +1,56 @@
+name: OnTrack review policy evaluator
+
+on:
+ workflow_run:
+ workflows:
+ - OnTrack review policy signal
+ types:
+ - completed
+ push:
+ branches:
+ - 11.0.x
+ schedule:
+ # Reconcile team membership and base-branch changes even when no PR event fires.
+ - cron: '3-58/5 * * * *'
+
+# The repository token is used only to check out the trusted evaluator from the
+# default branch. The GitHub App token is separately scoped to member/PR reads and
+# commit-status writes.
+permissions:
+ contents: read
+
+concurrency:
+ # Serialize signals for the same PR without allowing unrelated PR activity to
+ # replace a queued revocation. Scheduled and base-push reconciliations share a
+ # separate group. Every run re-reads authoritative GitHub state.
+ group: ontrack-review-policy-${{ github.repository }}-${{ github.event.workflow_run.pull_requests[0].number || github.event.workflow_run.display_title || 'reconcile' }}
+ cancel-in-progress: false
+
+jobs:
+ evaluate:
+ if: github.repository_owner == 'ontrack-features-t2-2026'
+ runs-on: ubuntu-24.04
+ timeout-minutes: 5
+ environment:
+ name: ontrack-review-policy
+ deployment: false
+
+ steps:
+ - name: Check out the trusted policy evaluator
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
+ with:
+ ref: refs/heads/11.0.x
+ fetch-depth: 1
+ persist-credentials: false
+ sparse-checkout: .github/review-policy
+
+ - name: Verify policy logic
+ run: node --test .github/review-policy/evaluate.test.mjs
+
+ - name: Evaluate current pull-request approvals
+ env:
+ ONTRACK_REVIEW_APP_ID: ${{ vars.ONTRACK_REVIEW_APP_ID }}
+ ONTRACK_REVIEW_APP_PRIVATE_KEY: ${{ secrets.ONTRACK_REVIEW_APP_PRIVATE_KEY }}
+ ONTRACK_CONTRIBUTOR_TEAM: ontrack-contributors
+ ONTRACK_LEAD_TEAM: ontrack-leads
+ run: node .github/review-policy/evaluate.mjs
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000..61cee19002
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,19 @@
+name: Test CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
+ - name: Use Node.js 22
+ uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run test:ci
diff --git a/.github/workflows/weekly-integration-prs.yml b/.github/workflows/weekly-integration-prs.yml
new file mode 100644
index 0000000000..59eefb3cf4
--- /dev/null
+++ b/.github/workflows/weekly-integration-prs.yml
@@ -0,0 +1,100 @@
+name: Weekly integration PRs
+
+on:
+ schedule:
+ - cron: "17 9 * * 1"
+ timezone: Australia/Melbourne
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ ensure-pr:
+ if: github.repository_owner == 'ontrack-features-t2-2026'
+ name: ${{ matrix.source }} -> ${{ matrix.target }}
+ runs-on: ubuntu-24.04
+ timeout-minutes: 10
+
+ strategy:
+ fail-fast: false
+ max-parallel: 4
+ matrix:
+ include:
+ - source: feature/cross-unit
+ target: 11.0.x
+ - source: feature/notifications
+ target: 11.0.x
+ - source: feature/peer-progress-indicator
+ target: 11.0.x
+ - source: feature/calendar-integration
+ target: 11.0.x
+
+ concurrency:
+ group: ${{ github.workflow }}-${{ matrix.target }}-${{ matrix.source }}
+ cancel-in-progress: false
+
+ steps:
+ - name: Ensure pull request exists
+ shell: bash
+ env:
+ GH_TOKEN: ${{ secrets.INTEGRATION_BOT_TOKEN }}
+ SOURCE_BRANCH: ${{ matrix.source }}
+ TARGET_BRANCH: ${{ matrix.target }}
+ run: |
+ set -euo pipefail
+
+ if [[ -z "${GH_TOKEN:-}" ]]; then
+ echo "::error::INTEGRATION_BOT_TOKEN is not configured."
+ exit 1
+ fi
+
+ existing="$(
+ gh api --method GET "repos/$GITHUB_REPOSITORY/pulls" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2026-03-10" \
+ -f state=open \
+ -f head="${GITHUB_REPOSITORY_OWNER}:${SOURCE_BRANCH}" \
+ -f base="$TARGET_BRANCH" \
+ -F per_page=1 \
+ --jq '.[0].html_url // empty'
+ )"
+
+ if [[ -n "$existing" ]]; then
+ echo "Pull request already open: $existing"
+ exit 0
+ fi
+
+ target_ref="$(jq -rn --arg ref "$TARGET_BRANCH" '$ref | @uri')"
+ source_ref="$(jq -rn --arg ref "$SOURCE_BRANCH" '$ref | @uri')"
+
+ for ref in "$target_ref" "$source_ref"; do
+ if ! gh api "repos/$GITHUB_REPOSITORY/branches/${ref}" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2026-03-10" \
+ --silent >/dev/null 2>&1; then
+ echo "Branch $ref no longer exists in $GITHUB_REPOSITORY; skipping."
+ exit 0
+ fi
+ done
+ comparison="$(
+ gh api "repos/$GITHUB_REPOSITORY/compare/${target_ref}...${source_ref}" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2026-03-10" \
+ --jq '[.ahead_by, (.files | length)] | @tsv'
+ )"
+ read -r ahead changed_files <<< "$comparison"
+
+ if [[ "$ahead" == "0" || "$changed_files" == "0" ]]; then
+ echo "Nothing to merge from $SOURCE_BRANCH into $TARGET_BRANCH."
+ exit 0
+ fi
+
+ gh api --method POST "repos/$GITHUB_REPOSITORY/pulls" \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2026-03-10" \
+ -f title="Integration: $SOURCE_BRANCH into $TARGET_BRANCH" \
+ -f head="$SOURCE_BRANCH" \
+ -f base="$TARGET_BRANCH" \
+ -f body="Created by the scheduled integration workflow. The source branch is intentionally retained after merge." \
+ --jq '.html_url'
diff --git a/.gitignore b/.gitignore
index 9abc43ca67..65909d007d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,10 +11,13 @@ vendor/
.sass-cache*
.bundle*
tmp.scss
-.vscode
.tscache
a.env
+.env
dist/
.angulardoc.json
.nx
.idea
+
+# Sentry Config File
+.sentryclirc
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..f259c41cca
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "JPlag-Report-Viewer"]
+ path = JPlag-Report-Viewer
+ url = https://github.com/doubtfire-lms/jplag-report-viewer-static.git
diff --git a/.husky/commit-msg b/.husky/commit-msg
index fe4c17a22d..70bd3dd23d 100755
--- a/.husky/commit-msg
+++ b/.husky/commit-msg
@@ -1,4 +1 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-npx --no-install commitlint --edit ""
+npx --no-install commitlint --edit "$1"
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 6700f51282..e69de29bb2 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,2 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
diff --git a/.nvmrc b/.nvmrc
index 6276cf12fb..6bb900519d 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v16.14.2
+v22.22.3
diff --git a/.postcssrc.json b/.postcssrc.json
new file mode 100644
index 0000000000..865e00b364
--- /dev/null
+++ b/.postcssrc.json
@@ -0,0 +1,6 @@
+{
+ "syntax": "postcss-scss",
+ "plugins": {
+ "@tailwindcss/postcss": {}
+ }
+}
diff --git a/.prettierignore b/.prettierignore
index 25257e586f..d541041bd2 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,3 +4,12 @@ yarn.lock
dist
build
e2e
+CHANGELOG.md
+JPlag-Report-Viewer/
+.github/
+karma/
+Gruntfile.js
+karma.conf.js
+polyfills.ts
+test.ts
+main.ts
diff --git a/.prettierrc b/.prettierrc
index d73829a932..021153e85a 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,9 +1,15 @@
{
"printWidth": 100,
"tabWidth": 2,
- "tabs": false,
"singleQuote": true,
- "semicolon": true,
+ "semi": true,
"quoteProps": "preserve",
- "bracketSpacing": false
+ "endOfLine": "lf",
+ "trailingComma": "all",
+ "bracketSpacing": false,
+ "plugins": ["@trivago/prettier-plugin-sort-imports"],
+ "importOrder": ["^@angular/(.*)$", "^rxjs$", "^rxjs/(.*)$", "^src/app/(.*)$", "^[./]"],
+ "importOrderSeparation": false,
+ "importOrderSortSpecifiers": true,
+ "importOrderParserPlugins": ["typescript", "decorators-legacy"]
}
diff --git a/.tool-versions b/.tool-versions
index c2ca3d3d25..42bb250e67 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1 +1 @@
-nodejs 20.9.0
+nodejs 22.22.3
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..7e6882bfa3
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "files.eol": "\n"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ca7d64128..52ce354d6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,1840 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+## [11.0.0-45](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-44...v11.0.0-45) (2026-07-13)
+
+
+### Features
+
+* add label for starting tasks ([46100f5](https://github.com/b0ink/doubtfire-deploy/commit/46100f50ca8ddac11668e97deb482994f47f87f9))
+* add task list filters and sorting ([c65279c](https://github.com/b0ink/doubtfire-deploy/commit/c65279cb1900b5198e764d53b671719512486a83))
+* show all tasks in task planner ([eb87251](https://github.com/b0ink/doubtfire-deploy/commit/eb87251dbef37d5cca8d11c29816c42722605764))
+
+
+### Bug Fixes
+
+* dont reset task list sidebar when switching between project and task dashboard ([acc3ac2](https://github.com/b0ink/doubtfire-deploy/commit/acc3ac2625137075262c44239772d121e52c3a34))
+* lint ([dab681d](https://github.com/b0ink/doubtfire-deploy/commit/dab681d0e3d0e025804d3477eb68727ab42330ed))
+* use task weighting system as default sorting ([aa3c261](https://github.com/b0ink/doubtfire-deploy/commit/aa3c2613f9c1027eea761f51297380a4443a42b2))
+
+## [11.0.0-44](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-43...v11.0.0-44) (2026-07-08)
+
+
+### Features
+
+* display submitted grade in portfolio view ([a76cecf](https://github.com/b0ink/doubtfire-deploy/commit/a76cecfe1e4f3a78becf9507900e621e87447d69))
+
+
+### Bug Fixes
+
+* ensure accurate tutor views ([3b5e693](https://github.com/b0ink/doubtfire-deploy/commit/3b5e69397abcc53136942428fb97ac81a5cddb3d))
+* only call d2l mapping as convenor ([d6ecd44](https://github.com/b0ink/doubtfire-deploy/commit/d6ecd44658f6a668dde409d399944f53793c1c35))
+* persist portfolio filters in url ([322eef2](https://github.com/b0ink/doubtfire-deploy/commit/322eef29e57d13267a2466da4bcb4a55a7b49db6))
+* reduce threshold for mobile view in inbox dashboard ([9479351](https://github.com/b0ink/doubtfire-deploy/commit/9479351619479fed2ac26199f77946ac64903e7c))
+
+## [11.0.0-43](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-42...v11.0.0-43) (2026-07-03)
+
+
+### Bug Fixes
+
+* regenerate ngsw cache after sourcemaps removed ([be85d84](https://github.com/b0ink/doubtfire-deploy/commit/be85d8443b56f20a6866c0fc8d65915d5fe63a2a))
+
+## [11.0.0-42](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-41...v11.0.0-42) (2026-07-03)
+
+
+### Features
+
+* collapse task comments in narrow screen sizes ([380f277](https://github.com/b0ink/doubtfire-deploy/commit/380f2773b44d6582b62b13785b90a146a7002123))
+* render ansi output to html ([732aedd](https://github.com/b0ink/doubtfire-deploy/commit/732aedd2414dcfe3d74a78b4b9ca0bc17d4f5521))
+
+
+### Bug Fixes
+
+* add tooltip to collapsed task list item ([c81e61e](https://github.com/b0ink/doubtfire-deploy/commit/c81e61ef48b83f076ba7dac2d039b4bade5c9717))
+* ensure project is valid ([095315b](https://github.com/b0ink/doubtfire-deploy/commit/095315b212fff98cbe661df7a33a7b091cb66b1e))
+
+## [11.0.0-41](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-40...v11.0.0-41) (2026-07-02)
+
+
+### Bug Fixes
+
+* add padding when user cant view submission ([f16d9dc](https://github.com/b0ink/doubtfire-deploy/commit/f16d9dc6ee815e93022f9b8446dd32d1852ce088))
+* avoid repeated unit fetching ([cfe04dc](https://github.com/b0ink/doubtfire-deploy/commit/cfe04dc29f78ab815a17312f929e6deb052a88ed))
+* remove hash prefix ([51794b8](https://github.com/b0ink/doubtfire-deploy/commit/51794b800d2d731d6445cd21b5b592302e43defd))
+* tutorials route transition error ([db1294a](https://github.com/b0ink/doubtfire-deploy/commit/db1294a37e7333612202a8413256288ae97c05aa))
+
+## [11.0.0-40](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-39...v11.0.0-40) (2026-07-01)
+
+
+### Bug Fixes
+
+* reveal overflow task claims to convenors only ([c17a6d2](https://github.com/b0ink/doubtfire-deploy/commit/c17a6d239b71f446300b305de5877372c206028d))
+
+## [11.0.0-39](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-38...v11.0.0-39) (2026-07-01)
+
+
+### Bug Fixes
+
+* dynamic hint sizing to prevent overlap ([518b58b](https://github.com/b0ink/doubtfire-deploy/commit/518b58ba30c23dc4456a98ebfbe41cb48446e715))
+
+## [11.0.0-38](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-37...v11.0.0-38) (2026-06-29)
+
+## [11.0.0-37](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-36...v11.0.0-37) (2026-06-29)
+
+
+### Bug Fixes
+
+* fetch images on init ([b28bc69](https://github.com/b0ink/doubtfire-deploy/commit/b28bc6905b43d5843eee010cefff4938568edb10))
+
+## [11.0.0-36](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-35...v11.0.0-36) (2026-06-29)
+
+
+### Bug Fixes
+
+* modify grey border ([59c102d](https://github.com/b0ink/doubtfire-deploy/commit/59c102d7708e4c28a5584cc3614046e8e8f5fd52))
+
+## [11.0.0-35](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-34...v11.0.0-35) (2026-06-29)
+
+
+### Features
+
+* add skeleton loading ui to overseer image list ([b833a1c](https://github.com/b0ink/doubtfire-deploy/commit/b833a1c58ce07e6bf9b6a12c77a1bbea1635da10))
+* add skeleton ui for activities ([47ffe2b](https://github.com/b0ink/doubtfire-deploy/commit/47ffe2b261d26be39e93e7b89204c65b5d5b1f57))
+
+
+### Bug Fixes
+
+* reveal tii threshold if tii enabled ([ab1a401](https://github.com/b0ink/doubtfire-deploy/commit/ab1a401a3f67db83f7a55aa25060f2a29116eae1))
+* use correct api url ([ed4dba4](https://github.com/b0ink/doubtfire-deploy/commit/ed4dba4552ebf088893dfa58fffac2546690cedd))
+
+## [11.0.0-34](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-33...v11.0.0-34) (2026-06-25)
+
+
+### Bug Fixes
+
+* align icons ([8af5f5a](https://github.com/b0ink/doubtfire-deploy/commit/8af5f5ab177a8057e6b894ae7b039b8b8203c19f))
+* set correct button size ([ec8eef4](https://github.com/b0ink/doubtfire-deploy/commit/ec8eef49ca4275f00502872dab5b0cf23251c337))
+
+## [11.0.0-33](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-32...v11.0.0-33) (2026-06-25)
+
+
+### Bug Fixes
+
+* ensure session replays are captured ([bc35164](https://github.com/b0ink/doubtfire-deploy/commit/bc351643d65104ff85d716cff0e0d405cdb10170))
+
+## [11.0.0-32](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-31...v11.0.0-32) (2026-06-25)
+
+
+### Bug Fixes
+
+* ensure sentry replays work ([b2a8cdd](https://github.com/b0ink/doubtfire-deploy/commit/b2a8cdd2bcb15d409047c346b93136fd2f605b5f))
+
+## [11.0.0-31](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-30...v11.0.0-31) (2026-06-25)
+
+## [11.0.0-30](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-29...v11.0.0-30) (2026-06-25)
+
+
+### Bug Fixes
+
+* tunnel sentry requests ([2d24ebc](https://github.com/b0ink/doubtfire-deploy/commit/2d24ebc9e611dbf7ca0cbdcd8507b238538d9ea3))
+
+## [11.0.0-29](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-28...v11.0.0-29) (2026-06-25)
+
+
+### Features
+
+* sentry ([#1305](https://github.com/b0ink/doubtfire-deploy/issues/1305)) ([865faa2](https://github.com/b0ink/doubtfire-deploy/commit/865faa286016bf61fdb0ae0e300df48e92045bbb))
+
+## [11.0.0-28](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-27...v11.0.0-28) (2026-06-24)
+
+## [11.0.0-27](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-26...v11.0.0-27) (2026-06-24)
+
+
+### Features
+
+* customisable grades ([#1271](https://github.com/b0ink/doubtfire-deploy/issues/1271)) ([c2b68ac](https://github.com/b0ink/doubtfire-deploy/commit/c2b68aca0b5b0873443823795fc50bbcdc81fa4d))
+
+
+### Bug Fixes
+
+* allow empty strings to fix create user dialog ([efb4e5d](https://github.com/b0ink/doubtfire-deploy/commit/efb4e5d97f9641843f3e3e50f1fd38bb560fe853))
+* center div ([1c20dd7](https://github.com/b0ink/doubtfire-deploy/commit/1c20dd73befb5c0e204874140ca494c968487bd3))
+* close dialog after creating teachiing period ([af9ab4d](https://github.com/b0ink/doubtfire-deploy/commit/af9ab4d692f3e4c5e84a10e54a7f63d289a8092b))
+* ensure overseer assessment is valid before expanding ([6cad43c](https://github.com/b0ink/doubtfire-deploy/commit/6cad43c1c6aac6cf7552ed9db937d6a73cb17369))
+* ensure user is authenticated before loading scorm ([1b066c0](https://github.com/b0ink/doubtfire-deploy/commit/1b066c0e08cffc7a1176c7ec7f1b23da4ce6cf3f))
+* extend reply height to avoid text cutoff ([b4af5d1](https://github.com/b0ink/doubtfire-deploy/commit/b4af5d1b64dc32bea17b65c817aeaf56139be0fb))
+* hide quality pts if negative ([e43b96d](https://github.com/b0ink/doubtfire-deploy/commit/e43b96d77f21ebc654b4ca9e0b5a1cfeb5b8058c))
+* only show status icon if set ([46f494c](https://github.com/b0ink/doubtfire-deploy/commit/46f494c926b604f0c1fd396b59678f5ee74a7492))
+* truncate portfolio task list item correctly ([a02fe9e](https://github.com/b0ink/doubtfire-deploy/commit/a02fe9ebe57ae4157b9124d8a8a1c7aea364ba60))
+* update husky hooks for v9 ([5961860](https://github.com/b0ink/doubtfire-deploy/commit/596186040329ee198e1ef703d76676d15ad3ce89))
+* use correct scorm url ([d95c4ca](https://github.com/b0ink/doubtfire-deploy/commit/d95c4ca400a7aee7dd50f816e1b342e3754db87a))
+
+## [11.0.0-26](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-25...v11.0.0-26) (2026-06-19)
+
+
+### Bug Fixes
+
+* avoid converting colons in urls to emojis [#706](https://github.com/b0ink/doubtfire-deploy/issues/706) ([550b867](https://github.com/b0ink/doubtfire-deploy/commit/550b867df0e52026825e594a72d8544c9505a22b))
+* improve global loading logic for staff ([d81ac52](https://github.com/b0ink/doubtfire-deploy/commit/d81ac52cf1b90e139a34bf13ed08142919b6cc53))
+* prevent multiple global fetches ([f1528d1](https://github.com/b0ink/doubtfire-deploy/commit/f1528d1aedc7d6485b094ed3c4923b44c786d45b))
+
+## [11.0.0-25](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-24...v11.0.0-25) (2026-06-18)
+
+
+### Features
+
+* add day of week to gantt chart ([1fb4637](https://github.com/b0ink/doubtfire-deploy/commit/1fb4637cd2386f1aeff227043e0002bf9b77837b))
+* engagement passport ([#1257](https://github.com/b0ink/doubtfire-deploy/issues/1257)) ([7a754dc](https://github.com/b0ink/doubtfire-deploy/commit/7a754dcc82ca228f5688abab4480774df75a007e))
+* show students name if viewing other project ([38c0e85](https://github.com/b0ink/doubtfire-deploy/commit/38c0e85ebe698c71ea087f3aeecc6d7ca24c86d7))
+
+
+### Bug Fixes
+
+* ensure dashboard dropdown switches from task details view ([1ba5d1e](https://github.com/b0ink/doubtfire-deploy/commit/1ba5d1e42d302218520f8be4c0875673ad5d7a3f))
+* web calendar ([#1282](https://github.com/b0ink/doubtfire-deploy/issues/1282)) ([83b6d5e](https://github.com/b0ink/doubtfire-deploy/commit/83b6d5ea381604ae88585195314eafd239880632))
+
+## [11.0.0-24](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-23...v11.0.0-24) (2026-06-17)
+
+
+### Features
+
+* check access token expiry locally before attempting request ([#1270](https://github.com/b0ink/doubtfire-deploy/issues/1270)) ([4ff5562](https://github.com/b0ink/doubtfire-deploy/commit/4ff55627566f9004872c915658bd91bc348dd8e8))
+* submission history ([#1269](https://github.com/b0ink/doubtfire-deploy/issues/1269)) ([4cdae07](https://github.com/b0ink/doubtfire-deploy/commit/4cdae07eb5bef0f9866927892e92b6b80dde0c74))
+* upgrade gantt chart and add screenshotting ability ([#1263](https://github.com/b0ink/doubtfire-deploy/issues/1263)) ([08a8eee](https://github.com/b0ink/doubtfire-deploy/commit/08a8eee9337331aa328960e206722f8905c2a0f9))
+
+
+### Bug Fixes
+
+* use either portfolio available field ([f8d5142](https://github.com/b0ink/doubtfire-deploy/commit/f8d514261c7eca5465ac3af95da33b95a39d3f23))
+
+## [11.0.0-23](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-22...v11.0.0-23) (2026-06-11)
+
+
+### Features
+
+* display list of tasks still being processed ([#1264](https://github.com/b0ink/doubtfire-deploy/issues/1264)) ([55ef4c0](https://github.com/b0ink/doubtfire-deploy/commit/55ef4c0409cf5a066e91763a00f2bb428670e520))
+
+
+### Bug Fixes
+
+* apply status color ([df6a6ec](https://github.com/b0ink/doubtfire-deploy/commit/df6a6ecc73d01dd2ef449270bc7c6de446d93884))
+* avoid loading rendering all students at the same time ([ba7910a](https://github.com/b0ink/doubtfire-deploy/commit/ba7910a59460a4d4139273b272e99625e5efc518))
+* ensure entire task list can be scrolled through ([8ce79a8](https://github.com/b0ink/doubtfire-deploy/commit/8ce79a8f33c82231f7f3c4033a4d37039514c015))
+
+## [11.0.0-22](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-21...v11.0.0-22) (2026-06-09)
+
+
+### Features
+
+* communications system ([#1239](https://github.com/b0ink/doubtfire-deploy/issues/1239)) ([b55bcfc](https://github.com/b0ink/doubtfire-deploy/commit/b55bcfc8f84f381beb7d62f41031db3ba6a38193))
+
+## [11.0.0-21](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-20...v11.0.0-21) (2026-06-08)
+
+
+### Bug Fixes
+
+* ensure unit dates map correctly ([532f185](https://github.com/b0ink/doubtfire-deploy/commit/532f185d2bc6839549a06a2f9e625c676cc280a6))
+
+## [11.0.0-20](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-19...v11.0.0-20) (2026-06-04)
+
+## [11.0.0-19](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-18...v11.0.0-19) (2026-06-03)
+
+
+### Bug Fixes
+
+* ensure unit is fetched in unit task editor ([54b1cf0](https://github.com/b0ink/doubtfire-deploy/commit/54b1cf03ed662447e7027a61d084f62f2548c996))
+
+## [11.0.0-18](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-17...v11.0.0-18) (2026-06-03)
+
+## [11.0.0-17](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-16...v11.0.0-17) (2026-06-03)
+
+
+### Features
+
+* support zip file submissions ([#1240](https://github.com/b0ink/doubtfire-deploy/issues/1240)) ([99d546f](https://github.com/b0ink/doubtfire-deploy/commit/99d546f1544feeab31fc6b2434e330b22544b56f))
+
+## [11.0.0-16](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-15...v11.0.0-16) (2026-06-03)
+
+## [11.0.0-15](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-14...v11.0.0-15) (2026-06-03)
+
+
+### Bug Fixes
+
+* display past due date on same day ([cd47f5c](https://github.com/b0ink/doubtfire-deploy/commit/cd47f5c0e4c351e2421afd94647a1297b3b5ec75))
+* prevent reload of task list component when switching back to dashboard ([be780b1](https://github.com/b0ink/doubtfire-deploy/commit/be780b14bc875c05e1083353c6d447315bc179e7))
+
+## [11.0.0-14](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-13...v11.0.0-14) (2026-06-03)
+
+## [11.0.0-13](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-12...v11.0.0-13) (2026-06-03)
+
+## [11.0.0-12](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-11...v11.0.0-12) (2026-06-02)
+
+
+### Bug Fixes
+
+* correctly apply task tutorial filters ([924c267](https://github.com/b0ink/doubtfire-deploy/commit/924c2676c6739820cc066d046b7ace71ee2d2f9e))
+
+## [11.0.0-11](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-10...v11.0.0-11) (2026-05-19)
+
+## [11.0.0-10](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-9...v11.0.0-10) (2026-05-18)
+
+## [11.0.0-9](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-8...v11.0.0-9) (2026-05-18)
+
+
+### Bug Fixes
+
+* ensure unit is loaded first before querying inbox ([6d7c9fb](https://github.com/b0ink/doubtfire-deploy/commit/6d7c9fb8ff7fe714844ebc8f0e04f2ef9a40d4d0))
+
+## [11.0.0-8](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-7...v11.0.0-8) (2026-05-18)
+
+## [11.0.0-7](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-6...v11.0.0-7) (2026-05-17)
+
+## [11.0.0-6](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-5...v11.0.0-6) (2026-05-14)
+
+## [11.0.0-5](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-4...v11.0.0-5) (2026-05-14)
+
+
+### Bug Fixes
+
+* ensure access to tutor notes ([5e0ec7c](https://github.com/b0ink/doubtfire-deploy/commit/5e0ec7cf6ccf04286aa6865e5d84d14487f68f7f))
+* ensure valid selected task ([deaae7f](https://github.com/b0ink/doubtfire-deploy/commit/deaae7fb819ea8dfc01d6ad67415f50c2e4258a2))
+
+## [11.0.0-4](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-3...v11.0.0-4) (2026-05-14)
+
+
+### Features
+
+* add route auth guards ([fb2e781](https://github.com/b0ink/doubtfire-deploy/commit/fb2e78113b13782dbbafe38c21f27c5d63be69c6))
+
+
+### Bug Fixes
+
+* enable inbox access for tutors ([05d3eda](https://github.com/b0ink/doubtfire-deploy/commit/05d3eda76bdcfb491649f5d83e69213db3de0885))
+
+## [11.0.0-3](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-2...v11.0.0-3) (2026-05-14)
+
+
+### Bug Fixes
+
+* add padding ([94f4216](https://github.com/b0ink/doubtfire-deploy/commit/94f4216e588d6dfb6abc4d10804971cb0e8f8678))
+* unlock task status selection for staff ([#1222](https://github.com/b0ink/doubtfire-deploy/issues/1222)) ([e4080d0](https://github.com/b0ink/doubtfire-deploy/commit/e4080d03a629654518bc362f48e8e0fbb790dd96))
+
+## [11.0.0-2](https://github.com/b0ink/doubtfire-deploy/compare/v11.0.0-1...v11.0.0-2) (2026-05-13)
+
+## [11.0.0-1](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-21...v11.0.0-1) (2026-05-13)
+
+
+### Features
+
+* (wip) add name to new student experience skeleton ([7b9ace4](https://github.com/b0ink/doubtfire-deploy/commit/7b9ace47132f0237bc621d3d0e45c3031e7fac05))
+* add burndown replacement ([837658a](https://github.com/b0ink/doubtfire-deploy/commit/837658afff090e52d53cefa7c2eed20a46c1ac01))
+* add new progress component ([8beef70](https://github.com/b0ink/doubtfire-deploy/commit/8beef701e6b7fc0dbea42f8849dc02e79a665fb7))
+* add new student experience ([9bb77ab](https://github.com/b0ink/doubtfire-deploy/commit/9bb77ab108e50b637329bd739925d1d9566d9203))
+* allow paste attachment comment ([#1165](https://github.com/b0ink/doubtfire-deploy/issues/1165)) ([0f24980](https://github.com/b0ink/doubtfire-deploy/commit/0f24980e6edcc5d0f81e015990adaf14afea400e))
+* batch upload feedback csv ([#1175](https://github.com/b0ink/doubtfire-deploy/issues/1175)) ([9f95987](https://github.com/b0ink/doubtfire-deploy/commit/9f959877b6f47d878d87b468f4aab73f536d598b))
+* bulk import staff via emails ([#1195](https://github.com/b0ink/doubtfire-deploy/issues/1195)) ([c91ab47](https://github.com/b0ink/doubtfire-deploy/commit/c91ab478ae1d1cd459040290f6f7a44c7dce3efe))
+* confirm recursive fix in mobile tutor view ([b64601a](https://github.com/b0ink/doubtfire-deploy/commit/b64601a425c7a64bccfaf47c1e4fccb0343b1589))
+* confirmation modal to reassign tutorials when removing staff ([5f90e90](https://github.com/b0ink/doubtfire-deploy/commit/5f90e9085d69913eced55ad54ce9ac09431d3822))
+* discussed in class refactor ([#1145](https://github.com/b0ink/doubtfire-deploy/issues/1145)) ([4d8bb5b](https://github.com/b0ink/doubtfire-deploy/commit/4d8bb5b82d89caa02ed4936819be601b3f6977fc))
+* display icon for tasks escalated by student ([dfcfd47](https://github.com/b0ink/doubtfire-deploy/commit/dfcfd472305ef2971fa72734c18e77253b98fa11))
+* display portfolio submission time ([d717b27](https://github.com/b0ink/doubtfire-deploy/commit/d717b270eb80c0b0245e6b39f0b3f0567c379512))
+* display sso redirecting state ([248c992](https://github.com/b0ink/doubtfire-deploy/commit/248c992f46488f61916e00a87ca32ed987721f31))
+* edit comments ([#1194](https://github.com/b0ink/doubtfire-deploy/issues/1194)) ([976b6ac](https://github.com/b0ink/doubtfire-deploy/commit/976b6ac4fa3bd2d5e954eebcae3fcb40dd8d1f0e))
+* enable task pinning in explorer ([1647e2b](https://github.com/b0ink/doubtfire-deploy/commit/1647e2bcc86ac6fb08c82be9795a06939c57bb6e))
+* improve look of task status count ([475c316](https://github.com/b0ink/doubtfire-deploy/commit/475c3165db099e1412e30fb6cf18bbaddd516e75))
+* pause feedback threshold during teaching period breaks ([#1138](https://github.com/b0ink/doubtfire-deploy/issues/1138)) ([12fbf81](https://github.com/b0ink/doubtfire-deploy/commit/12fbf8147107dc185a9a9cbea940efac93a0f316))
+* require discussion before marking complete ([#1103](https://github.com/b0ink/doubtfire-deploy/issues/1103)) ([86ae886](https://github.com/b0ink/doubtfire-deploy/commit/86ae8865268ee19e4b3430ff679358cc075e1346))
+* staff note and similarity indicators ([4a65a9b](https://github.com/b0ink/doubtfire-deploy/commit/4a65a9b21d615ecf78bf09743c9cf8c3026ff621))
+* visualisations ([f1cc39e](https://github.com/b0ink/doubtfire-deploy/commit/f1cc39e59dd224c55ee0e8b5f1d50855263d147e))
+
+
+### Bug Fixes
+
+* avoid rendering the staff list twice ([9e5be59](https://github.com/b0ink/doubtfire-deploy/commit/9e5be591b9a0cf72d01efa5e28e5b2b9e7eba043))
+* burndown chart visualisation ([#942](https://github.com/b0ink/doubtfire-deploy/issues/942)) ([8487b8d](https://github.com/b0ink/doubtfire-deploy/commit/8487b8d20e838fa1b2a2238bb856f73edc86442c))
+* check for valid unit ([d9560c3](https://github.com/b0ink/doubtfire-deploy/commit/d9560c3b68df0a09f577cdab990d252b5cd24c58))
+* complete student enrolment modal ([efa89c3](https://github.com/b0ink/doubtfire-deploy/commit/efa89c344069d386f2411a417e570d4158af91b9))
+* debounce duplicate task submission requests ([80f92e2](https://github.com/b0ink/doubtfire-deploy/commit/80f92e2277c48978e1738340063a7223c04fddb8))
+* display groups only when a group set is selected ([dde1e76](https://github.com/b0ink/doubtfire-deploy/commit/dde1e7697f8a9a93577bb0e18e3e81a3ebd1ace2))
+* duplicate files ([da51e8e](https://github.com/b0ink/doubtfire-deploy/commit/da51e8e0cc9518a2a76447a48079b42f144064fc))
+* ensure authorisation active in angular ([deaa1e9](https://github.com/b0ink/doubtfire-deploy/commit/deaa1e940f8295ff962c111f1dbac75ed5ff51fa))
+* ensure loading screen removed in sign in component ([6954ac6](https://github.com/b0ink/doubtfire-deploy/commit/6954ac62627e058b6a73c0e83dc8dc8a1740698d))
+* ensure pdf viewer is visible [#1186](https://github.com/b0ink/doubtfire-deploy/issues/1186) ([6bc0752](https://github.com/b0ink/doubtfire-deploy/commit/6bc075228fe2a48dcf91aa5350b5f15f602e2dc8))
+* ensure selected group is valid ([95cb9ac](https://github.com/b0ink/doubtfire-deploy/commit/95cb9ace6615b45dd75161fd02d6dd130420817b))
+* fix pdf viewer for portfolios ([456cf46](https://github.com/b0ink/doubtfire-deploy/commit/456cf466bf49b86467b269ec3d6fb4e63f6a059f))
+* get new visualisations to build ([f828fd4](https://github.com/b0ink/doubtfire-deploy/commit/f828fd42fafaf09d7f807cea8f6a1e14ec56ecc5))
+* link task list to definitions for project dashboard ([90853e9](https://github.com/b0ink/doubtfire-deploy/commit/90853e93c109430669e3f80e4324649cd2d0dba6))
+* new burndown and task status count ([bfecd08](https://github.com/b0ink/doubtfire-deploy/commit/bfecd086eeeae33e9bbba430c79bcc4f21506a07))
+* only render if submission date is valid ([04986a0](https://github.com/b0ink/doubtfire-deploy/commit/04986a0e501f0185dbd199b8d2554133217f7e75))
+* open report in turnitin ([091aaf8](https://github.com/b0ink/doubtfire-deploy/commit/091aaf8ba744bbf677f9b8f51a58bc16b7d631ab))
+* remove hardcoded chart view size ([1b31930](https://github.com/b0ink/doubtfire-deploy/commit/1b319303ac969ef63617b2362297233bc79bb916))
+* remove markdown filter from learning outcomes ([1de217c](https://github.com/b0ink/doubtfire-deploy/commit/1de217c0bc19b8dbedb21cf2e89e195772ae2a02))
+* set task data for project dashboard state ([ef75340](https://github.com/b0ink/doubtfire-deploy/commit/ef7534064ef92c06e459953076bc30b6eff4647d))
+* support building on windows ([b2aa7ae](https://github.com/b0ink/doubtfire-deploy/commit/b2aa7ae6ea7a456953ed4f2a8c63f784ac3870b5))
+* switch staff to unit roles in unit service ([0e5d9de](https://github.com/b0ink/doubtfire-deploy/commit/0e5d9de9df2eaf16e473f4f8fde920ffb59ce765))
+* task route transition race when switching from inbox ([63c52dc](https://github.com/b0ink/doubtfire-deploy/commit/63c52dc4cdb2f2fbc701f69c64b7273f5778c868))
+
+### [10.0.1-35](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-34...v10.0.1-35) (2026-04-28)
+
+
+### Features
+
+* bulk import staff via emails ([#1195](https://github.com/b0ink/doubtfire-deploy/issues/1195)) ([c91ab47](https://github.com/b0ink/doubtfire-deploy/commit/c91ab478ae1d1cd459040290f6f7a44c7dce3efe))
+* edit comments ([#1194](https://github.com/b0ink/doubtfire-deploy/issues/1194)) ([976b6ac](https://github.com/b0ink/doubtfire-deploy/commit/976b6ac4fa3bd2d5e954eebcae3fcb40dd8d1f0e))
+
+
+### Bug Fixes
+
+* task route transition race when switching from inbox ([63c52dc](https://github.com/b0ink/doubtfire-deploy/commit/63c52dc4cdb2f2fbc701f69c64b7273f5778c868))
+
+### [10.0.1-34](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-33...v10.0.1-34) (2026-04-27)
+
+### [10.0.1-33](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-32...v10.0.1-33) (2026-04-23)
+
+
+### Features
+
+* allow paste attachment comment ([#1165](https://github.com/b0ink/doubtfire-deploy/issues/1165)) ([0f24980](https://github.com/b0ink/doubtfire-deploy/commit/0f24980e6edcc5d0f81e015990adaf14afea400e))
+
+
+### Bug Fixes
+
+* open report in turnitin ([091aaf8](https://github.com/b0ink/doubtfire-deploy/commit/091aaf8ba744bbf677f9b8f51a58bc16b7d631ab))
+
+### [10.0.1-32](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-31...v10.0.1-32) (2026-04-18)
+
+### [10.0.1-31](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-30...v10.0.1-31) (2026-04-18)
+
+### [10.0.1-30](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-29...v10.0.1-30) (2026-04-18)
+
+
+### Features
+
+* batch upload feedback csv ([#1175](https://github.com/b0ink/doubtfire-deploy/issues/1175)) ([9f95987](https://github.com/b0ink/doubtfire-deploy/commit/9f959877b6f47d878d87b468f4aab73f536d598b))
+* display icon for tasks escalated by student ([dfcfd47](https://github.com/b0ink/doubtfire-deploy/commit/dfcfd472305ef2971fa72734c18e77253b98fa11))
+* enable task pinning in explorer ([1647e2b](https://github.com/b0ink/doubtfire-deploy/commit/1647e2bcc86ac6fb08c82be9795a06939c57bb6e))
+
+
+### Bug Fixes
+
+* debounce duplicate task submission requests ([80f92e2](https://github.com/b0ink/doubtfire-deploy/commit/80f92e2277c48978e1738340063a7223c04fddb8))
+
+### [10.0.1-29](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-28...v10.0.1-29) (2026-04-15)
+
+
+### Features
+
+* discussed in class refactor ([#1145](https://github.com/b0ink/doubtfire-deploy/issues/1145)) ([4d8bb5b](https://github.com/b0ink/doubtfire-deploy/commit/4d8bb5b82d89caa02ed4936819be601b3f6977fc))
+
+### [10.0.1-28](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-27...v10.0.1-28) (2026-04-13)
+
+### [10.0.1-27](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.27...v10.0.1-27) (2026-04-13)
+
+### [10.0.27](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-26...v10.0.27) (2026-04-13)
+
+
+### Features
+
+* confirmation modal to reassign tutorials when removing staff ([5f90e90](https://github.com/b0ink/doubtfire-deploy/commit/5f90e9085d69913eced55ad54ce9ac09431d3822))
+
+### [10.0.1-26](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-25...v10.0.1-26) (2026-03-26)
+
+### [10.0.1-25](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-24...v10.0.1-25) (2026-03-26)
+
+
+### Features
+
+* display sso redirecting state ([248c992](https://github.com/b0ink/doubtfire-deploy/commit/248c992f46488f61916e00a87ca32ed987721f31))
+* pause feedback threshold during teaching period breaks ([#1138](https://github.com/b0ink/doubtfire-deploy/issues/1138)) ([12fbf81](https://github.com/b0ink/doubtfire-deploy/commit/12fbf8147107dc185a9a9cbea940efac93a0f316))
+
+### [10.0.1-24](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-23...v10.0.1-24) (2026-03-24)
+
+
+### Features
+
+* confirm recursive fix in mobile tutor view ([b64601a](https://github.com/b0ink/doubtfire-deploy/commit/b64601a425c7a64bccfaf47c1e4fccb0343b1589))
+
+### [10.0.1-23](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-22...v10.0.1-23) (2026-03-24)
+
+### [10.0.1-22](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-21...v10.0.1-22) (2026-03-23)
+
+
+### Features
+
+* display portfolio submission time ([d717b27](https://github.com/b0ink/doubtfire-deploy/commit/d717b270eb80c0b0245e6b39f0b3f0567c379512))
+* require discussion before marking complete ([#1103](https://github.com/b0ink/doubtfire-deploy/issues/1103)) ([86ae886](https://github.com/b0ink/doubtfire-deploy/commit/86ae8865268ee19e4b3430ff679358cc075e1346))
+
+
+### Bug Fixes
+
+* avoid rendering the staff list twice ([9e5be59](https://github.com/b0ink/doubtfire-deploy/commit/9e5be591b9a0cf72d01efa5e28e5b2b9e7eba043))
+* only render if submission date is valid ([04986a0](https://github.com/b0ink/doubtfire-deploy/commit/04986a0e501f0185dbd199b8d2554133217f7e75))
+
+### [10.0.1-21](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-20...v10.0.1-21) (2026-03-13)
+
+### [10.0.1-20](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-19...v10.0.1-20) (2026-03-13)
+
+### [10.0.1-19](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-18...v10.0.1-19) (2026-03-13)
+
+
+### Features
+
+* view submission history + zip file editor ([#1104](https://github.com/b0ink/doubtfire-deploy/issues/1104)) ([d706f09](https://github.com/b0ink/doubtfire-deploy/commit/d706f0918285a2c6d69c6240a93b41e88506f771))
+
+### [10.0.1-18](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-17...v10.0.1-18) (2026-03-11)
+
+
+### Bug Fixes
+
+* convert emojis in submission comment to string with colons ([#1105](https://github.com/b0ink/doubtfire-deploy/issues/1105)) ([7c508cb](https://github.com/b0ink/doubtfire-deploy/commit/7c508cba8695fcd7b6df4aaa53a70066ecb137f3))
+* dont subtract tutor note count for reading your own notes ([8fe6171](https://github.com/b0ink/doubtfire-deploy/commit/8fe61718c0e263fa965b861763337af27c175b36))
+
+### [10.0.1-17](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-16...v10.0.1-17) (2026-03-06)
+
+
+### Bug Fixes
+
+* empty prompts layout ([c77325a](https://github.com/b0ink/doubtfire-deploy/commit/c77325a2992d0b326bdeb27cd00a955aaa19811c))
+
+### [10.0.1-16](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-15...v10.0.1-16) (2026-03-04)
+
+### [10.0.1-15](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-14...v10.0.1-15) (2026-03-04)
+
+### [10.0.1-14](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-13...v10.0.1-14) (2026-03-03)
+
+
+### Features
+
+* view tutor notes from unit staff editor ([011980d](https://github.com/b0ink/doubtfire-deploy/commit/011980ddebb4caddaebb380aae4825930c077122))
+
+### [10.0.1-13](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-12...v10.0.1-13) (2026-03-02)
+
+
+### Features
+
+* add snooze moderation action ([#1096](https://github.com/b0ink/doubtfire-deploy/issues/1096)) ([bdcefda](https://github.com/b0ink/doubtfire-deploy/commit/bdcefda81e4acdef56ebe9f228632f26a904287c))
+
+### [10.0.1-12](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-11...v10.0.1-12) (2026-03-02)
+
+### [10.0.1-11](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-10...v10.0.1-11) (2026-02-28)
+
+
+### Bug Fixes
+
+* ensure deadline is returned ([6629a5f](https://github.com/b0ink/doubtfire-deploy/commit/6629a5f4b9834009dabf19a60a36c1289126034d))
+
+### [10.0.1-10](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-9...v10.0.1-10) (2026-02-26)
+
+
+### Features
+
+* display available storage for docker images ([#1092](https://github.com/b0ink/doubtfire-deploy/issues/1092)) ([81ebd34](https://github.com/b0ink/doubtfire-deploy/commit/81ebd3488d20a1e057c24b37190f4eea99bad9ec))
+
+### [10.0.1-9](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-8...v10.0.1-9) (2026-02-25)
+
+
+### Bug Fixes
+
+* typo ([4ecfe27](https://github.com/b0ink/doubtfire-deploy/commit/4ecfe27c91104c746ac8a4f9059030d5475776bb))
+
+### [10.0.1-8](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-7...v10.0.1-8) (2026-02-25)
+
+
+### Features
+
+* overflow marking ([#1086](https://github.com/b0ink/doubtfire-deploy/issues/1086)) ([e047988](https://github.com/b0ink/doubtfire-deploy/commit/e047988c4fff787852d1cc5a041cb48e11ee57f8))
+
+### [10.0.1-7](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-6...v10.0.1-7) (2026-02-25)
+
+
+### Bug Fixes
+
+* ensure timestamp is normalised to start of day ([671c391](https://github.com/b0ink/doubtfire-deploy/commit/671c3916797dc5ab680b9dac52ae68626e49d351))
+
+### [10.0.1-6](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-5...v10.0.1-6) (2026-02-24)
+
+### [10.0.1-5](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-4...v10.0.1-5) (2026-02-24)
+
+
+### Features
+
+* per grade start dates ([#1090](https://github.com/b0ink/doubtfire-deploy/issues/1090)) ([a35019a](https://github.com/b0ink/doubtfire-deploy/commit/a35019aa4011fce7808da8cc1481fae7635aac69))
+
+### [10.0.1-4](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-3...v10.0.1-4) (2026-02-23)
+
+
+### Features
+
+* allow custom target dates per target grade ([#1089](https://github.com/b0ink/doubtfire-deploy/issues/1089)) ([79b5af6](https://github.com/b0ink/doubtfire-deploy/commit/79b5af6bf05419e05dbef69e6ef1f72bfc85097c))
+
+### [10.0.1-3](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-2...v10.0.1-3) (2026-02-21)
+
+### [10.0.1-2](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-1...v10.0.1-2) (2026-02-19)
+
+
+### Bug Fixes
+
+* set min width for mentor dropdown ([75cc1b3](https://github.com/b0ink/doubtfire-deploy/commit/75cc1b3569d17cd54e71f86874addb4bc34d566f))
+
+### [10.0.1-1](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-0...v10.0.1-1) (2026-02-18)
+
+
+### Features
+
+* tutor notes + mentorship + moderation + escalation ([#1068](https://github.com/b0ink/doubtfire-deploy/issues/1068)) ([48f4db7](https://github.com/b0ink/doubtfire-deploy/commit/48f4db730f06e349f5e3c791f1972d23b0f8573f))
+
+
+### Bug Fixes
+
+* ensure tutor notes view extends full height ([c872ed7](https://github.com/b0ink/doubtfire-deploy/commit/c872ed766bf493456da56e33dcda48a90ed3f6ce))
+
+### [10.0.1-0](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0...v10.0.1-0) (2026-02-17)
+
+## [10.0.0](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-73...v10.0.0) (2026-02-12)
+
+## [10.0.0-73](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-72...v10.0.0-73) (2026-02-12)
+
+## [10.0.0-72](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-71...v10.0.0-72) (2026-02-12)
+
+
+### Bug Fixes
+
+* null safe user access ([f6aeecc](https://github.com/b0ink/doubtfire-deploy/commit/f6aeecc6c2c79909ca859422e7d9b3bc65ef6456))
+
+## [10.0.0-71](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-70...v10.0.0-71) (2026-02-11)
+
+
+### Features
+
+* download staff notes csv ([#1062](https://github.com/b0ink/doubtfire-deploy/issues/1062)) ([6dcfe95](https://github.com/b0ink/doubtfire-deploy/commit/6dcfe952f0415fb1327b738137f817a0fcfa0488))
+* migrate to jplag report viewer v6.3.0 ([#1082](https://github.com/b0ink/doubtfire-deploy/issues/1082)) ([697588b](https://github.com/b0ink/doubtfire-deploy/commit/697588b98e28ec663f4982c49f18fe8109ba7c34))
+
+## [10.0.0-70](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-69...v10.0.0-70) (2026-01-05)
+
+
+### Bug Fixes
+
+* skip query for unsaved task definition ([a6bdd22](https://github.com/b0ink/doubtfire-deploy/commit/a6bdd22ff864e72c679fae1844345963447fdf08))
+
+## [10.0.0-69](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-68...v10.0.0-69) (2026-01-05)
+
+
+### Features
+
+* attention required task status ([#1061](https://github.com/b0ink/doubtfire-deploy/issues/1061)) ([15365ed](https://github.com/b0ink/doubtfire-deploy/commit/15365ed4f42af790af9a9ae24dae42f041dff1df))
+* display number of stuff notes in tutor discussion ([2674d4e](https://github.com/b0ink/doubtfire-deploy/commit/2674d4e5ade26e5420b87518f4cd9da909ec7246))
+* overseer pipeline ([#1064](https://github.com/b0ink/doubtfire-deploy/issues/1064)) ([7cb8fc7](https://github.com/b0ink/doubtfire-deploy/commit/7cb8fc7471f0402c562fb8a0b87df672ceb22ea3))
+* project task planner gantt chart ([#1051](https://github.com/b0ink/doubtfire-deploy/issues/1051)) ([4f60792](https://github.com/b0ink/doubtfire-deploy/commit/4f607926e3ac305316a05607fda7f7bff7faec00))
+
+## [10.0.0-68](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-67...v10.0.0-68) (2025-12-08)
+
+
+### Bug Fixes
+
+* only trim if valid ([fbc63b0](https://github.com/b0ink/doubtfire-deploy/commit/fbc63b0308d87d921e1d74788e8da6031443e57e))
+
+## [10.0.0-67](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-66...v10.0.0-67) (2025-12-03)
+
+
+### Features
+
+* allow sidekiq web access ([#1053](https://github.com/b0ink/doubtfire-deploy/issues/1053)) ([6651279](https://github.com/b0ink/doubtfire-deploy/commit/6651279c49f431dbddf6fd5d810440ae7b3b9898))
+
+## [10.0.0-66](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-65...v10.0.0-66) (2025-12-03)
+
+
+### Features
+
+* discussion prompts ([#1042](https://github.com/b0ink/doubtfire-deploy/issues/1042)) ([730ffd0](https://github.com/b0ink/doubtfire-deploy/commit/730ffd0c3f79a5ad42c0c59c2f2c2a901ab0d702))
+
+
+### Bug Fixes
+
+* correctly set rollover end date ([0611be0](https://github.com/b0ink/doubtfire-deploy/commit/0611be04e8fe7e75ae07504945a2984839475e3a))
+
+## [10.0.0-65](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-64...v10.0.0-65) (2025-11-25)
+
+
+### Bug Fixes
+
+* typo ([bdcd75d](https://github.com/b0ink/doubtfire-deploy/commit/bdcd75d91d5ddc7d9a60317bd91289a7242251e1))
+
+## [10.0.0-64](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-63...v10.0.0-64) (2025-11-25)
+
+
+### Bug Fixes
+
+* avoid calling window to open project in new tab ([9ab015d](https://github.com/b0ink/doubtfire-deploy/commit/9ab015d168cf7e421500910766b9e6fb0bf907f5))
+* use new google fonts api for proper weight loading ([#1039](https://github.com/b0ink/doubtfire-deploy/issues/1039)) ([40f2640](https://github.com/b0ink/doubtfire-deploy/commit/40f2640e6579536cef8e82e9476733083b82bac1))
+
+## [10.0.0-63](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-62...v10.0.0-63) (2025-11-10)
+
+
+### Features
+
+* open project dashboard from portfolios view ([5b4b097](https://github.com/b0ink/doubtfire-deploy/commit/5b4b097a9b189e5a816695cce7438104f1144283)), closes [#1040](https://github.com/b0ink/doubtfire-deploy/issues/1040)
+
+## [10.0.0-62](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-61...v10.0.0-62) (2025-11-06)
+
+
+### Features
+
+* restrict assessments to tutors in the same tutorial stream ([#1033](https://github.com/b0ink/doubtfire-deploy/issues/1033)) ([29fbc14](https://github.com/b0ink/doubtfire-deploy/commit/29fbc1498b0d48811cb59c5f2a66871fbac06849))
+
+## [10.0.0-61](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-60...v10.0.0-61) (2025-11-06)
+
+## [10.0.0-60](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-59...v10.0.0-60) (2025-11-05)
+
+
+### Features
+
+* upload grades csv ([#1038](https://github.com/b0ink/doubtfire-deploy/issues/1038)) ([c67ceaa](https://github.com/b0ink/doubtfire-deploy/commit/c67ceaa84d02bb71626890fc28bb4aaea164b952))
+
+## [10.0.0-59](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-58...v10.0.0-59) (2025-11-04)
+
+
+### Bug Fixes
+
+* require comment for new evidence ([58ec499](https://github.com/b0ink/doubtfire-deploy/commit/58ec499a8d59c7202f591de661551d8a32c37527))
+* require comment for new evidence ([c5397fc](https://github.com/b0ink/doubtfire-deploy/commit/c5397fc5c444e76b431aa5d6487553c60e711c2e))
+
+## [10.0.0-58](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-57...v10.0.0-58) (2025-11-01)
+
+## [10.0.0-57](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-56...v10.0.0-57) (2025-10-30)
+
+
+### Features
+
+* open similarities from project dashboard ([#1035](https://github.com/b0ink/doubtfire-deploy/issues/1035)) ([ec0af0c](https://github.com/b0ink/doubtfire-deploy/commit/ec0af0cbe47392d6542d9741e1131954195d407f))
+
+
+### Bug Fixes
+
+* enable submission button for 'need help' trigger ([#1034](https://github.com/b0ink/doubtfire-deploy/issues/1034)) ([07de5b1](https://github.com/b0ink/doubtfire-deploy/commit/07de5b1e34f78f83c1418399f45e8c754aea530a))
+* typo ([2712a68](https://github.com/b0ink/doubtfire-deploy/commit/2712a68f6031794c571a7ee5866c653bf67de1dd))
+
+## [10.0.0-56](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-55...v10.0.0-56) (2025-10-23)
+
+
+### Bug Fixes
+
+* ensure grade has been selected ([32e03d1](https://github.com/b0ink/doubtfire-deploy/commit/32e03d111e61df42a9c67cb9e68d271f9300d5bc))
+
+## [10.0.0-55](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-54...v10.0.0-55) (2025-10-20)
+
+
+### Features
+
+* display task count in inbox ([#1025](https://github.com/b0ink/doubtfire-deploy/issues/1025)) ([3858e37](https://github.com/b0ink/doubtfire-deploy/commit/3858e3792fef3f0542d954e213987484ac97a990))
+
+
+### Bug Fixes
+
+* display correct marking session details ([c49ffed](https://github.com/b0ink/doubtfire-deploy/commit/c49ffeddedb2b79c39252770692e7df9dcd97bfc))
+* typo ([8a1dd1a](https://github.com/b0ink/doubtfire-deploy/commit/8a1dd1ac806c5acbbf8d1ccf7bdcb6af1c68dd14))
+
+## [10.0.0-54](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-53...v10.0.0-54) (2025-10-17)
+
+## [10.0.0-53](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-52...v10.0.0-53) (2025-10-17)
+
+## [10.0.0-52](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-51...v10.0.0-52) (2025-10-17)
+
+
+### Features
+
+* campus timezone ([#1022](https://github.com/b0ink/doubtfire-deploy/issues/1022)) ([4384b37](https://github.com/b0ink/doubtfire-deploy/commit/4384b376eaedf8ab66febec4c0ada53111311ff0))
+* download marking sessions for tutor ([e3fbe44](https://github.com/b0ink/doubtfire-deploy/commit/e3fbe4404661eb16b9c0e5ba19a1369dac1fb5b0))
+
+
+### Bug Fixes
+
+* display tutor name ([8956020](https://github.com/b0ink/doubtfire-deploy/commit/89560205d7316c53d77f376b5f7cfff5bf99e9c6))
+* filter out students from unit staff editor ([#1018](https://github.com/b0ink/doubtfire-deploy/issues/1018)) ([5c0c997](https://github.com/b0ink/doubtfire-deploy/commit/5c0c9974569e720a6c95aa8333c2a38d0ab4a1b0))
+* re-enable user filtering on click ([697daf2](https://github.com/b0ink/doubtfire-deploy/commit/697daf26693f20f61dcfbbeb0597a02715c9512d))
+
+## [10.0.0-51](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-50...v10.0.0-51) (2025-10-14)
+
+
+### Bug Fixes
+
+* unlock tasks when prerequisite requires rff and task is in aip state ([173d8c2](https://github.com/b0ink/doubtfire-deploy/commit/173d8c25375e231b25e64a04c1e3d2aedf741300))
+
+## [10.0.0-50](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-49...v10.0.0-50) (2025-10-13)
+
+
+### Features
+
+* jplag base code ([#1010](https://github.com/b0ink/doubtfire-deploy/issues/1010)) ([9a7a860](https://github.com/b0ink/doubtfire-deploy/commit/9a7a860fe799e351831e2a3f9701792792b76b38))
+
+## [10.0.0-49](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-48...v10.0.0-49) (2025-10-12)
+
+
+### Features
+
+* display session start and end time ([bb548b9](https://github.com/b0ink/doubtfire-deploy/commit/bb548b92d04bb7b8665444e7935317bc4b6a0981))
+
+## [10.0.0-48](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-47...v10.0.0-48) (2025-10-12)
+
+
+### Features
+
+* add tutor times summary download ([#1015](https://github.com/b0ink/doubtfire-deploy/issues/1015)) ([2b4afe2](https://github.com/b0ink/doubtfire-deploy/commit/2b4afe24e3f8139fec23337facc7656eec9a9d3a))
+
+
+### Bug Fixes
+
+* add assess in portfolio tasks as completed tasks in burndown chart ([8d36c94](https://github.com/b0ink/doubtfire-deploy/commit/8d36c94f03563a0924cfeb7d2d9e8dd911b65960))
+
+## [10.0.0-47](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-46...v10.0.0-47) (2025-09-25)
+
+
+### Features
+
+* add observer only ui ([bfbcee9](https://github.com/b0ink/doubtfire-deploy/commit/bfbcee98cfa4888241c5956372d004ec1cd813cb))
+
+## [10.0.0-46](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-45...v10.0.0-46) (2025-09-24)
+
+## [10.0.0-45](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-44...v10.0.0-45) (2025-09-22)
+
+
+### Bug Fixes
+
+* check for valid unit ([bfae1ad](https://github.com/b0ink/doubtfire-deploy/commit/bfae1ad69bb767cdad47dd8fac35cc84e5134534))
+
+## [10.0.0-44](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-43...v10.0.0-44) (2025-09-18)
+
+## [10.0.0-43](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-42...v10.0.0-43) (2025-09-17)
+
+
+### Bug Fixes
+
+* remove /api from host url ([5889edc](https://github.com/b0ink/doubtfire-deploy/commit/5889edc35bb427dfd4defefa7b77e8c81cd6dd7c))
+
+## [10.0.0-42](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-41...v10.0.0-42) (2025-09-17)
+
+
+### Features
+
+* display result of student imports ([e0503f4](https://github.com/b0ink/doubtfire-deploy/commit/e0503f424dfbef49e68b4e2f697e8d135120634e))
+* enable unit linking to lms context using deeplink ([73de1f1](https://github.com/b0ink/doubtfire-deploy/commit/73de1f1c68223c863c35198b773f0bdeaa1f549e))
+* init lti integration ([248be93](https://github.com/b0ink/doubtfire-deploy/commit/248be93e7c8b5ff060957e500b6a4943882f456c))
+* sync enrolments for all lti members ([5ec499f](https://github.com/b0ink/doubtfire-deploy/commit/5ec499f37b93f2b4bd56d145501fd55ab6af45e6))
+* sync grades for all members ([0f5daa8](https://github.com/b0ink/doubtfire-deploy/commit/0f5daa852f4c3750367f7d2c3f9b0c07ffbfed3f))
+* unauthorised lti banner ([12e65d8](https://github.com/b0ink/doubtfire-deploy/commit/12e65d8f1609f2030b25347beb1c0e06f513f65c))
+
+
+### Bug Fixes
+
+* forward correct lti token ([e95544e](https://github.com/b0ink/doubtfire-deploy/commit/e95544e2e158affefafd2fc0fe9657bf47c200db))
+
+## [10.0.0-41](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-40...v10.0.0-41) (2025-09-15)
+
+## [10.0.0-40](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-39...v10.0.0-40) (2025-09-15)
+
+
+### Features
+
+* assess in portfolio status ([ada4a21](https://github.com/b0ink/doubtfire-deploy/commit/ada4a21c945018abeb9ca1169e48fdd822a27322))
+* ensure complete button updates aip only tasks to working on it ([c8dd020](https://github.com/b0ink/doubtfire-deploy/commit/c8dd020a5e572765f7710c266ae2b20563ab05c2))
+* send automated message when set back to working on it ([a858d3a](https://github.com/b0ink/doubtfire-deploy/commit/a858d3a7b2f41ac7478d4917d2d2f6c68a167e6e))
+* submission type modal ([52f7d40](https://github.com/b0ink/doubtfire-deploy/commit/52f7d404ef154ff27c0ba887c8321360df95ddb6))
+
+
+### Bug Fixes
+
+* typo ([2a2e4a4](https://github.com/b0ink/doubtfire-deploy/commit/2a2e4a41e6ea20152632a9f82ca1723d9a6b56cc))
+* wording ([6291bd6](https://github.com/b0ink/doubtfire-deploy/commit/6291bd61ccc4ff45c708300d0a5014a59a565414))
+
+## [10.0.0-39](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-38...v10.0.0-39) (2025-09-10)
+
+
+### Features
+
+* display warning above task sheet for incomplete prerequisites ([86433bb](https://github.com/b0ink/doubtfire-deploy/commit/86433bb18e6ff9b024d7c397b8744618721b602f))
+
+## [10.0.0-38](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-37...v10.0.0-38) (2025-09-09)
+
+
+### Bug Fixes
+
+* ensure task prerequisites are correctly cached ([3945f4e](https://github.com/b0ink/doubtfire-deploy/commit/3945f4e2ecff1a1d79662c099baf745bfd6f4b7d))
+
+## [10.0.0-37](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-36...v10.0.0-37) (2025-09-09)
+
+## [10.0.0-36](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-35...v10.0.0-36) (2025-09-09)
+
+## [10.0.0-35](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-34...v10.0.0-35) (2025-09-09)
+
+
+### Features
+
+* add task prerequisites ([d5c1a51](https://github.com/b0ink/doubtfire-deploy/commit/d5c1a51dc049b025ae85b2ef6513630a197eabee))
+* cross out prerequisite tasks that have been submitted ([7e1a7db](https://github.com/b0ink/doubtfire-deploy/commit/7e1a7dbd3351dbcc8230f115554d6afdfaca0f70))
+* show list of prerequisites in task dashboard ([111f60a](https://github.com/b0ink/doubtfire-deploy/commit/111f60a4272abcf683c50b711bd43a1f772153ec))
+
+
+### Bug Fixes
+
+* typo ([e0c867f](https://github.com/b0ink/doubtfire-deploy/commit/e0c867fc7f24301d414ed584b6859e09d95c652c))
+
+## [10.0.0-34](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-33...v10.0.0-34) (2025-09-08)
+
+
+### Features
+
+* task assessment counts csv ([c2c6002](https://github.com/b0ink/doubtfire-deploy/commit/c2c6002cef6e5a75f722c964bf2843cf662380f4))
+
+
+### Bug Fixes
+
+* allow scrolling in edit profile form dialog ([ee2b908](https://github.com/b0ink/doubtfire-deploy/commit/ee2b9085a931df551408cd12dbc8187c04d088fc))
+
+## [10.0.0-33](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-32...v10.0.0-33) (2025-09-04)
+
+## [10.0.0-32](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-31...v10.0.0-32) (2025-09-02)
+
+## [10.0.0-31](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-30...v10.0.0-31) (2025-08-27)
+
+
+### Bug Fixes
+
+* use new checked_in comment type ([3033c6c](https://github.com/b0ink/doubtfire-deploy/commit/3033c6ca3c27ad4fc0a7ce4bda3ac44df09516e3))
+
+## [10.0.0-30](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-29...v10.0.0-30) (2025-08-27)
+
+## [10.0.0-29](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-28...v10.0.0-29) (2025-08-27)
+
+## [10.0.0-28](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-27...v10.0.0-28) (2025-08-27)
+
+## [10.0.0-27](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-26...v10.0.0-27) (2025-08-27)
+
+
+### Features
+
+* add option for qr footer text ([9e9b9db](https://github.com/b0ink/doubtfire-deploy/commit/9e9b9dbfb13d7531cb5387060407e23c25212cca))
+* support viewing qr for selected student in tutor view ([1f7fa51](https://github.com/b0ink/doubtfire-deploy/commit/1f7fa51d65767b3186e6eda8970a408cf0e6c847))
+
+
+### Bug Fixes
+
+* add padding when edit profile form displayed as modal ([a3f3b97](https://github.com/b0ink/doubtfire-deploy/commit/a3f3b977251a206b268c369c370934ff69b574d3))
+* show student id ([d1eeefb](https://github.com/b0ink/doubtfire-deploy/commit/d1eeefbea53cb13385bef2b3ba6b04d2f606c391))
+
+## [10.0.0-26](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-25...v10.0.0-26) (2025-08-27)
+
+
+### Features
+
+* add attendance marked task comment ([1559e81](https://github.com/b0ink/doubtfire-deploy/commit/1559e8104c8c4bc137df4f46a0a5ed0e345260af))
+* add tutor discussion to unit dropdown ([90f4149](https://github.com/b0ink/doubtfire-deploy/commit/90f41493313d6a3ad9bdafdd4cd4865c80e94ca2))
+* redirect back to inbox if unit context exists ([b7f4ab9](https://github.com/b0ink/doubtfire-deploy/commit/b7f4ab9e7801fbb33ffc6b4e02bf6613e6d7a524))
+
+
+### Bug Fixes
+
+* enforce minimum height on task comments viewer ([a7588e6](https://github.com/b0ink/doubtfire-deploy/commit/a7588e6f007381c04a570650257cd6f2ff93e5ae))
+* set max width on main container ([d1e5ab1](https://github.com/b0ink/doubtfire-deploy/commit/d1e5ab1427352fcf1b0fdd920cb9838a4f8799a0))
+
+## [10.0.0-25](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-24...v10.0.0-25) (2025-08-26)
+
+
+### Bug Fixes
+
+* only clear draft comment after successfully sending message ([2556355](https://github.com/b0ink/doubtfire-deploy/commit/2556355b0c3e1770fab021ed7d46e462882025b3))
+
+## [10.0.0-24](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-23...v10.0.0-24) (2025-08-21)
+
+## [10.0.0-23](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-22...v10.0.0-23) (2025-08-14)
+
+## [10.0.0-22](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-21...v10.0.0-22) (2025-08-14)
+
+
+### Features
+
+* download tasks awaiting feedback csv ([4a94373](https://github.com/b0ink/doubtfire-deploy/commit/4a943736689e41c405a831ca88a1303fb7c00385))
+
+
+### Bug Fixes
+
+* add return statement ([b8779e7](https://github.com/b0ink/doubtfire-deploy/commit/b8779e70d4decdc837caa8c84e8e8e2dfd3faf7f))
+* show full progress when sidekiq job is complete ([6a60d94](https://github.com/b0ink/doubtfire-deploy/commit/6a60d94b896d173a5008badd1b45bc7f45eee808))
+
+## [10.0.0-21](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-20...v10.0.0-21) (2025-08-12)
+
+
+### Bug Fixes
+
+* clear notification after fetching task comments ([c2d1536](https://github.com/b0ink/doubtfire-deploy/commit/c2d15367d9c53cb426dd12e73dc8d0b779a7f563))
+
+## [10.0.0-20](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-19...v10.0.0-20) (2025-08-11)
+
+## [10.0.0-19](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-18...v10.0.0-19) (2025-08-07)
+
+
+### Features
+
+* mark as discussed button in mobile tutor discussion ([9e62214](https://github.com/b0ink/doubtfire-deploy/commit/9e62214df772355048f4c7c7ffc13f3181f746e2))
+
+## [10.0.0-18](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-17...v10.0.0-18) (2025-07-31)
+
+
+### Bug Fixes
+
+* ensure no progress returns 0 ([9cb1f1a](https://github.com/b0ink/doubtfire-deploy/commit/9cb1f1ab770ac798ec8ecc555ceb73024bc98649))
+
+## [10.0.0-17](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-16...v10.0.0-17) (2025-07-31)
+
+
+### Features
+
+* return task to not started status ([bce1483](https://github.com/b0ink/doubtfire-deploy/commit/bce1483296064bafd44bc538f83731809d816a05))
+
+
+### Bug Fixes
+
+* check if project task stats exist ([e59af80](https://github.com/b0ink/doubtfire-deploy/commit/e59af803f8440338b0210b6a437537372f5dac5b))
+* ensure new comments are fetched for selected project only ([3b0e387](https://github.com/b0ink/doubtfire-deploy/commit/3b0e3877e05f162143fc78065cee9a191d0755f4))
+* ensure whitespace is trimmed ([fe687df](https://github.com/b0ink/doubtfire-deploy/commit/fe687df8b035ef851b9d147e01f575187f428a8c))
+* prevent additional inbox queries ([0d35f7f](https://github.com/b0ink/doubtfire-deploy/commit/0d35f7f4ef2e87dd078edf20e509c8f6efc5c216))
+
+## [10.0.0-16](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-15...v10.0.0-16) (2025-07-29)
+
+## [10.0.0-15](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-14...v10.0.0-15) (2025-07-29)
+
+
+### Bug Fixes
+
+* correctly load start page ([e297f26](https://github.com/b0ink/doubtfire-deploy/commit/e297f261e3df19faf11be9fde6515ce75434c320))
+* map correct variables ([b180799](https://github.com/b0ink/doubtfire-deploy/commit/b180799efd716d802cf83ce08b6df0639d9055b6))
+* prevent report button from expanding accordion ([e715415](https://github.com/b0ink/doubtfire-deploy/commit/e7154157ff1b88817141bae5b1b3de9fd37171f0))
+
+## [10.0.0-14](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.0-13...v10.0.0-14) (2025-07-28)
+
+
+### Features
+
+* portfolio zip downloading via sidekiq ([9000830](https://github.com/b0ink/doubtfire-deploy/commit/9000830e5e98bc0b921d618c034471eeddf996b4))
+* show list of active sidekiq jobs ([aa85665](https://github.com/b0ink/doubtfire-deploy/commit/aa85665670a23fea556c6420f139e7e7853d16ba))
+* show maintenance ui when api fails ([29bb95f](https://github.com/b0ink/doubtfire-deploy/commit/29bb95f5974dc8f258b204957a212a8399ad2d6e))
+* sidekiq progress modal component ([b9691e7](https://github.com/b0ink/doubtfire-deploy/commit/b9691e793204e84fae323610a7b555b15e065514))
+
+
+### Bug Fixes
+
+* fallback to task definition due date ([bef0a9c](https://github.com/b0ink/doubtfire-deploy/commit/bef0a9cbfefc58407f851d3ca73b90af6b81151f))
+
+## [10.0.0-13](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-12...v10.0.0-13) (2025-07-16)
+
+
+### Bug Fixes
+
+* display task-date-slider only when task exists ([c886034](https://github.com/macite/doubtfire-deploy/commit/c886034d4bf69851f4b06599a45bd1b135c3fff2))
+
+## [10.0.0-12](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-11...v10.0.0-12) (2025-07-15)
+
+
+### Bug Fixes
+
+* exclude jplag route from ngsw cache ([69b55bb](https://github.com/macite/doubtfire-deploy/commit/69b55bb85d8583b6935e08c91aad63e98e6ce887))
+
+## [10.0.0-11](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-10...v10.0.0-11) (2025-07-15)
+
+## [10.0.0-10](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-9...v10.0.0-10) (2025-07-15)
+
+## [10.0.0-9](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-8...v10.0.0-9) (2025-07-15)
+
+## [10.0.0-8](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-7...v10.0.0-8) (2025-07-15)
+
+## [10.0.0-7](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-6...v10.0.0-7) (2025-07-14)
+
+
+### Features
+
+* add icon to discussed in class update ([c11cec0](https://github.com/macite/doubtfire-deploy/commit/c11cec02ece3b6bf7c2e36998ee2fa2a3572bc58))
+* add jplag report download button to student submission tutor teacher footer view ([2ec1b06](https://github.com/macite/doubtfire-deploy/commit/2ec1b06b5330c5e142cc3fc01c49c2aded5411cb))
+* add tutor discussion qr scanning ([e549c4e](https://github.com/macite/doubtfire-deploy/commit/e549c4e0ac9377bd864c81122cffce004681c845))
+* auto send dicussed in class comment ([fbf90c7](https://github.com/macite/doubtfire-deploy/commit/fbf90c7fdfa440f565e2c8abae4ae62bc1fe70eb))
+* **comments:** enhanced drafting for messages using localStorage ([f331734](https://github.com/macite/doubtfire-deploy/commit/f33173409f0804da4de24e1e6ec53971896b28e9))
+* **comments:** enhanced drafting for messages using localStorage ([b6f6831](https://github.com/macite/doubtfire-deploy/commit/b6f683175db6ab9549b8b4481ab54bb89700016a))
+* edit staff note ([b2d8ba4](https://github.com/macite/doubtfire-deploy/commit/b2d8ba41b1dc467a03728d5df4ede6ac6fb3c9a1))
+* fetch new comments on task switching and status updates ([1b7da69](https://github.com/macite/doubtfire-deploy/commit/1b7da692423acaf53b96229b665b971360bacc89))
+* force scanner to use back camera, enhance ux ([e5799b1](https://github.com/macite/doubtfire-deploy/commit/e5799b18ec0603123b6a99b3733919d2611b4fbd))
+* highlight selected task ([25b361f](https://github.com/macite/doubtfire-deploy/commit/25b361f699ad54e45c32167ccae230635a6b2758))
+* indicate reply has been deleted ([c3e288a](https://github.com/macite/doubtfire-deploy/commit/c3e288ae95e2fa5fd43bdf3922e81501f26e1baf))
+* init qr tutor marking state ([41d1d61](https://github.com/macite/doubtfire-deploy/commit/41d1d61a417573e33c4344898ae6d7562c459004))
+* init reply to button ([100a533](https://github.com/macite/doubtfire-deploy/commit/100a5335aa7f37f4477b9d9b8f43806ca32c640b))
+* init staff notes ([c7eed99](https://github.com/macite/doubtfire-deploy/commit/c7eed9903bcc4463f4d0b52889e0d3d9045b03ba))
+* jplag report download button in task explorer view ([16670a2](https://github.com/macite/doubtfire-deploy/commit/16670a27d5dc544ae828da85cda48358cab4d75e))
+* link note that is being replied to ([27726b3](https://github.com/macite/doubtfire-deploy/commit/27726b3f1368031c24bf069bde4f930dd02c1e80))
+* load jplag report viewer into iframe ([1360438](https://github.com/macite/doubtfire-deploy/commit/13604383b403dbf82b013786127a72fab08ac895))
+* localized date time pipe ([5cf752b](https://github.com/macite/doubtfire-deploy/commit/5cf752baf1b9de6105ea2a951ec75818ce1e0cfc))
+* mark task as discussed ([a54f94f](https://github.com/macite/doubtfire-deploy/commit/a54f94f045b4280d0f13304a6b5728b62fc4107e))
+* message tooltip timestamps ([2d79007](https://github.com/macite/doubtfire-deploy/commit/2d79007c033d56218326f55dd8d6821e17fec74f))
+* native jplag report viewer state ([8f6bc6a](https://github.com/macite/doubtfire-deploy/commit/8f6bc6ae5b80502b5d81b65bab2be3aaa56ce42f))
+* qr code generation for student projects ([6f8b94f](https://github.com/macite/doubtfire-deploy/commit/6f8b94f7c0b48d292dfe28871ff7ae7fda86b2b9))
+* redirect back to home if exiting the scanner ([71f09ae](https://github.com/macite/doubtfire-deploy/commit/71f09ae77da6f0a2f5ed135071c92c110940c2f5))
+* reply to ui ([40078df](https://github.com/macite/doubtfire-deploy/commit/40078df477bd300c9b8897976d10658fb91a6d04))
+* show number of notes project has ([f3d4c4c](https://github.com/macite/doubtfire-deploy/commit/f3d4c4c00128ca00dec18ea9950c01df25b75b61))
+* staff note deletion ([0ab1ebf](https://github.com/macite/doubtfire-deploy/commit/0ab1ebff3964d8665d9560bf3007a2d30109ee37))
+* switch to staff note tab view ([65f8c71](https://github.com/macite/doubtfire-deploy/commit/65f8c71bd37ef71481bd14f591af263470987a6e))
+* task and unit ilos ui ([1cef438](https://github.com/macite/doubtfire-deploy/commit/1cef438b458efcee4adce01cc81648d32bb97f35))
+* view filtered tasks only button ([a4e422d](https://github.com/macite/doubtfire-deploy/commit/a4e422dcf3dfa329b83cd7e3d521fc3665c9c45f))
+* view staff notes in portfolio assessment ([020ab5b](https://github.com/macite/doubtfire-deploy/commit/020ab5b9a98a401835e41bee7abc3dee70978c07))
+
+
+### Bug Fixes
+
+* allow oversized images to be scrollable ([8ff67d2](https://github.com/macite/doubtfire-deploy/commit/8ff67d2f8e483813bc3af0dc2429b58fe16f8f2b))
+* avoid fetching twice if no comments are cached yet ([d219d49](https://github.com/macite/doubtfire-deploy/commit/d219d49ca6457119084d4c3d2cb39343dece5aa0))
+* decode url from qr code ([31822ab](https://github.com/macite/doubtfire-deploy/commit/31822abaa4c66e2dcbc3ab74c71fe0f6e9b5861d))
+* discussed context type ([d87e620](https://github.com/macite/doubtfire-deploy/commit/d87e620ab54aa75f03d8295f0319798214ad39f0))
+* display load more button only if there are more tasks ([84d5bca](https://github.com/macite/doubtfire-deploy/commit/84d5bcaa4f6329ed34f30a58d88e18fce9c3a76d))
+* enable unit name text wrapping ([5f779b8](https://github.com/macite/doubtfire-deploy/commit/5f779b8cb626ada2e8b6488812424813cc5e9913))
+* ensure jplag iframe extends to full height ([7ae6466](https://github.com/macite/doubtfire-deploy/commit/7ae6466e6586ceaf58edec8a741c9e2f8a737c0c))
+* ensure scanner doesn't break on ios ([a0b010e](https://github.com/macite/doubtfire-deploy/commit/a0b010ef31161b6cf2ab1038deb82c199ffb9ecb))
+* improve task similarity notice ui ([ba26841](https://github.com/macite/doubtfire-deploy/commit/ba2684192c2a8a139237affc4c0702af901f1808))
+* match comments-modals's original max width ([7edb445](https://github.com/macite/doubtfire-deploy/commit/7edb445d7bc5f61daba1b47a3a36b633b551267e))
+* merge conflict ([e599fe3](https://github.com/macite/doubtfire-deploy/commit/e599fe35ec338cfd7f5ffc419a3379d3f3160312))
+* show edit icon for authors only ([a83a875](https://github.com/macite/doubtfire-deploy/commit/a83a875f25efbe9297fe76d773ef4ff55456bcf8))
+* support markdown ([3ac10f6](https://github.com/macite/doubtfire-deploy/commit/3ac10f6cab39dab42ce19f61029488d220786703))
+* **task-comment-composer:** deleting the key when text is empty ([6ef91e0](https://github.com/macite/doubtfire-deploy/commit/6ef91e0ddb119e03dbaf2b6e53e450e5bcc58b8b))
+* update discussion route ([b5c35d9](https://github.com/macite/doubtfire-deploy/commit/b5c35d9cb0be11a6c6cbd49d9e6aee37bb5b83cd))
+* welcome page redirect issues ([14ad26e](https://github.com/macite/doubtfire-deploy/commit/14ad26e8ed4acf6f77043447a2691360fd62ce51))
+
+## [10.0.0-6](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-5...v10.0.0-6) (2025-06-13)
+
+
+### Features
+
+* add spec con and flexible dates ([a874cff](https://github.com/macite/doubtfire-deploy/commit/a874cff846e788a1820da7e19899ecb20a113888))
+
+
+### Bug Fixes
+
+* only get task feedback templates if staff ([8341b93](https://github.com/macite/doubtfire-deploy/commit/8341b933487ccd451a15cb8f7ec6afc1695cb121))
+* remove unnecessary safe pipe ([c92242a](https://github.com/macite/doubtfire-deploy/commit/c92242ad9afb0a934d4e8fb00afee0c69782c625))
+
+## [10.0.0-5](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-4...v10.0.0-5) (2025-05-30)
+
+
+### Bug Fixes
+
+* correct broken transition hooks ([d237eda](https://github.com/macite/doubtfire-deploy/commit/d237eda82dd989fc1b69d6e4236932050bc5b1a4))
+* delay authorisation code until after authentication ([34410d4](https://github.com/macite/doubtfire-deploy/commit/34410d4afdf5bd73d9c244a6f40c7fb78b714e63))
+* ensure transition in student pages update pdf view ([68a2f38](https://github.com/macite/doubtfire-deploy/commit/68a2f38167c792974aafd907f04fcf9e8b5bf091))
+* simplify setting of auto redirect on login ([60dbceb](https://github.com/macite/doubtfire-deploy/commit/60dbceb2a104894b4eff157c038446796bf01708))
+
+## [10.0.0-4](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-3...v10.0.0-4) (2025-04-15)
+
+
+### Bug Fixes
+
+* ensure sign in waits for global load ([fb6e61f](https://github.com/macite/doubtfire-deploy/commit/fb6e61f874797cd48e77f2a8d0a32f345f5b785b))
+
+## [10.0.0-3](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-2...v10.0.0-3) (2025-04-10)
+
+
+### Bug Fixes
+
+* correct auth callback ([2e087d2](https://github.com/macite/doubtfire-deploy/commit/2e087d267c8bfde633550823aed460379a643ad9))
+
+## [10.0.0-2](https://github.com/macite/doubtfire-deploy/compare/v10.0.0-1...v10.0.0-2) (2025-04-10)
+
+
+### Bug Fixes
+
+* correct handling of errors on auth ([622768a](https://github.com/macite/doubtfire-deploy/commit/622768a06112910d5ba79061689fc0ba486982a5))
+
+## [10.0.0-1](https://github.com/macite/doubtfire-deploy/compare/v8.0.37...v10.0.0-1) (2025-04-10)
+
+
+### Features
+
+* add comment text to comment area ([5c45415](https://github.com/macite/doubtfire-deploy/commit/5c45415eafb38e221963781e4575480294d74e09))
+* add connected learning outcomes ([9532d9f](https://github.com/macite/doubtfire-deploy/commit/9532d9ffe620d67d7bc83137d59e0a91ecf8a73a))
+* add editor for task feedback templates ([3da7a4b](https://github.com/macite/doubtfire-deploy/commit/3da7a4b54a5892ea99cd365ae439677b0f52ed6f))
+* add feedback template picker to comment composer ([9acc365](https://github.com/macite/doubtfire-deploy/commit/9acc36502dc3fd2365817f73ccc742dd3947e293))
+* add field to connect outcomes ([db2d2bc](https://github.com/macite/doubtfire-deploy/commit/db2d2bc32b19165bac8ff6ba0282a16adffbb815))
+* add glo editor ([4efe876](https://github.com/macite/doubtfire-deploy/commit/4efe87694a426c4b6a55ef943576fe47498166e5))
+* add greeting and summary chip ([ed68317](https://github.com/macite/doubtfire-deploy/commit/ed683174ab7b330a48b2aeacdd65012396357479))
+* add option for institution logo ([5141529](https://github.com/macite/doubtfire-deploy/commit/5141529b1ed37f8ca254e89e1b91edac3139f166))
+* add startpage property to auto scroll to on pdf render ([90830cd](https://github.com/macite/doubtfire-deploy/commit/90830cdba73bcfe564362015b5abaf2c40810f5f))
+* combine outcome and template editor ([c12536b](https://github.com/macite/doubtfire-deploy/commit/c12536bc4375b1ae5f19d4e54d0783b6948921eb))
+* edit feedback chips ([04f8dce](https://github.com/macite/doubtfire-deploy/commit/04f8dce30f79d86637abd6db2aa9fa3aa14d1de2))
+* **feedback-template:** add hierarchical sorting for feedback templates ([8db1844](https://github.com/macite/doubtfire-deploy/commit/8db1844402d080902a9d3938ecc04dd7a2f0340f))
+* **feedback-template:** improve sorting logic for feedback templates ([d3f156a](https://github.com/macite/doubtfire-deploy/commit/d3f156a640e6a3da99c147c4c2303a1af3050f8f))
+* **feedback-template:** merge upstream changes ([0fb1d8a](https://github.com/macite/doubtfire-deploy/commit/0fb1d8abfa3264befddd97e1ce0832359866919d))
+* fetch feedback chips when selecting task in unit editor ([9caeda4](https://github.com/macite/doubtfire-deploy/commit/9caeda4e71946ddb7907027ebf7a54397f11606f))
+* init pdf viewer on page 2 for student submissions ([45cc89e](https://github.com/macite/doubtfire-deploy/commit/45cc89e18cff8fcde66480448eea178940d316a2))
+* make template editor reusable across contexts ([a42f177](https://github.com/macite/doubtfire-deploy/commit/a42f177f61b3f7166edf3b9c93dfd2dcd3949df7))
+* modify fields and operate on learning outcomes ([65ffc4f](https://github.com/macite/doubtfire-deploy/commit/65ffc4f8d61a9d0b2cc7c5f8d13db932436fc4f5))
+* persistent pdf zoom level ([1348da2](https://github.com/macite/doubtfire-deploy/commit/1348da2d345b080002a62a24d717fac15c4b0a92))
+* rebuild auth with refresh token ([5d3a36e](https://github.com/macite/doubtfire-deploy/commit/5d3a36e8f16e8993c5607241112c490361a31c97))
+* show chips on picker ([e54216f](https://github.com/macite/doubtfire-deploy/commit/e54216f8472b67cf9f2ff69c9bcb3355efa90081))
+* suggest task status according to feedback ([95796da](https://github.com/macite/doubtfire-deploy/commit/95796da4e7855bcd76023754d95cec8d033668fc))
+
+
+### Bug Fixes
+
+* adjust ui for nested csv modal ([d7fac6f](https://github.com/macite/doubtfire-deploy/commit/d7fac6f0fb23f060373fd4c40f5e82b960098c77))
+* check difference before updating linked outcomes ([aa0bc76](https://github.com/macite/doubtfire-deploy/commit/aa0bc76d32656dc14d28ebca520e4f90d6d9434e))
+* check for unit context for template csv name ([6ecec84](https://github.com/macite/doubtfire-deploy/commit/6ecec8483222304161f9cda87a8bdf3ecd774bcb))
+* comment console logs ([e86162d](https://github.com/macite/doubtfire-deploy/commit/e86162d1592d8418bab3c889b1d0bbfe3662b86d))
+* correct a typo in /edit_profile ([4b7901f](https://github.com/macite/doubtfire-deploy/commit/4b7901fd03d9652230aecb52791313f052d7b53d))
+* correct string manipulation for task period formatting ([aa06cbf](https://github.com/macite/doubtfire-deploy/commit/aa06cbf3a08efa02f508062d47c127289fc8c6fa))
+* display feedback picker chips in color ([6ae0771](https://github.com/macite/doubtfire-deploy/commit/6ae077149d16e5af538d432a9ce2db974298b549))
+* display nested feedback groups in picker ([db93b18](https://github.com/macite/doubtfire-deploy/commit/db93b187cfe6e197a3b6243445809ec6d767d4bd))
+* display task status in coloured chip in feedback editor ([73986e8](https://github.com/macite/doubtfire-deploy/commit/73986e89839603b19f643404e5a48964ac9a3c1b))
+* exclude greeting and summary from selected ([1978e54](https://github.com/macite/doubtfire-deploy/commit/1978e5410f6aa9314252ae3cf922917033f551f0))
+* feedback picker search ([3a5e02e](https://github.com/macite/doubtfire-deploy/commit/3a5e02e2f7864b9e1ea4f8d49ce69d23addbcc5a))
+* **feedback-template-editor:** add missing operator ([a1f220c](https://github.com/macite/doubtfire-deploy/commit/a1f220c9f5462a2b91035eb936f3ff25b7d4cd67))
+* **feedback-template-editor:** improve feedback template sorting logic ([bd48c40](https://github.com/macite/doubtfire-deploy/commit/bd48c40c4c3b0a3d67d41373b624ed8b3b754f4a))
+* **feedback-template:** ensure sorting defaults are applied ([96f7555](https://github.com/macite/doubtfire-deploy/commit/96f7555f9dfcf4c62ee3dd54b9e9e74087bec47a))
+* focus on composer input after selecting chip ([95afdff](https://github.com/macite/doubtfire-deploy/commit/95afdff12aac914bb8af8727f8b8d960bc3b072c))
+* hide feedback group comment text ([55a6625](https://github.com/macite/doubtfire-deploy/commit/55a662584c4b8b6123335bcbc5149a9f0f0701d8))
+* hide picker on task change ([198b7f9](https://github.com/macite/doubtfire-deploy/commit/198b7f9d0ccad631059ed5e1ad5487604582074b))
+* hide summary instead of comment text for groups ([7644cba](https://github.com/macite/doubtfire-deploy/commit/7644cba92f92f5c261501c1907f7f5418538e3a7))
+* hide template editor on outcome switch ([a3706e9](https://github.com/macite/doubtfire-deploy/commit/a3706e9394b78edf9ec15030246c98acca9d3852))
+* lock ng2-pdf-viewer to 10.2.2 ([2cf6215](https://github.com/macite/doubtfire-deploy/commit/2cf62152524ec728c73aaa23f678bbce9ae6b4d1))
+* only get feedback chips if staff ([7f4381e](https://github.com/macite/doubtfire-deploy/commit/7f4381e1179400642821068d1acdba346a2969a2))
+* organise outcome and feedback csv functionality ([641f1d9](https://github.com/macite/doubtfire-deploy/commit/641f1d9f75177d48b3911c300e23c901e0a603e1))
+* prevent saving empty items ([0eed547](https://github.com/macite/doubtfire-deploy/commit/0eed5477940db511cee09bbdb7ccb0adde57cbbf))
+* refine feedback editor ([f410cfe](https://github.com/macite/doubtfire-deploy/commit/f410cfe47b0c242f1a164c6345bc4d29f5659f6a))
+* refresh context when changing task defs ([c078bcf](https://github.com/macite/doubtfire-deploy/commit/c078bcf40198c5495d4c94354fab411d3e0ee934))
+* remove duplicate alert ([955d0a1](https://github.com/macite/doubtfire-deploy/commit/955d0a17ae23be77954145ba189ac8bb0aa90da0))
+* remove glo outcome specific csv buttons ([579c491](https://github.com/macite/doubtfire-deploy/commit/579c49170808a6fcca1bfd67f0923f0339255463))
+* remove selected outcomes from dropdown ([5494384](https://github.com/macite/doubtfire-deploy/commit/5494384f1a4becf462dd031e89fae2734ed46e55))
+* reply to position and wrapping ([e065c46](https://github.com/macite/doubtfire-deploy/commit/e065c46c3fe6689a7b295a3f91a576eabc11f7bd))
+* reset template table for new outcome ([7585c1c](https://github.com/macite/doubtfire-deploy/commit/7585c1c60ca8fe168084ec3d857aec4856bfb0db))
+* save connected outcomes ([eb3cf6d](https://github.com/macite/doubtfire-deploy/commit/eb3cf6daf99b893642336efb48ed58bc9c8b8207))
+* save linked outcomes upon selection ([07f103d](https://github.com/macite/doubtfire-deploy/commit/07f103de46defa2a78f5d7c43030e67255c0d5fe))
+* save to proper cache and clean up ([80edff2](https://github.com/macite/doubtfire-deploy/commit/80edff2ef04137b3d38ccdd252788cfb40f458f0))
+* show description for connected outcome selection ([0f2c164](https://github.com/macite/doubtfire-deploy/commit/0f2c164579491298b81849cc110301229f2ca5af))
+* show picker above composer and below comments ([ef524f1](https://github.com/macite/doubtfire-deploy/commit/ef524f1b048b3bfe1781307a34e1a5fe364943a4))
+* show proper selected outcome or template ([0ac3881](https://github.com/macite/doubtfire-deploy/commit/0ac3881e7f6d0f5b7ddeb85e5e8f9e6835c5dba6))
+* support hot reloading on windows ([247330e](https://github.com/macite/doubtfire-deploy/commit/247330e46d74186519dbcbbef8378b508eaca83f))
+* support windows build ([3594528](https://github.com/macite/doubtfire-deploy/commit/35945288c73052afd28209a7381335af2b14ba10))
+* template editor not showing if new ([6cfe379](https://github.com/macite/doubtfire-deploy/commit/6cfe37999fa7f484e1b89f4dbe657b99907adae1))
+* tlo csv url ([73588d8](https://github.com/macite/doubtfire-deploy/commit/73588d886118861bbd3e5ae380cddc7baa532dbb))
+* update packages ([e5a57f8](https://github.com/macite/doubtfire-deploy/commit/e5a57f8ba3669345a272b308b0c892fdab4843ef))
+* upon save update entity in frontend ([a128744](https://github.com/macite/doubtfire-deploy/commit/a1287449b945295ced60e86a21bcaab67ed0f9bd))
+* use icon instead of color for template task status ([11a3993](https://github.com/macite/doubtfire-deploy/commit/11a399324ee64af901f44f28de3526cbd7650bac))
+* use task status color for recommended button ([174cde0](https://github.com/macite/doubtfire-deploy/commit/174cde0022d0c728d2c6408d40129d6c343aa91d))
+
+### [8.0.37](https://github.com/macite/doubtfire-deploy/compare/v8.0.36...v8.0.37) (2025-03-10)
+
+
+### Bug Fixes
+
+* ensure overseer download get resources zip ([a1e4d71](https://github.com/macite/doubtfire-deploy/commit/a1e4d71b34733bc53cd2cfa63ee1e70f8f43386f))
+
+### [8.0.36](https://github.com/macite/doubtfire-deploy/compare/v8.0.35...v8.0.36) (2025-03-07)
+
+
+### Bug Fixes
+
+* overseer image admin in unit and task ([72c89b3](https://github.com/macite/doubtfire-deploy/commit/72c89b30cca426d56c7efbbac34e00fd734fe714))
+
+### [8.0.35](https://github.com/macite/doubtfire-deploy/compare/v8.0.34...v8.0.35) (2025-02-28)
+
+
+### Bug Fixes
+
+* correct ability to edit teaching periods ([ce37bf2](https://github.com/macite/doubtfire-deploy/commit/ce37bf27689744a6f43be46912be0d14e2cdbc6b))
+* ensure an old grade is present in portfolio assessment ([7daacd1](https://github.com/macite/doubtfire-deploy/commit/7daacd1bf0af050aeb6db503d963e574f68b9a94))
+* ensure overseer images can be edited ([3479b7a](https://github.com/macite/doubtfire-deploy/commit/3479b7af589b9ba986ddbb7dc8a84fd0be3e66e6))
+
+### [8.0.34](https://github.com/macite/doubtfire-deploy/compare/v8.0.33...v8.0.34) (2025-02-12)
+
+
+### Bug Fixes
+
+* enhance rollover messages ([48348c8](https://github.com/macite/doubtfire-deploy/commit/48348c8e6a7ffdb4186fe3e6d27fe860076922e1))
+
+### [8.0.33](https://github.com/macite/doubtfire-deploy/compare/v8.0.32...v8.0.33) (2025-01-29)
+
+
+### Bug Fixes
+
+* nginx conf redirect for spa ([95c0ca9](https://github.com/macite/doubtfire-deploy/commit/95c0ca9384e5a0fe7e522f61146b711b706712c1))
+
+### [8.0.32](https://github.com/macite/doubtfire-deploy/compare/v8.0.31...v8.0.32) (2025-01-29)
+
+
+### Bug Fixes
+
+* correct issue fetching user from cookie ([0d63775](https://github.com/macite/doubtfire-deploy/commit/0d63775339dcbb648d7c8702b1c3428b8622a704))
+
+### [8.0.31](https://github.com/macite/doubtfire-deploy/compare/v8.0.30...v8.0.31) (2025-01-28)
+
+
+### Bug Fixes
+
+* ensure test attempt work for staff students ([06bfa71](https://github.com/macite/doubtfire-deploy/commit/06bfa714d0b54938231004a29ae11e68fe515d80))
+* serve index.html for unknown paths ([d07ad26](https://github.com/macite/doubtfire-deploy/commit/d07ad26bd7e7bd8ba7c7877a1208a8f82958c72b))
+
+### [8.0.30](https://github.com/doubtfire-lms/doubtfire-deploy/compare/v8.0.29...v8.0.30) (2025-01-22)
+
+
+### Features
+
+* add ability to collect d2l org and grade object ids ([93fd614](https://github.com/doubtfire-lms/doubtfire-deploy/commit/93fd6144c798fd302204728a6c2dd43f74d7bf91))
+* add ability to trigger d2l grade transfer ([fd0cc07](https://github.com/doubtfire-lms/doubtfire-deploy/commit/fd0cc0737682e9aa02af14f858c8a5f5f27f22e7))
+* add success page for oauth callback ([921406e](https://github.com/doubtfire-lms/doubtfire-deploy/commit/921406ed595c7a74cb750bc87d49fac0337bdb8a))
+* check if d2l results available ([2460c88](https://github.com/doubtfire-lms/doubtfire-deploy/commit/2460c88af08740f8626ed71d8851a75af095a03d))
+* d2l integration checks settings ([0a61df5](https://github.com/doubtfire-lms/doubtfire-deploy/commit/0a61df53c9fc187b7462e8564a19641ac1693cf0))
+* switch to html5 mode ([9d017fb](https://github.com/doubtfire-lms/doubtfire-deploy/commit/9d017fbfaa1d5bae7f43da37ec7ae43e1dfafa8f))
+
+
+### Bug Fixes
+
+* ensure standard error propagate to the ui ([fd056db](https://github.com/doubtfire-lms/doubtfire-deploy/commit/fd056db47631087d5ef29223bd3dc9037c4b10dd))
+* report better errors during file downloads ([cf87d68](https://github.com/doubtfire-lms/doubtfire-deploy/commit/cf87d68ea3a5433a073ae6db413f8c1f825ada80))
+
+### [8.0.29](https://github.com/macite/doubtfire-deploy/compare/v8.0.28...v8.0.29) (2024-10-25)
+
+
+### Features
+
+* add support for the view language ([151ca04](https://github.com/macite/doubtfire-deploy/commit/151ca04ca8da9059890d7846293bf34ae67f05c5))
+
+
+### Bug Fixes
+
+* correct task scroll into view ([7984b39](https://github.com/macite/doubtfire-deploy/commit/7984b393752381e0a219dbbdafc0e7b44c3fcdb3))
+
+### [8.0.27](https://github.com/macite/doubtfire-deploy/compare/v8.0.26...v8.0.27) (2024-08-09)
+
+
+### Bug Fixes
+
+* encode . in the username in scorm player ([1ec1754](https://github.com/macite/doubtfire-deploy/commit/1ec175419ab3ef80f22ac8a6384f6ed40a17c217))
+
+### [8.0.26](https://github.com/macite/doubtfire-deploy/compare/v8.0.25...v8.0.26) (2024-08-09)
+
+
+### Bug Fixes
+
+* register service names in state resolve functions ([64e41f4](https://github.com/macite/doubtfire-deploy/commit/64e41f4aedcf519a2786d7fe4929900f86731896))
+
+### [8.0.25](https://github.com/macite/doubtfire-deploy/compare/v8.0.24...v8.0.25) (2024-08-09)
+
+
+### Bug Fixes
+
+* adjust shortcuts and unregister on inbox destroy ([8a2ce3b](https://github.com/macite/doubtfire-deploy/commit/8a2ce3bfe32d99be1bc0667ca1aba55d36f987d2))
+* switch to control shift ([a0c1185](https://github.com/macite/doubtfire-deploy/commit/a0c1185bf4af3acb8f58c97cc27021c952861603))
+
+### [8.0.24](https://github.com/macite/doubtfire-deploy/compare/v8.0.23...v8.0.24) (2024-08-09)
+
+
+### Features
+
+* add ability to preview scorm test ([7ffbea9](https://github.com/macite/doubtfire-deploy/commit/7ffbea9f56c393d44571c18f30735e090701b554))
+* add new Numbas Feature ([6f1ac4e](https://github.com/macite/doubtfire-deploy/commit/6f1ac4e4c1a78115c426838bce350bce286e44f0))
+* add new Numbas Feature ([8eba913](https://github.com/macite/doubtfire-deploy/commit/8eba913ec4462e1c252a8a698b4b6de67c4ec25f))
+* add numbas component ([7c1734b](https://github.com/macite/doubtfire-deploy/commit/7c1734b137ac369b3b605b38749c845df38b9a78))
+* add Numbas config options to task def service keys ([ff28e48](https://github.com/macite/doubtfire-deploy/commit/ff28e4802b86dc22be339cc196ef82ade67934f7))
+* add Numbas test attempt model ([097df79](https://github.com/macite/doubtfire-deploy/commit/097df7960d34a8c905ee495b8c6c8b8843e43b36))
+* add Numbas test section on ready for feedback ([e61295c](https://github.com/macite/doubtfire-deploy/commit/e61295c50006dbc89cfc0639bb3c68a09a3cda9d))
+* add Numbas test upload section and reorder editor sections ([edbd536](https://github.com/macite/doubtfire-deploy/commit/edbd536e73ace8b6b4cced1481c809fd36524dce))
+* add Numbas upload component and related functions to task-definition model ([4ecaee8](https://github.com/macite/doubtfire-deploy/commit/4ecaee8ad1c0caed9a5d848066a173000989f79b))
+* add test attempt service ([a576f48](https://github.com/macite/doubtfire-deploy/commit/a576f484bc434728eab9632c022bccf1ed26cb01))
+* add test attempt service and minor numbas related changes ([0652b56](https://github.com/macite/doubtfire-deploy/commit/0652b56f69eb850c2dfb43be54d07beb4c4eb469))
+* added numbas-lms service code ([471d344](https://github.com/macite/doubtfire-deploy/commit/471d34486f2582e95aac84469187f087277cc079))
+* allow changing scorm review config and add minor UI changes ([fc023af](https://github.com/macite/doubtfire-deploy/commit/fc023af462656e3557e2970f211fa4d59ee1e3d5))
+* change Numbas time delay config to enable incremental delays ([0afa719](https://github.com/macite/doubtfire-deploy/commit/0afa7197293c90a154c1716db91ecadae3677d53))
+* disable attempt button if passed and add button to review latest attempt in card ([703563c](https://github.com/macite/doubtfire-deploy/commit/703563c86253c60ad30d451ee2d8e0fa7ebbfabb))
+* display numbas task comments ([48a31da](https://github.com/macite/doubtfire-deploy/commit/48a31da1442f1e14f497c614053d9002c9f2631b))
+* enable reviewing, passing, deleting test attempts and add test attempt model and service ([561b924](https://github.com/macite/doubtfire-deploy/commit/561b9241c2f44fd69d3f09c656a025f514bbaf3a))
+* enable students to request extra scorm attempt ([d904ffd](https://github.com/macite/doubtfire-deploy/commit/d904ffd6fd674f6e61894d517f5aa147d1db6d29))
+* get unique token for scorm asset retrieval ([8fe2f9e](https://github.com/macite/doubtfire-deploy/commit/8fe2f9e9ca60c02e55c24d0f2bb3eb6dbea8217c))
+* implement numbas test data upload in task definition service ([2c7dab5](https://github.com/macite/doubtfire-deploy/commit/2c7dab555fc9a226b980a8dae96f5738bf517b1b))
+* insert Numbas test rules options in the task editor ([7e52ad5](https://github.com/macite/doubtfire-deploy/commit/7e52ad5e5759291d7d61805070ec482eb49c90be))
+* numbas-test-numbas-service ([cee13b7](https://github.com/macite/doubtfire-deploy/commit/cee13b727b35f804fc16070885f0e57c422c9982))
+* prevent uploading files until scorm passed ([ec86e4e](https://github.com/macite/doubtfire-deploy/commit/ec86e4eca8e9c50ebdaf54c34c302da88dd44b31))
+* show banner based on scorm success status ([db16172](https://github.com/macite/doubtfire-deploy/commit/db161721fab1359694c44d8df7237cd01892938b))
+* show launch button on ready for feedback if Numbas test is enabled for the task ([17af5b7](https://github.com/macite/doubtfire-deploy/commit/17af5b7fe9e1fbc8e5f18e2fece6e029b017408c))
+* use confirmation modal when passing or deleting test attempts ([3fb25bb](https://github.com/macite/doubtfire-deploy/commit/3fb25bb373acab837a3ef787620dd654bfd6803f))
+
+
+### Bug Fixes
+
+* add accepted Numbas file types ([bcaa8af](https://github.com/macite/doubtfire-deploy/commit/bcaa8af150aaf68b5cf5fb07ad9cb72037212d5d))
+* add auth headers to scorm adapter xhr requests ([97e1ea1](https://github.com/macite/doubtfire-deploy/commit/97e1ea187b769a51ebb66b82d76f21193bb29386))
+* adjusted edit profile accidental change ([316abc7](https://github.com/macite/doubtfire-deploy/commit/316abc775eeba6ca846c7c742bc88bec61d05a5e))
+* change success status descriptions ([20da042](https://github.com/macite/doubtfire-deploy/commit/20da0423450fe3b9b8006660bf6f5e06670f520c))
+* delete comment as well as test attempt ([b6887e8](https://github.com/macite/doubtfire-deploy/commit/b6887e85b7a06b166519924dd682ef57650a4e32))
+* disable launch scorm test button if user is staff ([9bc48b0](https://github.com/macite/doubtfire-deploy/commit/9bc48b03905c0a839d78ea13be9817ca73ba54cf))
+* ensure counters are incremented after object creation ([2b1dcfc](https://github.com/macite/doubtfire-deploy/commit/2b1dcfc717eb770dd623c62ac99d8d961d1c3124))
+* ensure datamodel is updated on termination ([2ac487f](https://github.com/macite/doubtfire-deploy/commit/2ac487f13c3743f2b5b805521964bdcbca7cdc15))
+* ensure scorm frame loads when src is available ([6ae4295](https://github.com/macite/doubtfire-deploy/commit/6ae42954162996acf145cef211981153381ff49d))
+* hide config options if numbas test is disabled ([0b15b1c](https://github.com/macite/doubtfire-deploy/commit/0b15b1c38b8f09a30f53bb42dccd1971109bce01))
+* indicate task def has scorm data when zip is uploaded ([4b983ba](https://github.com/macite/doubtfire-deploy/commit/4b983bae1f522013f750b8c36d5e1a52624abbbc))
+* initialise SCORM API wrapper before iframe loads ([5d0606c](https://github.com/macite/doubtfire-deploy/commit/5d0606c56eaeb929d5873cae7038ce729581512c))
+* integrate Numbas services well with the existing system ([d47b8ed](https://github.com/macite/doubtfire-deploy/commit/d47b8edc745660801984346c4fa67d6bb371f4cb))
+* marking shortcuts no longer conflict with common browser shortcuts ([ec7524a](https://github.com/macite/doubtfire-deploy/commit/ec7524aaae040794de76521363c2d885a0425db0))
+* remove attempt number field ([f0ff40b](https://github.com/macite/doubtfire-deploy/commit/f0ff40bfd7e86b904adae94bf8cbe5d56371e011))
+* remove saved scorm token and always get ([c8fc702](https://github.com/macite/doubtfire-deploy/commit/c8fc702bde6f1c448fc585042e5265625552bae8))
+* retrieve test attempt data correctly ([2810ce6](https://github.com/macite/doubtfire-deploy/commit/2810ce65d423a137600d621a98bf27dbc221921c))
+* send task id with numbas completed attempt data ([e46214d](https://github.com/macite/doubtfire-deploy/commit/e46214dd59d86014bb04d1c31f3a3bf21240e32b))
+* show correct attempts left and allow tutor to review attempt always ([58c24c3](https://github.com/macite/doubtfire-deploy/commit/58c24c3a6760af168a918bc099e755f475eac5f0))
+* show correct Numbas test from the task def with all assets loaded ([bee0a0b](https://github.com/macite/doubtfire-deploy/commit/bee0a0bb1eb905c964caf7888419effe70553520))
+* show delete and download buttons in editor when Numbas test exists ([821feb9](https://github.com/macite/doubtfire-deploy/commit/821feb93a8a40d095ef4f50adedbfd9216278fa9))
+* show Numbas button component and modify iframe request ([c9c2fbe](https://github.com/macite/doubtfire-deploy/commit/c9c2fbe10db156512946355f3cabfe54461f0eba))
+* show Numbas iframe on top of other elements ([f53befd](https://github.com/macite/doubtfire-deploy/commit/f53befd82b52bba24bc8c593a9266c11f9155d32))
+* show previously configured Numbas attempt limit ([56e1a5d](https://github.com/macite/doubtfire-deploy/commit/56e1a5de44ee275f9544b77909b7472f1020c2c3))
+* update numbas api path ([3df59dc](https://github.com/macite/doubtfire-deploy/commit/3df59dcc58f021ef16d81938d1d05473818bc75e))
+* use modal for Numbas and enable authentication ([64b1bfb](https://github.com/macite/doubtfire-deploy/commit/64b1bfb2918993e58a6659948d9621fc7d0b8ba4))
+* use nullish coalescing when retrieving data from the datamodel ([226d919](https://github.com/macite/doubtfire-deploy/commit/226d9193251fb675c92bec8265508477857a4ec9))
+
+### [8.0.28](https://github.com/macite/doubtfire-deploy/compare/v8.0.23...v8.0.28) (2024-09-05)
+
+
+### Features
+
+* add support for the view language ([b4b3654](https://github.com/macite/doubtfire-deploy/commit/b4b36541943232f7df3ee4888bf4ca46c4c04018))
+
+### [8.0.23](https://github.com/macite/doubtfire-deploy/compare/v8.0.22...v8.0.23) (2024-08-02)
+
+
+### Features
+
+* allow pdf download and switch to native viewer ([16ac9c8](https://github.com/macite/doubtfire-deploy/commit/16ac9c8bc488607025ee95dbb4b41134a5346b00))
+
+
+### Bug Fixes
+
+* ensure overseer menu only shows when active for task ([c04e9fe](https://github.com/macite/doubtfire-deploy/commit/c04e9fe3a824e316a632b3f7b53b08887e57c831))
+
+### [8.0.22](https://github.com/macite/doubtfire-deploy/compare/v8.0.21...v8.0.22) (2024-07-26)
+
+
+### Bug Fixes
+
+* correct duplicate shortcut registration ([dca03a4](https://github.com/macite/doubtfire-deploy/commit/dca03a41f502947136e301e51242570169826dd9))
+
+### [8.0.21](https://github.com/macite/doubtfire-deploy/compare/v8.0.20...v8.0.21) (2024-07-24)
+
+
+### Bug Fixes
+
+* ensure groups can be locked ([b671065](https://github.com/macite/doubtfire-deploy/commit/b67106589b71660bdfe01151deb68d3092b9091f))
+
+### [8.0.20](https://github.com/macite/doubtfire-deploy/compare/v8.0.19...v8.0.20) (2024-07-15)
+
+
+### Bug Fixes
+
+* ensure turn it in eula is accessible by all ([3d44c11](https://github.com/macite/doubtfire-deploy/commit/3d44c115b498366b80e5b8b99ccd3eaa660ff613))
+
+### [8.0.19](https://github.com/macite/doubtfire-deploy/compare/v8.0.18...v8.0.19) (2024-07-10)
+
+
+### Features
+
+* add ability to minimise task details in task viewer ([e904640](https://github.com/macite/doubtfire-deploy/commit/e9046400e827481e09492bcb458b00b4e3aca347))
+
+
+### Bug Fixes
+
+* ensure pdf is visible in task viewer mobile/narrow ([f5388e4](https://github.com/macite/doubtfire-deploy/commit/f5388e45715597f0c92f252f721cf274bb8de0d5))
+* task def editor so save is not over fields and header visible for task being edited ([22a2616](https://github.com/macite/doubtfire-deploy/commit/22a26165c9f2ef634b3e79d1e5d5d6b74e6f2731))
+
+### [8.0.18](https://github.com/macite/doubtfire-deploy/compare/v8.0.17...v8.0.18) (2024-07-03)
+
+
+### Features
+
+* add ability to set unit code on import from rollover ([d47d105](https://github.com/macite/doubtfire-deploy/commit/d47d10561bd7bfad495f878ebb0bab399ce144b7))
+
+### [8.0.17](https://github.com/macite/doubtfire-deploy/compare/v8.0.16...v8.0.17) (2024-07-01)
+
+
+### Bug Fixes
+
+* correct broken template in activities list ([4d81fa1](https://github.com/macite/doubtfire-deploy/commit/4d81fa1fabb07e40d0d42068f23b8e756f4cdf4b))
+* correct campus list templates ([1795895](https://github.com/macite/doubtfire-deploy/commit/17958955f65bab2b268826f2a7e19f311071615b))
+* switch date formats to date-fns ([2394efb](https://github.com/macite/doubtfire-deploy/commit/2394efb07c03da18fb8114d87b0c89e68bf7fd2d))
+
+### [8.0.16](https://github.com/macite/doubtfire-deploy/compare/v8.0.15...v8.0.16) (2024-06-28)
+
+
+### Bug Fixes
+
+* ensure drop works on task resource upload in windows ([f0505d7](https://github.com/macite/doubtfire-deploy/commit/f0505d7b62b97f1b821b19b1c4178973503789da))
+
+### [8.0.15](https://github.com/macite/doubtfire-deploy/compare/v8.0.14...v8.0.15) (2024-06-28)
+
+
+### Bug Fixes
+
+* ensure zip uploads work on windows ([1857bdc](https://github.com/macite/doubtfire-deploy/commit/1857bdc708c81dc39d467f42c9e316a8664dc080))
+
+### [8.0.14](https://github.com/macite/doubtfire-deploy/compare/v8.0.13...v8.0.14) (2024-06-27)
+
+
+### Bug Fixes
+
+* ensure unit import allows unit code change ([01013d3](https://github.com/macite/doubtfire-deploy/commit/01013d3d16ab5b704acc589d34975693030def0b))
+* ensure unit load sets main convenor user in all cases ([4c96624](https://github.com/macite/doubtfire-deploy/commit/4c966245d270af46107aae94bdd38e84b2188b65))
+
+### [8.0.13](https://github.com/macite/doubtfire-deploy/compare/v8.0.12...v8.0.13) (2024-06-27)
+
+
+### Bug Fixes
+
+* update discuss text to promote use ([7b5d35c](https://github.com/macite/doubtfire-deploy/commit/7b5d35c89a577d0408a51d8390400f44615591eb))
+
+### [8.0.12](https://github.com/macite/doubtfire-deploy/compare/v8.0.11...v8.0.12) (2024-06-25)
+
+
+### Bug Fixes
+
+* ensure null fields will not break in entity mapping ([32046e8](https://github.com/macite/doubtfire-deploy/commit/32046e89eaaf2a9b36e88f078a7b695b1506886d))
+* reinstate unit import for teaching period ([991230f](https://github.com/macite/doubtfire-deploy/commit/991230f7b527957116263b6abaa588ed43d3a787))
+
+### [8.0.11](https://github.com/macite/doubtfire-deploy/compare/v8.0.10...v8.0.11) (2024-06-24)
+
+
+### Bug Fixes
+
+* ensure turn it in only appears to task editor when enabled ([3a81688](https://github.com/macite/doubtfire-deploy/commit/3a81688904159c8c9e3efdde8f3605aa5677630f))
+* task date picker allows direct entry ([2f91e8f](https://github.com/macite/doubtfire-deploy/commit/2f91e8fbba7902e1e22871cc82c87f4ff797b058))
+
+### [7.0.24](https://github.com/macite/doubtfire-deploy/compare/v7.0.23...v7.0.24) (2024-06-05)
+
+
+### Bug Fixes
+
+* ensure download blob supports 206 responses ([6445f9f](https://github.com/macite/doubtfire-deploy/commit/6445f9f998db70fbe9b9abf723dc17fd298b5d2f))
+
+### [8.0.10](https://github.com/macite/doubtfire-deploy/compare/v7.0.23...v8.0.10) (2024-06-21)
+
+
+### Bug Fixes
+
+* ensure loading screen removed in sign in component ([729c438](https://github.com/macite/doubtfire-deploy/commit/729c438f7f6a988f5ff2fa4035493b5cae1c98f7))
+* ensure portfolio can get grades from strings ([fd916b9](https://github.com/macite/doubtfire-deploy/commit/fd916b94e9012c0909c5880f377f93d326c18e4d))
+* ensure portfolio only shown when it exists ([ce970f5](https://github.com/macite/doubtfire-deploy/commit/ce970f5fdc4f42708edc71c98dc9d869056e9e2c))
+* ensure tii open report alerts errors ([c5dd45c](https://github.com/macite/doubtfire-deploy/commit/c5dd45c57fd34e8373f11d065a0cd08edef0a794))
+* fix pdf viewer for portfolios ([da376b9](https://github.com/macite/doubtfire-deploy/commit/da376b9bc7905aadeb4d234cbb4f595e4c4424a1))
+* grade icon for portfolio page ([aab1b9c](https://github.com/macite/doubtfire-deploy/commit/aab1b9c3e7365cdf7dd64d767fdb6e5a9a2436a3))
+* show burndown and pie chart on portfolio page ([e70f4c7](https://github.com/macite/doubtfire-deploy/commit/e70f4c7cd1395eaab942ee389788f75f92e985c9))
+
+### [8.0.9](https://github.com/macite/doubtfire-deploy/compare/v7.0.22...v8.0.9) (2024-05-31)
+
+
+### Bug Fixes
+
+* comment out fix and complete hotkeys temporarily ([c1f623d](https://github.com/macite/doubtfire-deploy/commit/c1f623d17432f3563366ef32fc3da3b54dbef0b7))
+
+### [8.0.8](https://github.com/macite/doubtfire-deploy/compare/v8.0.7...v8.0.8) (2024-05-30)
+
+
+### Features
+
+* added percentage to tooltip when hovering student's task progress ([69ed613](https://github.com/macite/doubtfire-deploy/commit/69ed61380bd7681d4496cb109e628111873b6e82))
+
+
+### Bug Fixes
+
+* change from hiding element to not loading ([414da65](https://github.com/macite/doubtfire-deploy/commit/414da65fcef9b10367f53112239a60e1b271d475))
+* change grade values, change grade display ([081c35b](https://github.com/macite/doubtfire-deploy/commit/081c35ba0fbc7ab4ddb2fba3c58892d0e66f325c))
+* fix task inbox shortcuts ([9bc949f](https://github.com/macite/doubtfire-deploy/commit/9bc949fd6dcb71ea165e89de9a4ec9d9347e6562))
+* inbox search box width ([02a99a8](https://github.com/macite/doubtfire-deploy/commit/02a99a8f71033014b26260d3b0dbfca075ca7273))
+* remove pdf viewer on task view when no sheet ([f3e973d](https://github.com/macite/doubtfire-deploy/commit/f3e973dacc5b079ab58da239194d802c5586ca07))
+* revert index changes, html changes for grade ([8f41cd1](https://github.com/macite/doubtfire-deploy/commit/8f41cd1656971963c2035a9249acdde0a257766e))
+* task list scrolling ([0088e9e](https://github.com/macite/doubtfire-deploy/commit/0088e9e245dbd326dea4032239b53eee88754179))
+* use tailwind classes instead of css styling ([42434a5](https://github.com/macite/doubtfire-deploy/commit/42434a5fd4b866781da5d23ecdb0b9b4369aace1))
+
+### [8.0.7](https://github.com/macite/doubtfire-deploy/compare/v8.0.6...v8.0.7) (2024-05-26)
+
+
+### Bug Fixes
+
+* add task description editing ([ee0cfbf](https://github.com/macite/doubtfire-deploy/commit/ee0cfbf7ce804a1806ca5810bad138b97703ebfe))
+
+### [8.0.6](https://github.com/macite/doubtfire-deploy/compare/v8.0.5...v8.0.6) (2024-05-25)
+
+
+### Bug Fixes
+
+* ensure all admin units are fully loaded ([917325e](https://github.com/macite/doubtfire-deploy/commit/917325ed08b89690a7eea0716438673732ba1ec9))
+
+### [8.0.5](https://github.com/macite/doubtfire-deploy/compare/v8.0.4...v8.0.5) (2024-05-25)
+
+
+### Bug Fixes
+
+* ensure (click) is on button, not mat-icon ([99c717d](https://github.com/macite/doubtfire-deploy/commit/99c717d2333de0975da1b3f282195a0b3577a3f6))
+
+### [8.0.4](https://github.com/macite/doubtfire-deploy/compare/v8.0.3...v8.0.4) (2024-05-25)
+
+
+### Bug Fixes
+
+* fix various issues with admin unit list ([3720666](https://github.com/macite/doubtfire-deploy/commit/3720666fb4105bac274449eb468ce34e0f68bae7))
+
+### [8.0.3](https://github.com/macite/doubtfire-deploy/compare/v8.0.2...v8.0.3) (2024-05-25)
+
+
+### Features
+
+* add 4-character abbreviations ([eb62dfc](https://github.com/macite/doubtfire-deploy/commit/eb62dfc90db836870d6c396727fc43c149808e9d))
+
+### [8.0.2](https://github.com/macite/doubtfire-deploy/compare/v8.0.1...v8.0.2) (2024-05-25)
+
+
+### Bug Fixes
+
+* gradeservice bugs ([fd6eb1c](https://github.com/macite/doubtfire-deploy/commit/fd6eb1cd152b72cf297cbd6bd529579858afc543))
+* gradeservice bugs ([7545eb9](https://github.com/macite/doubtfire-deploy/commit/7545eb92b9353d99ac8d8d7d89982cdadf9688b1))
+
+### [8.0.1](https://github.com/macite/doubtfire-deploy/compare/v8.0.0...v8.0.1) (2024-05-24)
+
+
+### Features
+
+* implement live PDF search that updates dynamically as you type ([b9b9945](https://github.com/macite/doubtfire-deploy/commit/b9b9945346d55392e5ee9a6431d85ae92de1bde5))
+
+
+### Bug Fixes
+
+* fix critical bugs ([356846e](https://github.com/macite/doubtfire-deploy/commit/356846e5c385c484a6034553366965cde95ec78b))
+* fix splashscreen ([2692b7f](https://github.com/macite/doubtfire-deploy/commit/2692b7f4a3e1ca2a5bc4df3b82a9dfd4aa2ec68a))
+
+## [8.0.0](https://github.com/macite/doubtfire-deploy/compare/v8.0.0-6...v8.0.0) (2024-05-23)
+
+
+### Features
+
+* add multi-badge support ([44c1473](https://github.com/macite/doubtfire-deploy/commit/44c1473700a3d7a4a025d68acac5aa61189d8b52))
+* add synchronised multi-badge ([f70ebdc](https://github.com/macite/doubtfire-deploy/commit/f70ebdca92a05e5ca350d4062a8dc8efba9fd681))
+* add working unit-badge ([dd30247](https://github.com/macite/doubtfire-deploy/commit/dd302478e97228e6bfbfdbee0134abb3eabf2587))
+* add working unit-badge ([c7338ff](https://github.com/macite/doubtfire-deploy/commit/c7338ffd88edb457a3443b43ea21706c43231973))
+* qol improvements for unit codes ([f7f928e](https://github.com/macite/doubtfire-deploy/commit/f7f928e485448a69b054dca94f07d623f84e328f))
+* small fixes to home and unit code ([918e804](https://github.com/macite/doubtfire-deploy/commit/918e8047745b489aae1fafdc67720568dd8d829e))
+
+
+### Bug Fixes
+
+* add missing imports ([659b52c](https://github.com/macite/doubtfire-deploy/commit/659b52c82a26b9496a3dba81401b5147d05248ae))
+* change time exceeded to feedback exceeded ([673619d](https://github.com/macite/doubtfire-deploy/commit/673619d99f177d834fba3643171426092e0d05e0))
+* correct task resource upload actions ([5e9c9b1](https://github.com/macite/doubtfire-deploy/commit/5e9c9b1d05d6d6975fd4ba3a3176314ddd7d8514))
+* ensure loading state changed after projects and unit roles load ([9171d0c](https://github.com/macite/doubtfire-deploy/commit/9171d0c2c37c17d26f464f6aa0ba7c479851e6eb))
+* ensure staff task list checks unit exists ([dbb312d](https://github.com/macite/doubtfire-deploy/commit/dbb312dc3c5c9e2be24161914e8d95e1a5c1bc53))
+* ensure unit load updates unit object if it already exists ([e48ffad](https://github.com/macite/doubtfire-deploy/commit/e48ffadf63891212c3e40ab696d54a3935485b08))
+* hide overseer settings if automation disabled ([0c08fe3](https://github.com/macite/doubtfire-deploy/commit/0c08fe3e82a78dd1e91fb44ce6008fb5298c46ee))
+* slide unit code off left side ([8cd57b5](https://github.com/macite/doubtfire-deploy/commit/8cd57b589812fd59c6da4de02246791fb4410959))
+* sort units desc by start date ([9b78856](https://github.com/macite/doubtfire-deploy/commit/9b788561372a3ddf065d3b28e547a2194ef29655))
+* zip upload for overseer ([5ad2085](https://github.com/macite/doubtfire-deploy/commit/5ad2085b2d730235f61e7b7c603727d8b6736cf2))
+
+## [8.0.0-6](https://github.com/macite/doubtfire-deploy/compare/v8.0.0-5...v8.0.0-6) (2024-05-13)
+
+## [8.0.0-5](https://github.com/macite/doubtfire-deploy/compare/v8.0.0-4...v8.0.0-5) (2024-05-13)
+
+## [8.0.0-4](https://github.com/macite/doubtfire-deploy/compare/v8.0.0-3...v8.0.0-4) (2024-05-11)
+
+
+### Features
+
+* add task badge ([b48cd4c](https://github.com/macite/doubtfire-deploy/commit/b48cd4c99333e41c0c1d32531e571d32bb482a81))
+
+
+### Bug Fixes
+
+* (WIP) fix alert service migration ([3d9f4dc](https://github.com/macite/doubtfire-deploy/commit/3d9f4dc05e7a45884dc383e0f5ed4bdb09b0558d))
+* add max length validator and change reason field to text-area ([6227200](https://github.com/macite/doubtfire-deploy/commit/6227200566c3f608c279e92a3f255a7e94148c1d))
+* address linter issues in task.ts ([14e9699](https://github.com/macite/doubtfire-deploy/commit/14e9699bdfe336f1e3f3cce3d722c835d58f3cdb))
+* copy dist browser to nginx html ([08fa919](https://github.com/macite/doubtfire-deploy/commit/08fa91937e028cb3ee093709e5a31f94da44fc33))
+* correct filenames for grotesk fonts ([9bedd81](https://github.com/macite/doubtfire-deploy/commit/9bedd812b12628df9cb32e48cb91a88b5cdf69b1))
+* ensure eula state remains visible ([131540c](https://github.com/macite/doubtfire-deploy/commit/131540ca4b934773afd9f72aa2578579048ed6a2))
+* ensure unit admin shows all units ([02b7a15](https://github.com/macite/doubtfire-deploy/commit/02b7a15bbbd2306a61e344f995dffb72a3915498))
+* fix course progress bar on firefox ([ae5c1e0](https://github.com/macite/doubtfire-deploy/commit/ae5c1e0b5901bae0411cf7bbaa32ee8f340b908a))
+* fix incorrect projcets listed in dropdown ([6a7e478](https://github.com/macite/doubtfire-deploy/commit/6a7e4788b522e5af6331a82ed33e9c9137d2ca86))
+* fix margin issue on dropdown ([c8145f0](https://github.com/macite/doubtfire-deploy/commit/c8145f0972a03e52377e0203cabb2cf6353db8b3))
+* grade and quality point display on task-assessment-card ([7cba502](https://github.com/macite/doubtfire-deploy/commit/7cba502e002143e8255bf0d786551bd270f73f0e))
+* indicate mandatory fields in upload-submission-modal ([e8801e2](https://github.com/macite/doubtfire-deploy/commit/e8801e2a64fe15ad423d9017d10e5e23f6e00eda))
+* only check grade if it exists ([1c213c5](https://github.com/macite/doubtfire-deploy/commit/1c213c53571f79563e9a071e2dd9645598382522))
+* remove unused imports in extension-modal.service ([8081f51](https://github.com/macite/doubtfire-deploy/commit/8081f51b5f28ea511b9f76d026ae63f16153ae2c))
+* use tailwindcss classes ([8d7e160](https://github.com/macite/doubtfire-deploy/commit/8d7e160e2d40c42284ee0c1fc85d6d9e01166cda))
+
+## [8.0.0-3](https://github.com/macite/doubtfire-deploy/compare/v8.0.0-2...v8.0.0-3) (2024-05-02)
+
+
+### Bug Fixes
+
+* reenable eula html fetch ([cda9e52](https://github.com/macite/doubtfire-deploy/commit/cda9e52a259bfaa27e8252f40e579d11d1af280c))
+
+## [8.0.0-2](https://github.com/macite/doubtfire-deploy/compare/v7.0.21...v8.0.0-2) (2024-05-01)
+
+
+### Features
+
+* accept promela files ([df93a69](https://github.com/macite/doubtfire-deploy/commit/df93a69e45d2d9e41b9cf0802d521a3663d7a36c))
+* migrate unit list in all locations ([6651d00](https://github.com/macite/doubtfire-deploy/commit/6651d0011446fdde7179c5173f902ce2e4ea7545))
+* remove misleading progress statement ([63e276c](https://github.com/macite/doubtfire-deploy/commit/63e276c041f77d8b401d24a4f49896cfa2095442))
+
+
+### Bug Fixes
+
+* correct change detection on task def ([55daff2](https://github.com/macite/doubtfire-deploy/commit/55daff234441a242d96de097c2b2912df0c6c9a8))
+* correct npm install for dev container image ([5126c3b](https://github.com/macite/doubtfire-deploy/commit/5126c3ba7fed55217e88c54c25dc70595ec4c666))
+* ensure npm will force install ([f91751e](https://github.com/macite/doubtfire-deploy/commit/f91751eae788bc3f65d33b610ed80c96d3978974))
+* fix firefox-specific bug ([6b6054b](https://github.com/macite/doubtfire-deploy/commit/6b6054bd5c1104a96ccac413a7115009eeba8d2f))
+* remove ngClass ([d1d8c72](https://github.com/macite/doubtfire-deploy/commit/d1d8c72ace21551a68bd4d0a8ba88eabb2ebdb9d))
+* remove plagiarism checks ([21b02fb](https://github.com/macite/doubtfire-deploy/commit/21b02fb7cc616090f1fb2d26f65b336072f9105b))
+* remove the incorrect option from for scrollIntoView() ([9e752fe](https://github.com/macite/doubtfire-deploy/commit/9e752fe9d49df786ceb3e7007cbf0aad57809604))
+* remove unused loading from f-units ([c4c38fa](https://github.com/macite/doubtfire-deploy/commit/c4c38fad4fbdfe248e383cf247f949197ad0a047))
+* typo ([820c56f](https://github.com/macite/doubtfire-deploy/commit/820c56f3a1bc30b7637b98e6f80b406ca350b5b8))
+
+## [8.0.0-1](https://github.com/macite/doubtfire-deploy/compare/v8.0.0-0...v8.0.0-1) (2024-03-21)
+
+
+### Bug Fixes
+
+* update deploy dockerfile ([41a5161](https://github.com/macite/doubtfire-deploy/commit/41a5161b356931eafd8e4caf256f311f10bc8bb4))
+
+## [8.0.0-0](https://github.com/macite/doubtfire-deploy/compare/v7.0.18...v8.0.0-0) (2024-03-21)
+
+
+### Features
+
+* (wip) add similarities notification ([8e4a198](https://github.com/macite/doubtfire-deploy/commit/8e4a198a037de832bbebe96e309c4c8485d076cc))
+* add ability to accept tii eula to model ([0106b01](https://github.com/macite/doubtfire-deploy/commit/0106b01ed249139cbe06096f95a36b5d11bcd0ac))
+* add ability to accept turn it in eula ([380ad1e](https://github.com/macite/doubtfire-deploy/commit/380ad1e93252f8f928893626b65f4dab01813524))
+* add ability to open turnitin viewer ([5248e80](https://github.com/macite/doubtfire-deploy/commit/5248e80622e55b79ce4d7b35f8202ee8099c2dce))
+* add ability to update similarity flag ([de005ee](https://github.com/macite/doubtfire-deploy/commit/de005ee98a270998e2175109783cab72b92e36f3))
+* add dynamic footer ([3c62fe0](https://github.com/macite/doubtfire-deploy/commit/3c62fe06d009c3b752259f50887c625c98a371cd))
+* add empty pdf view to new task dashboard ([9c3cdfb](https://github.com/macite/doubtfire-deploy/commit/9c3cdfbb93711a6ae911ed461651200d8c75db48))
+* add file drop component ([c211003](https://github.com/macite/doubtfire-deploy/commit/c211003004ae6c7505158a65ce2006b219920827))
+* add footer content ([735c43b](https://github.com/macite/doubtfire-deploy/commit/735c43b5c2df7e8f367310ad92ef716fd5140375))
+* add link to student task from inbox ([d4301e0](https://github.com/macite/doubtfire-deploy/commit/d4301e0d1f2f6ec4c563801a7f0d1132884f9989))
+* add mobile inbox view ([56ded70](https://github.com/macite/doubtfire-deploy/commit/56ded707e638ae769665a863ce65acffea5a588a))
+* add multi-file support to f-upload ([d181969](https://github.com/macite/doubtfire-deploy/commit/d181969198e2b7611e3f0e330577b825e99a248f))
+* add new file upload component ([891ab20](https://github.com/macite/doubtfire-deploy/commit/891ab20eb6f1d545ff58d0157fdd9912b9546c9f))
+* add new inbox component ([ec0f62c](https://github.com/macite/doubtfire-deploy/commit/ec0f62c488ae3e99e9487ea1ab87f5bb2f1fbed7))
+* add new pdf-viewer ([617fbd2](https://github.com/macite/doubtfire-deploy/commit/617fbd24f8424541aeb87b16651f06737b570633))
+* add new similarity component ([48ba01c](https://github.com/macite/doubtfire-deploy/commit/48ba01c8d780a04a1f526f89fcd9bc63ef5d4aec))
+* add new task assessment card ([ac3b50f](https://github.com/macite/doubtfire-deploy/commit/ac3b50f29b8a2722d09ad94a2f82bb54c1c32da7))
+* add new task dashboard component ([dc841f9](https://github.com/macite/doubtfire-deploy/commit/dc841f9e7af811a3a56a0c21fd3b3968ec7aead9))
+* add new task due card ([3ade5ea](https://github.com/macite/doubtfire-deploy/commit/3ade5ea34eb3c2c756d7df0f7a6c250fa50290bb))
+* add new task due card content ([c43ea3e](https://github.com/macite/doubtfire-deploy/commit/c43ea3e27abdf253596bfedd5112f5ef7a7b929d))
+* add new task submission card ([6d27690](https://github.com/macite/doubtfire-deploy/commit/6d2769029413f84b73c7c825d16e9a3366142177))
+* add pdf search and zoom ([f83ce57](https://github.com/macite/doubtfire-deploy/commit/f83ce570e80dc20d118288c6d32c2d2a9ca263b5))
+* add resizable inbox panels ([4920e31](https://github.com/macite/doubtfire-deploy/commit/4920e31ce366c3dfb4340ab4da159b176381cf49))
+* add resizable panels to inbox ([5abda03](https://github.com/macite/doubtfire-deploy/commit/5abda039af5b2d3b5e2589c4864b2e1715c5e8c0))
+* add rest of layout to footer ([3b9de7c](https://github.com/macite/doubtfire-deploy/commit/3b9de7cc6d923a0e6431524f2967b5e3bc360a6f))
+* add selected task sheet url ([00a7fb5](https://github.com/macite/doubtfire-deploy/commit/00a7fb516d362d3725b915d012878a9ccf8ef009))
+* add selectedTask service ([02aa41a](https://github.com/macite/doubtfire-deploy/commit/02aa41a34232359d1366d841d1236a068a837ae5))
+* add similarities staff dashboard switch ([1ec0b72](https://github.com/macite/doubtfire-deploy/commit/1ec0b72c26cc27bd4d17376af5b47650d28bbc10))
+* add similarity flags to view ([8c7975c](https://github.com/macite/doubtfire-deploy/commit/8c7975cadd14e6d3390cfa9917983cd01498a69c))
+* add simple project progress bar ([73fda77](https://github.com/macite/doubtfire-deploy/commit/73fda776ee36c6574c1ddc0dc09b958d61de3000))
+* add start of new similarity view ([a1e51b6](https://github.com/macite/doubtfire-deploy/commit/a1e51b6bc7030086a1d7a4d715f23b3e76f7ddb3))
+* add task plagairism warning on inbox ([6bd8620](https://github.com/macite/doubtfire-deploy/commit/6bd8620f5473db4517e38fd0487ab5876f921a36))
+* add teaching period remove break function ([90c6a37](https://github.com/macite/doubtfire-deploy/commit/90c6a3730f84da9e64abec676bcdf66c10b7fc60))
+* add tii action log ([96d982d](https://github.com/macite/doubtfire-deploy/commit/96d982d5fb945bdbf9ea8b6d534a5dd18ab64ad7))
+* add unselected user icon badge in inbox ([794697b](https://github.com/macite/doubtfire-deploy/commit/794697b53d6ee81f57b37b173d03a0688c6993b5))
+* complete mobile task inbox view ([00a4b3f](https://github.com/macite/doubtfire-deploy/commit/00a4b3f1e8f52364a83b6affae34295da4a7af34))
+* disable hover on task inbox for mobile ([a59fd01](https://github.com/macite/doubtfire-deploy/commit/a59fd0153de8b1f9e9b9022d0f6df1032c92f8d1))
+* downgrade task due card ([c342ac2](https://github.com/macite/doubtfire-deploy/commit/c342ac2e3cc4213c2b92499bc4a191485184af37))
+* enhance task search with similarity flags ([65ad918](https://github.com/macite/doubtfire-deploy/commit/65ad91842882e76379033350f90b966d874c6ba4))
+* enhance ui/ux of task inbox screen ([6b275c3](https://github.com/macite/doubtfire-deploy/commit/6b275c3cd5d575a399e901d52a62808238c6d0e4))
+* expand new file uplaoder ([11d798d](https://github.com/macite/doubtfire-deploy/commit/11d798d6a6e5eca4555a7d141e1d11d2a862fcfe))
+* finish new alerts service ([3beb059](https://github.com/macite/doubtfire-deploy/commit/3beb05987e605c9f4270fc0d7bdd95f82bf64007))
+* make all datetime in aus format ([7a0f749](https://github.com/macite/doubtfire-deploy/commit/7a0f749a4328a44c1ffb5692017dfa39a061e1d5))
+* migrate new unit dialog ([0a2a1e9](https://github.com/macite/doubtfire-deploy/commit/0a2a1e9baca6b0553730c1a939b246bf1be178a3))
+* minor improvements to file-drop ([73139be](https://github.com/macite/doubtfire-deploy/commit/73139be83449f50036eedb3520ed2d8daa5da09c))
+* new footer component ([c3b4c18](https://github.com/macite/doubtfire-deploy/commit/c3b4c182921079d6ebc0851ec7e6515e65f1c15b))
+* new grade service ([31073e1](https://github.com/macite/doubtfire-deploy/commit/31073e1dbe963c1e0f2c84139a2c3f746cc31bbb))
+* new student user badge ([97c84d3](https://github.com/macite/doubtfire-deploy/commit/97c84d3016b0e322d54062fae0d46da112146d5d))
+* new task status card ([78218cf](https://github.com/macite/doubtfire-deploy/commit/78218cf6d45f5cac50203f96109d27400fa37686))
+* only download similarity resource on view ([563d102](https://github.com/macite/doubtfire-deploy/commit/563d1024ee7023e4228a9ceef9f556e48e7127d1))
+* progress redesigned inbox ([f1b672b](https://github.com/macite/doubtfire-deploy/commit/f1b672b7495a9086abb88f5e1991cb4c4f7b60c1))
+* register new inbox component ([3d79baa](https://github.com/macite/doubtfire-deploy/commit/3d79baae505e44c401179ae34de4804e8d90b7ba))
+* show TII accepted status in profile ([7b5c3e2](https://github.com/macite/doubtfire-deploy/commit/7b5c3e2c0fe9fcc30f9fffc73d9a942714db7cb2))
+* small change to task view ([f640920](https://github.com/macite/doubtfire-deploy/commit/f6409201e9d4af4f075b4db8559fc1e26f4a374f))
+* small changes to user table ([18957ac](https://github.com/macite/doubtfire-deploy/commit/18957acfc700e941ebb161d9ddb8f5e1ebe1beff))
+* trigger window resize when pdf loads ([044c170](https://github.com/macite/doubtfire-deploy/commit/044c170ebe05e7e3a368df2e8041884d2e23c752))
+* update file viewer to use file download and blobs ([8751123](https://github.com/macite/doubtfire-deploy/commit/87511232ca384a355ddfc5eb396186babd676f92))
+* wip add file uploader component ([48c73f5](https://github.com/macite/doubtfire-deploy/commit/48c73f547892ae3ce12fdac416bf93c3a2c72006))
+
+
+### Bug Fixes
+
+* add activeUntil date mapping to tps ([2cae20a](https://github.com/macite/doubtfire-deploy/commit/2cae20a6b3fa3e5ea8a001ffa40a87263c438b68))
+* add arbitrary task argument to `togglePin` in task inbox ([6e1784f](https://github.com/macite/doubtfire-deploy/commit/6e1784ff52a745330e57f2ccc2a55b3206d535d0))
+* add seperator to naviation on mobile ([fc4f052](https://github.com/macite/doubtfire-deploy/commit/fc4f0527b144fd8f3b6e98b335e629f85fe1bb3b))
+* add the correct enterkeyhint to the comment composer ([569db05](https://github.com/macite/doubtfire-deploy/commit/569db059fc9786363b1b62d7be72e44f711faba3))
+* add unloaded state to progressbar ([2c3a28e](https://github.com/macite/doubtfire-deploy/commit/2c3a28e9718879f55596221517db35bda8c09450))
+* adjust due card layout ([0360a6e](https://github.com/macite/doubtfire-deploy/commit/0360a6e0868e995d7ba5c9091be556dad4bfd474))
+* always display shortened task name in header ([a05c8ff](https://github.com/macite/doubtfire-deploy/commit/a05c8ffd9aef9c865f72458157b465f9de1e07f3))
+* center pins along items in new task inbox ([6b3c0d2](https://github.com/macite/doubtfire-deploy/commit/6b3c0d2b1d5541fe36c6fbd03dfc752f43d03e18))
+* center placeholder text in task inbox search bar & reduce padding on far-right ([ffec9b6](https://github.com/macite/doubtfire-deploy/commit/ffec9b6f4bfc9d2aee6aa762c41bcce1ed1823ba))
+* center student and task name on bottom left screen ([e986199](https://github.com/macite/doubtfire-deploy/commit/e9861992e9e5a40076973a2215bcc919c1777b5d))
+* center view all units button on home page ([9807dfd](https://github.com/macite/doubtfire-deploy/commit/9807dfd1f18e1efc9f882a67ef99f1d198a03ff7))
+* centering of Administration icons on home page ([1043f74](https://github.com/macite/doubtfire-deploy/commit/1043f74e2ca5d23cad8c4d890a202b20ddb4bc37))
+* change display name of student on bottom left screen from being nickname to full name ([5fe7790](https://github.com/macite/doubtfire-deploy/commit/5fe7790e23aec709f8be5a444bf84ebdf77c9ce5))
+* change shown name on messages in chat to be student's full name ([93636c4](https://github.com/macite/doubtfire-deploy/commit/93636c400c1850e20320a57be010347541c5137c))
+* chatbox spacing, placement of messages <> photos, distance between message & name ([dd9cb2a](https://github.com/macite/doubtfire-deploy/commit/dd9cb2ab3bdde886c2f297d8044986427febd366))
+* ensure accepted TII eula details are retained ([e60ae54](https://github.com/macite/doubtfire-deploy/commit/e60ae547121f33edd7cc8d8c5d4afd27a1f90e8c))
+* ensure blobs are freed on destroy ([ee017eb](https://github.com/macite/doubtfire-deploy/commit/ee017eb00a506c81a108ce6dec52f7f91064d350))
+* ensure font is set to roboto ([a6ff4ce](https://github.com/macite/doubtfire-deploy/commit/a6ff4cee9794e37a208af28d7d085182441fa555))
+* ensure loadedUnitRoles is public in global ([b92159f](https://github.com/macite/doubtfire-deploy/commit/b92159ff33cf9537c0ccec50d57a54636315bec4))
+* ensure logout in timeout always signs out ([3380075](https://github.com/macite/doubtfire-deploy/commit/33800759083156682014f0f63bc83a0283ae96fa))
+* ensure mat menu icons have correct tags ([f971ad1](https://github.com/macite/doubtfire-deploy/commit/f971ad1cf7fa56de0f4e97e9533a0567db897da4))
+* ensure only staff need to accept turn it in EULA ([029c40b](https://github.com/macite/doubtfire-deploy/commit/029c40b556bb1709e75547615b2d1a1109997a74))
+* ensure pdf actions loads after pdf loaded ([0425c54](https://github.com/macite/doubtfire-deploy/commit/0425c5465a678509e1a8d719fef39975c4d8e9e5))
+* ensure similarities without files still view ([5794e2f](https://github.com/macite/doubtfire-deploy/commit/5794e2fb345328396517a7d2f945cfabb1c37173))
+* ensure task pdf viewer works ([d3d1bf8](https://github.com/macite/doubtfire-deploy/commit/d3d1bf8564c6e6134130b44df90bef42430f4654))
+* ensure task submission status updated on init ([ca77c92](https://github.com/macite/doubtfire-deploy/commit/ca77c92d251bf4087964886579de642f6f300b9a))
+* ensure that timeout state not re-routed ([c16ee90](https://github.com/macite/doubtfire-deploy/commit/c16ee90c2c372ea1889d3470e2be510aefd2245f))
+* ensure welcome page still can go to timeout ([5253f2e](https://github.com/macite/doubtfire-deploy/commit/5253f2eb6fce1de1b6df585589f1cc2594ac87a8))
+* excessive spacing below unit tags on home page ([1bfaa21](https://github.com/macite/doubtfire-deploy/commit/1bfaa216440d880d82875f519e9b3f0f59ddcc23))
+* fix few type errors ([8a96e5b](https://github.com/macite/doubtfire-deploy/commit/8a96e5b33a095cd70222719b33d7dc853d67f347))
+* fix file drop to support click to upload ([599cd9e](https://github.com/macite/doubtfire-deploy/commit/599cd9e82c1518113374f0675fc54dd1483fcc85))
+* fix footer height not resetting on similarity ([19c43d7](https://github.com/macite/doubtfire-deploy/commit/19c43d7945c5848d1ca511e146fefdfa9735c49b))
+* fix incorrect closing tag ([ad28818](https://github.com/macite/doubtfire-deploy/commit/ad28818fba9dac552a2adf14785c5bbf98b4e206))
+* fix page height recalculation trigger ([8f219c3](https://github.com/macite/doubtfire-deploy/commit/8f219c31e9a3461626a9344352c4d7aa68c57c43))
+* fix various build issues ([8b947ae](https://github.com/macite/doubtfire-deploy/commit/8b947aeea1cb0dfa4bac8e22ff5bdb2a9a82ec49))
+* fix various task comment composer/display issues ([5670bf1](https://github.com/macite/doubtfire-deploy/commit/5670bf1dcd77e8010fe88bf24662ec96e4e29847))
+* hide extension for staff if can't apply ([1699e50](https://github.com/macite/doubtfire-deploy/commit/1699e50f2d7424361e0f7d5a173bdd9fffd87c24))
+* initialise inbox on return ([1249e96](https://github.com/macite/doubtfire-deploy/commit/1249e96f53ef9544cd81e3f2c3f1d5acf4a628f3))
+* initialise inbox view ([1ae4ac9](https://github.com/macite/doubtfire-deploy/commit/1ae4ac9b304348f5528368d87ded3c26e8e5cf40))
+* issues with UI in photo spacing and text alignment ([716ead3](https://github.com/macite/doubtfire-deploy/commit/716ead3497a8a2ecd1050484bd289c003a47dffa))
+* make photo in chatbox fall to bottom ([aa4e814](https://github.com/macite/doubtfire-deploy/commit/aa4e81446546f1e62b08fd74ba8ac1e563e5efc8))
+* make read receipt smaller to fix message alignment ([c869458](https://github.com/macite/doubtfire-deploy/commit/c869458cd45334e009ae83ab3c911033bc1987b9))
+* null input checks on migrated cards ([7a192af](https://github.com/macite/doubtfire-deploy/commit/7a192af22bf1eef026d05a16edcc3a97c6abbebf))
+* pass selected task to footer's user icon ([28782b1](https://github.com/macite/doubtfire-deploy/commit/28782b134cb03f5c334df48e8887f81393661bda))
+* photo from below message to being inline ([0e97a03](https://github.com/macite/doubtfire-deploy/commit/0e97a03a491f4137ec301dd18a42130a845ebe37))
+* reduce user badge clickable region ([7305f86](https://github.com/macite/doubtfire-deploy/commit/7305f866a9582e10dd870fad1861de4ce209a4e0))
+* regenerate user icons on input changes ([257ab4e](https://github.com/macite/doubtfire-deploy/commit/257ab4eb63ec15de545193f8b60176ac33e17b01))
+* remove call to teaching period name ([1e2e019](https://github.com/macite/doubtfire-deploy/commit/1e2e0198a32acd59c11932fde2a2f14a5bacf48f))
+* remove dupe case in transition hook ([9808798](https://github.com/macite/doubtfire-deploy/commit/98087981e06296a1eb9a17f527c49ab9b27caef3))
+* remove incorrect aria hidden tags ([46bfe21](https://github.com/macite/doubtfire-deploy/commit/46bfe2109e4b912a146201572671ff759fc02226))
+* remove no PDF text from overlapping with icon ([c6543cf](https://github.com/macite/doubtfire-deploy/commit/c6543cf2f9be9644b7382eeb64699e15e461447a))
+* remove old teaching periods from home ([4f4a617](https://github.com/macite/doubtfire-deploy/commit/4f4a6175f39ac9df75c7efe920ffcc15576cc98e))
+* remove pct copy data ([6bb96b4](https://github.com/macite/doubtfire-deploy/commit/6bb96b49f436a7d4bd6f5e587b30a8528e6d75b7))
+* remove user badge from old task panel ([9129956](https://github.com/macite/doubtfire-deploy/commit/9129956f41b78a8e3e84ffe9a124a7941d8905c1))
+* require eula for any user ([daf7ba9](https://github.com/macite/doubtfire-deploy/commit/daf7ba9d9056c262cafc871b623c59a0ef837274))
+* resolved issue with intelligent discussion ([18cd1fc](https://github.com/macite/doubtfire-deploy/commit/18cd1fc6f0443c353fc5f49c81dafd2428901cbc))
+* resolved issue with intelligent-discussion-dialog component rendering ([1efa758](https://github.com/macite/doubtfire-deploy/commit/1efa758fd813751788cc79fe30e01ca8a09013d8))
+* shrink header menu on small screens ([580445d](https://github.com/macite/doubtfire-deploy/commit/580445d25573bf00671246269c61685917c34867))
+* spacing (of own/person messaging) is now right/left aligned respectively ([7d10aa0](https://github.com/macite/doubtfire-deploy/commit/7d10aa0f58e2d7c8e8fd45439d8c464fabe3508c))
+* split header onto seperate lines on small screens ([d8a9016](https://github.com/macite/doubtfire-deploy/commit/d8a90168e58ef8d71f31f76d999d4f6a59232987))
+* switch max pct to similarity flag in project ([094b418](https://github.com/macite/doubtfire-deploy/commit/094b418c1bff709e755bf7c61207ebf22bfc6106))
+* task explorer 405 error ([7ea516c](https://github.com/macite/doubtfire-deploy/commit/7ea516ce8e346d46e8acf68c8b41fa81485515ba))
+* task submission pdf downloading ([b0becb4](https://github.com/macite/doubtfire-deploy/commit/b0becb4c76e4e45f10782561a6b53a322eb43665))
+* tweak theme.scss file ([393770b](https://github.com/macite/doubtfire-deploy/commit/393770b35d1688d27bce47e1d6b94735a1822494))
+* update package lock ([674f3c7](https://github.com/macite/doubtfire-deploy/commit/674f3c70bd0a4c8c33e25d34beeab76c29f56325))
+* use angular 15 ([d0104cc](https://github.com/macite/doubtfire-deploy/commit/d0104cc57edda68a1a165bdf1fd7cf75bd74ec68))
+* use correct task object in task pinning ([a5fceb0](https://github.com/macite/doubtfire-deploy/commit/a5fceb048b931dbd5ccc3ad5d4fd898b146284fb))
+* use correct theming import ([b0d3232](https://github.com/macite/doubtfire-deploy/commit/b0d3232eaed2096296cf025437dda26ddcd595e0))
+* use disabled blinding ([628ca55](https://github.com/macite/doubtfire-deploy/commit/628ca55b4e8f569f751b78ead7a50fa9e086ccec))
+* use new state name in task dropdown ([3c2fe44](https://github.com/macite/doubtfire-deploy/commit/3c2fe444d398bcedb5c998a47683d04279996680))
+* use onChanges rather than input setter ([8069c21](https://github.com/macite/doubtfire-deploy/commit/8069c21d9ecfc412dea37cade8e8a37e79730f01))
+
### [8.0.9](https://github.com/doubtfire-lms/doubtfire-deploy/compare/v8.0.8...v8.0.9) (2024-05-31)
@@ -328,8 +2162,15 @@ All notable changes to this project will be documented in this file. See [standa
* use tailwindcss classes ([8d7e160](https://github.com/macite/doubtfire-deploy/commit/8d7e160e2d40c42284ee0c1fc85d6d9e01166cda))
-### [7.0.23](https://github.com/macite/doubtfire-deploy/compare/v7.0.22...v7.0.23) (2024-06-04)
+### [7.0.24](https://github.com/macite/doubtfire-deploy/compare/v7.0.23...v7.0.24) (2024-06-05)
+
+### Bug Fixes
+* ensure download blob supports 206 responses ([6445f9f](https://github.com/macite/doubtfire-deploy/commit/6445f9f998db70fbe9b9abf723dc17fd298b5d2f))
+
+
+
+### [7.0.23](https://github.com/macite/doubtfire-deploy/compare/v7.0.22...v7.0.23) (2024-06-04)
### Bug Fixes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fad5d54a31..a06585384e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,11 +15,28 @@ Before continuing, **please read the [contributing document](https://github.com/
- [Project structure](#project-structure)
- [Testing](#testing)
- [Formatting](#formatting)
- - [Migrating components](#migrating-components)
+ - [Component conventions](#component-conventions)
## Project structure
-Coming...
+The Angular application lives under `src/app`.
+
+- `src/app/api/models` contains client-side models for Doubtfire data.
+- `src/app/api/services` contains services used to access and work with API data.
+- `src/app/api/fixtures` contains API-related fixture data.
+- `src/app/common` contains components and other UI code shared by multiple features.
+- Feature-specific code lives in feature folders under `src/app`, such as
+ `account`, `admin`, `dashboard`, `projects`, `tasks`, and `units`.
+- Keep components that belong to one feature with that feature rather than
+ moving them into `common`.
+- Components wired through the main Angular module use `standalone: false`.
+- Doubtfire component selectors use the `f-` prefix.
+- New non-standalone components must be imported and declared in
+ `src/app/doubtfire-angular.module.ts`.
+- `src/main.ts` bootstraps `DoubtfireAngularModule`, so registering a
+ non-standalone component in that module is required before it can be used.
+- Follow the structure and naming of a nearby component when adding new
+ component TypeScript, template, style, and test files.
## Testing
@@ -33,7 +50,7 @@ npm test
- [ESLint] is used in the project to enforce code style and should be
configured in your [editor](https://eslint.org/docs/user-guide/integrations).
-- [Prettier] is also used and apply automatically by ESLint.
+- [Prettier] is also used and applied automatically by ESLint.
We also use a number of framework plugins:
@@ -54,11 +71,24 @@ npm run lint:fix
Please note that not all issues can be fixed by ESLint and Prettier.
-## Migrating components
+## Component conventions
-See [MIGRATION-GUIDE.md](MIGRATION-GUIDE.md)
+The Angular.js migration is complete, so new work should follow the current
+Angular application structure rather than the old migration process.
-[ESLint](https://eslint.org/)
-[Prettier](https://prettier.io/)
-[TypeScript ESLint](https://github.com/typescript-eslint/typescript-eslint)
-[Angular ESLint](https://github.com/angular-eslint/angular-eslint)
+When adding a component:
+
+- place shared components under `src/app/common`;
+- place feature-specific components in the relevant feature folder;
+- use the `f-` selector prefix;
+- set `standalone: false` for components registered with the main module; and
+- import and add the component to the `declarations` in
+ `src/app/doubtfire-angular.module.ts`.
+
+Check a nearby existing component before adding a new one so its location,
+naming, and module registration match the surrounding feature.
+
+[ESLint]: https://eslint.org/
+[Prettier]: https://prettier.io/
+[TypeScript ESLint]: https://github.com/typescript-eslint/typescript-eslint
+[Angular ESLint]: https://github.com/angular-eslint/angular-eslint
\ No newline at end of file
diff --git a/CROSS_UNIT_STATUS.md b/CROSS_UNIT_STATUS.md
new file mode 100644
index 0000000000..b83cfc162e
--- /dev/null
+++ b/CROSS_UNIT_STATUS.md
@@ -0,0 +1,41 @@
+# Cross-Unit (Cross-Project) Dashboard — starting-point status
+
+**Branch:** `feature/cross-unit` (off `11.0.x`) · **Repos:** `doubtfire-web` + `doubtfire-api`
+**State:** skeleton ported onto v11.0 and wired — *not yet built/tested* (see Verification).
+
+## What this feature is
+A single dashboard that shows a student's tasks across **all** their units in one view
+(`/dashboard`, Student-only). Currently a bare unit→task list — the visual/UX build sits on top.
+
+## What landed in this port
+Ported from the old `Feature/Cross-Unit` branch (forked ~`10.0.0`, ~561 web / 169 api commits
+behind) and **re-homed onto v11.0** — not a git rebase.
+
+### doubtfire-web
+- **Clean adds (7 files):** `src/app/dashboard/**` — `f-cross-dashboard`, `list-item/dashboard-list-item`,
+ `list-item/expanded-list-item/*`. Verified to compile against v11's `GlobalStateService`
+ (`onLoad`, `currentUserProjects.values`).
+- **Wiring (4 edits), translated UI-Router → Angular Router:**
+ - `app.routes.ts` — new top-level `{path: 'dashboard', component: CrossDashboardComponent,
+ canActivate: [roleWhitelistGuard], data: {roleWhitelist: ['Student']}}`.
+ - `doubtfire-angular.module.ts` — registered the 3 components in `declarations`.
+ ⚠️ **Shared file — coordinate with `feature/notifications` and `peer-progress`.**
+ - `home/states/home/home.component.html` — added a "View all" (`/dashboard`) button beside
+ "View previous" (`/view-all-projects`); converted `uiSref` → `routerLink`.
+ - `projects/states/index/global-state.service.ts` — projects query now sends
+ `include_task_definitions: true` (also fixed the latent `include_in_active` → `include_inactive`
+ param typo so web matches the API).
+
+### doubtfire-api (`feature/cross-unit` branch there)
+- `app/api/projects_api.rb` — new `include_task_definitions` param on `GET /projects`.
+- `app/api/entities/project_entity.rb` — expose `tasks` when `include_task_definitions`.
+- `app/api/entities/minimal/minimal_unit_entity.rb` — expose `task_definitions` when requested.
+
+## Verification (NOT yet run — do before relying on this)
+- web: `npm ci && npm run build && npm run lint`
+- api: `bundle exec rubocop` + relevant tests
+- manual: log in as a Student → home shows the new button → `/dashboard` renders the unit list.
+
+## What the team builds on top
+Task status/colours, due dates, sorting/filtering, Material styling, empty/loading states.
+The port only gets the compiling skeleton onto v11 as a shared starting point.
diff --git a/DASHBOARD-SAMPLE-DATA.md b/DASHBOARD-SAMPLE-DATA.md
new file mode 100644
index 0000000000..2befa99d66
--- /dev/null
+++ b/DASHBOARD-SAMPLE-DATA.md
@@ -0,0 +1,10 @@
+# Cross-unit dashboard sample data
+
+These values are entirely synthetic and are intended only as a visual reference for dashboard
+mockups. They are not an API schema or an automated-test fixture. Dates use `DD/MM/YYYY`.
+
+| Unit code | Example task due date | Unit completion | Example task status |
+| --------- | --------------------- | --------------- | ------------------- |
+| COS10001 | 17/10/2026 | 10% | `not_started` |
+| COS20007 | 19/08/2026 | 100% | `complete` |
+| COS30046 | 25/08/2026 | 90% | `working_on_it` |
diff --git a/Dockerfile b/Dockerfile
index 18590c09e9..8818bb1674 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,14 @@
-FROM node:20
+FROM node:22.23.2-bookworm-slim@sha256:83f487e0a63425e5b4d146fb5e5be574bcbe1b7b843d3ebafdd95eaf7767a7e5
-ENV DEBIAN_FRONTEND noninteractive
+# The upstream image ships npm 10.9.8. Upgrade to the latest Node 22-compatible
+# npm 11 release to pick up patched bundled dependencies before dropping
+# privileges.
+RUN npm install --global npm@11.19.1 \
+ && npm cache clean --force
+
+ENV DEBIAN_FRONTEND=noninteractive
ENV USER=node
-ENV NODE_ENV docker
+ENV NODE_ENV=docker
# You can not use `${USER}` here, but reference `/home/node`.
ENV PATH="/home/node/.npm-global/bin:${PATH}"
@@ -26,5 +32,5 @@ RUN npm ci --force --include=optional
EXPOSE 9876
-# Launch - install on launch so that node_modules are updated in volume
-CMD /bin/bash -c 'npm install; npm start'
+# Install on launch so that bind-mounted source and node_modules stay current.
+CMD ["/bin/bash", "-c", "npm install && exec npm start"]
diff --git a/JPlag-Report-Viewer b/JPlag-Report-Viewer
new file mode 160000
index 0000000000..4132c30d9e
--- /dev/null
+++ b/JPlag-Report-Viewer
@@ -0,0 +1 @@
+Subproject commit 4132c30d9ed5606df4648a7f1494ca751d3dca49
diff --git a/MIGRATION-GUIDE.md b/MIGRATION-GUIDE.md
deleted file mode 100644
index 049c5d2e52..0000000000
--- a/MIGRATION-GUIDE.md
+++ /dev/null
@@ -1,218 +0,0 @@
-
-
-# Doubtfire Web - Angular.js to Angular migration guide
-
-This guide will provide the basic steps you can follow in order to successfully migrate a Doubtfire component from CoffeeScript and Angular.js to TypeScript and Angular.
-
-Here we will demonstrate what to do using the changes associated with the Task Description card.
-
-## Step 0 - Come up with a plan
-
-Before you really get started you need to identify a suitable component to migrate, and come up with a plan for the components new styling.
-
-### Identifying a suitable component to migrate
-
-When picking a component make sure your component **DOES NOT** have any other Angular.js components nested within it. Always start at the bottom and work our way up to the "bigger" components.
-
-Look for components that should be easy to migrate. It will be easier to migrate things that just present data, but in general remember you already have a working component. So it should mostly be a matter of mapping the code across, and changing the styling to switch from bootstrap to material design.
-
-When in doubt... ask. We are happy to make suggestions for components that should be beneficial to migrate.
-
-We picked the Task Description Card for this guide as it was relatively straightforward in terms of functionality, and it does not have any other Doubtfire components nested within it.
-
-### Plan your components new styling
-
-Explore the [Angular Material](https://material.angular.io) details. Look for similar [components](https://material.angular.io/components/categories) to the bootstrap components being used in the component you are migrating. Where there is a simple mapping you can probably just proceed, but if you want to make bigger changes (which is good) then please mock something up and discuss with the team first. We do not want you wasting effort if your ideas are not in line with what we will accept.
-
-For the Task Description Card there is a matching [card component](https://material.angular.io/components/card/overview) in Angular Material with a suitable [example](https://material.angular.io/components/card/examples) we can build from. We can do a first pass and then make any styling fixes if needed at the end.
-
-## Step 1 - Create a branch
-
-Ok, this should be a given but it is always going to be important to isolate the changes for these migrations. So start by checking out a new branch for this change.
-
-For the task description card this was done using the following command in the Terminal:
-
-```bash
-git checkout -b migrate/task-description-card
-```
-
-## Step 2 - Create replacement files
-
-Create a typescript, scss, and html file to replace the coffeescript, scss, and html files from the angular.js project.
-
-For the Task Description Card we had the files:
-
-- task-description-card.coffee
-- task-description-card.tpl.html
-- task-description-card.scss
-
-In the same folder we can start by creating the following files:
-
-- task-description-card.component.ts
-- task-description-card.component.html
-- task-description-card.component.scss
-
-Notice the naming convention. When migrating a component we use the format _name_.**component**._extension_.
-
-Add the start of the typescript using something based on the following:
-
-```typescript
-import { Component, Input, Inject } from '@angular/core';
-
-@Component({
- selector: 'task-description-card',
- templateUrl: 'task-description-card.component.html',
- styleUrls: ['task-description-card.component.scss'],
-})
-export class TaskDescriptionCardComponent {
- constructor() {}
-}
-```
-
-We cant see any of these changes yet, but it is a good clean start so lets commit this before we move on.
-
-```bash
-git add task-description-card.component.ts
-git add task-description-card.component.html
-git add task-description-card.component.scss
-# or "git add ." if you have a clean repository and only these files are changed.
-git commit -m "NEW: Create initial files for migration of task-description-card"
-```
-
-Then we should make sure to push this back to GitHub so that others can see our progress. As this is a new branch you will need set the upstream branch, but if you forget the `git push` will remind you anyway.
-
-```bash
-git push --set-upstream origin migrate/task-description-card
-```
-
-You should see a suggestion to create a pull request when you add this branch. This is also a good idea. At this stage you can create a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) which will get updated as you push your changes. Use this to get a quick screenshot of the component in action currently. Then you can include this as a "from" image.
-
-See the resulting [commit](933df7b673b5d57dc162fb42f79c511444f5fbe3) and [pull request](https://github.com/doubtfire-lms/doubtfire-web/pull/321). Its great that you can go back and see how things evolved.
-
-## Step 3 - Unlink old component and add in new
-
-We want to make sure we can see our progress as quickly as possible. So lets start by replacing the old component with the new one.
-
-There are a few files we need to update to achieve this.
-
-- Remove link to component from the angular module.
-
- - Open the component's CoffeeScript file and make a note of the name of the module.
-
- ```coffeescript
- angular.module('doubtfire.projects.states.dashboard.directives.task-dashboard.directives.task-description-card', [])
- ```
-
- The name is `doubtfire.projects.states.dashboard.directives.task-dashboard.directives.task-description-card`
-
- - Search for where this is referenced. For the task-description-card this was in file `src/app/projects/states/dashboard/directives/task-dashboard/directives/directives.coffee`... hence the suggestion to search :satisfied:.
- - Remove the reference. This will mean that the component is no longer loaded by angular.js.
-
-- Setup the new component in **doubtfire-angular.module.ts**
- - Import like this:
- ```ts
- import { TaskDescriptionCardComponent } from './projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.component';
- ```
- - Then add the component name to the list of **declarations**. Now the component will be available in Angular.
-- Remove the old and downgrade the new in **doubtfire-angularjs.module.ts**
- - Remove the line importing the old javascript file:
- ```typescript
- import 'build/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.js';
- ```
- - Import the new component... _it is the same code as importing to the angular module_.
- - Downgrade to make the new component available to Angular.js
- ```typescript
- DoubtfireAngularJSModule.directive(
- 'taskDescriptionCard',
- downgradeComponent({ component: TaskDescriptionCardComponent })
- );
- ```
-- Update attributes on the new component usage.
-
- - Search for all of the places where the component was already used (i.e. search for the component HTML tag).
- - Update the property binding style to use the Angular form which is `[property]="value"`.
-
- For example:
-
- ```html
-
- ```
-
- Needs to change to:
-
- ```html
-
- ```
-
-- Add matching inputs into your components typescript declaration. These use the syntax `@Input() name: type;`. For the task description card we use:
-
- ```ts
- export class TaskDescriptionCardComponent {
- @Input() task: any;
- @Input() taskDef: any;
- @Input() unit: any;
- ...
- }
- ```
-
-Now we can compile to see if this has all worked... :crossed_fingers:. When you change the config in this way I generally find you need to kill any old build processes and start them again. So we can compile now using:
-
-```bash
-npm start
-```
-
-Open your local copy of Doubtfire and navigate somewhere that you can see the lack of the old component. As we have no HTML yet, there should be nothing in its place but we are mostly checking that we have connected this all up correctly.
-
-See the resulting [commit](81563fa3bc04ba4c1afc40a682dbce0703dfc10b) for all of these changes.
-
-Once things are working make sure to commit and push your changes. This will update your draft pull request as well.
-
-Now we are on to the "real" work.
-
-## Step 4 - Migrate the HTML, CoffeeScript, and SCSS
-
-This is where things are going to depend on what you are migrating. Hopefully you have already got a plan for this...
-
-For the task description card I used the [mat-card](https://material.angular.io/components/card/examples) as the main component I needed to migrate to. I did this with the following steps:
-
-1. Copy in the current bootstrap HTML
-2. Identify update to wrap in the material card component (save and test)
-3. Update the header (save and test)
-4. Update the footer to actions (save and test)
-5. Scan for other bootstrap tags and replace.
-
-As I needed it I copied across the CoffeeScript code into the TypeScript component. This included switching the hyperlinks to use buttons, and including the addition of the file saver to make it easy to save the downloaded task sheets and resources as a click action rather than opening a new page with the links.
-
-At the end I made sure to check that **all functionality** from the old component had a matching set of code in the new component.
-
-Here are a few things to watch out for when doing this migration:
-
-- Switching `ng-show` to `[hidden]` remember to change the boolean expression.
-- Font awesome icons needs to change to Material Design icons using [mat-icon](https://material.angular.io/components/icon/overview) - check out the [icon list].
-- Make sure to add [aria-hidden](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute) and [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute) to ensure icons and images are screen reader friendly.
-- Make sure to run `ng lint` at the end of this process to make sure your TypeScript code is looking good!
-
-### A note on porting the CSS of components
-
-In Doubtfire's AngularJS codebase, the components use bootstrap classes such as `card-sm` or `card-danger`. The eventual goal of the rewrite is to remove this Bootstrap dependancy as we are moving to Angular Material. As such, when the new components are being created, these styles need to be replaced with Angular Material equivalents, or a similar component design needs to be written which can replace the old one.
-
-These bootstrap components are usually bassed into the `class` of the components, for example:
-
-- `class="card-heading"`, `class="card-body"` would need to be recreated with: https://material.angular.io/components/card/overview
-- `class="text-muted"` would be recreated with: https://material.angular.io/guide/typography
-
-Once things are all working... you can delete the old CoffeeScript, HTML and SCSS files. These are no longer needed... so they can go!
-
-Check the [final commit](9c8a62c1d70e8f950f8c72b3a7a48c0d7274f670) with all of these changes.
-
-## Step 5 - Create the Pull Request
-
-Now it is time to update the Pull Request. Please ensure the pull request contains two screenshots for each use-case of the component, showing the old component (before) and the new component (after). The component can also be demonstrated in a video which can be directly uploaded to the Pull Request (please do not link to another hosting platform). Once you have added this, make sure to note any changes that the team should check. You want to make sure that you dont break things when then is merged in. If you think everything is ready then switch from a draft to full PR... As long as things are tidy, and you have clear screenshots to show that everything is working, you can expect things to be merged quickly or for you to get some instructions on what to change.
-
-You can check out how this [pull request](https://github.com/doubtfire-lms/doubtfire-web/pull/321) went... :crossed_fingers:
-
-## Conclusion
-
-Hopefully this has provided some useful steps that will mean you can quickly and efficiently migrate the Doubtfire components. We are all really looking forward to switching to the new Angular approach, and trying to keep things more up to date going forward.
-
-Many thanks for your contributions on behalf of the Doubtfire team!
diff --git a/README.md b/README.md
index b213dcce33..206d0377e7 100644
--- a/README.md
+++ b/README.md
@@ -1,335 +1,154 @@
-
+
+
+
-# Doubtfire Web [](https://github.com/doubtfire-lms/doubtfire-web/actions/workflows/nodejs-ci.yml)
+# OnTrack Web [](https://github.com/doubtfire-lms/doubtfire-web/actions/workflows/nodejs-ci.yml)
A modern, lightweight learning management system.
-> ## 🛠 Migration Status: In Development
->
-> Doubtfire web migration from AngularJS/Coffeescript to Angular/Typescript, including refactoring all components, is currently in development.
->
-> See the progress of component migration below.
-
-## Migration Progress
-
-SUMMARY:
-
-73 / 132 components migrated
-
-MIGRATED:
-
-- [x] ./src/app/home/splash-screen/splash-screen.component.ts
-- [x] ./src/app/home/states/home/home.component.ts
-- [x] ./src/app/tasks/task-submission-history/task-submission-history.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/task-assessment-comment/task-assessment-comment.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/extension-comment/extension-comment.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/intelligent-discussion-player/intelligent-discussion-player.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/intelligent-discussion-player/intelligent-discussion-recorder/intelligent-discussion-recorder.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/pdf-image-comment/pdf-image-comment.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/comment-bubble-action/comment-bubble-action.component.ts
-- [x] ./src/app/tasks/task-comments-viewer/task-comments-viewer.component.ts
-- [x] ./src/app/tasks/task-comment-composer/task-comment-composer.component.ts
-- [x] ./src/app/tasks/task-comment-composer/discussion-prompt-composer/discussion-prompt-composer.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/student-task-list/create-portfolio-task-list-item/create-portfolio-task-list-item.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/student-task-list/task-list-item/task-list-item.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-assessment-card/task-assessment-card.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-similarity-view/task-similarity-view.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-due-card/task-due-card.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-status-card/task-status-card.component.ts
-- [x] ./src/app/projects/states/dashboard/directives/task-dashboard/task-dashboard.component.ts
-- [x] ./src/app/admin/institution-settings/overseer-images/overseer-image-list.component.ts
-- [x] ./src/app/admin/institution-settings/institution-settings.component.ts
-- [x] ./src/app/admin/institution-settings/campuses/campus-list/campus-list.component.ts
-- [x] ./src/app/admin/institution-settings/activity-type-list/activity-type-list.component.ts
-- [x] ./src/app/admin/tii-action-log/tii-action-log.component.ts
-- [x] ./src/app/admin/states/teaching-periods/teaching-period-list/teaching-period-list.component.ts
-- [x] ./src/app/admin/states/teaching-periods/teaching-period-unit-import/teaching-period-unit-import.dialog.ts
-- [x] ./src/app/eula/accept-eula/accept-eula.component.ts
-- [x] ./src/app/welcome/welcome.component.ts
-- [x] ./src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.component.ts
-- [x] ./src/app/units/states/tasks/inbox/inbox.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-students-editor/student-tutorial-select/student-tutorial-select.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-students-editor/unit-students-editor.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-students-editor/student-campus-select/student-campus-select.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tutorials-manager/unit-tutorials-manager.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tutorials-list/unit-tutorials-list.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-who/task-definition-who.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-dates/task-definition-dates.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-options/task-definition-options.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-resources/task-definition-resources.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-overseer/task-definition-overseer.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-general/task-definition-general.component.ts
-- [x] ./src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-upload/task-definition-upload.component.ts
-- [x] ./src/app/units/states/analytics/unit-analytics-route.component.ts
-- [x] ./src/app/common/footer/footer.component.ts
-- [x] ./src/app/common/audio-recorder/audio/audio-comment-recorder/audio-comment-recorder.ts
-- [x] ./src/app/common/audio-recorder/audio/microphone-tester/microphone-tester.component.ts
-- [x] ./src/app/common/audio-player/audio-player.component.ts
-- [x] ./src/app/common/edit-profile-form/edit-profile-form.component.ts
-- [x] ./src/app/common/file-drop/file-drop.component.ts
-- [x] ./src/app/common/modals/extension-modal/extension-modal.component.ts
-- [x] ./src/app/common/modals/about-doubtfire-modal/about-doubtfire-modal.component.ts
-- [x] ./src/app/common/modals/calendar-modal/calendar-modal.component.ts
-- [x] ./src/app/common/modals/task-assessment-modal/task-assessment-modal.component.ts
-- [x] ./src/app/common/pdf-viewer/pdf-viewer.component.ts
-- [x] ./src/app/common/obect-select/object-select.component.ts
-- [x] ./src/app/common/hero-sidebar/hero-sidebar.component.ts
-- [x] ./src/app/common/project-progress-bar/project-progress-bar.component.ts
-- [x] ./src/app/common/f-chip/f-chip.component.ts
-- [x] ./src/app/common/status-icon/status-icon.component.ts
-- [x] ./src/app/common/user-badge/user-badge.component.ts
-- [x] ./src/app/common/file-viewer/file-viewer.component.ts
-- [x] ./src/app/common/user-icon/user-icon.component.ts
-- [x] ./src/app/common/pdf-viewer-panel/pdf-viewer-panel.component.ts
-- [x] ./src/app/common/header/header.component.ts
-- [x] ./src/app/common/header/task-dropdown/task-dropdown.component.ts
-- [x] ./src/app/common/header/unit-dropdown/unit-dropdown.component.ts
-- [x] ./src/app/common/services/alert.service.ts
-- [x] ./src/app/sessions/states/sign-in/sign-in.component.ts
-- [x] ./src/app/account/edit-profile/edit-profile.component.ts
-
-TODO:
-
-- [ ] ./src/app/visualisations/alignment-bar-chart.coffee
-- [ ] ./src/app/visualisations/summary-task-status-scatter.coffee
-- [ ] ./src/app/visualisations/target-grade-pie-chart.coffee
-- [ ] ./src/app/visualisations/achievement-custom-bar-chart.coffee
-- [ ] ./src/app/visualisations/student-task-status-pie-chart.coffee
-- [ ] ./src/app/visualisations/alignment-bullet-chart.coffee
-- [ ] ./src/app/visualisations/progress-burndown-chart.coffee
-- [ ] ./src/app/visualisations/task-status-pie-chart.coffee
-- [ ] ./src/app/visualisations/achievement-box-plot.coffee
-- [ ] ./src/app/visualisations/task-completion-box-plot.coffee
-- [ ] ./src/app/visualisations/visualisations.coffee
-- [ ] ./src/app/tasks/task-status-selector/task-status-selector.coffee
-- [ ] ./src/app/tasks/tasks.coffee
-- [ ] ./src/app/tasks/modals/modals.coffee
-- [ ] ./src/app/tasks/modals/upload-submission-modal/upload-submission-modal.coffee
-- [ ] ./src/app/tasks/modals/grade-task-modal/grade-task-modal.coffee
-- [ ] ./src/app/tasks/task-definition-selector/task-definition-selector.coffee
-- [ ] ./src/app/tasks/project-tasks-list/project-tasks-list.coffee
-- [ ] ./src/app/tasks/task-ilo-alignment/task-ilo-alignment-rater/task-ilo-alignment-rater.coffee
-- [ ] ./src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment-modal/task-ilo-alignment-modal.coffee
-- [ ] ./src/app/tasks/task-ilo-alignment/modals/task-ilo-alignment.coffee
-- [ ] ./src/app/tasks/task-ilo-alignment/task-ilo-alignment-editor/task-ilo-alignment-editor.coffee
-- [ ] ./src/app/tasks/task-ilo-alignment/task-ilo-alignment.coffee
-- [ ] ./src/app/tasks/task-ilo-alignment/task-ilo-alignment-viewer/task-ilo-alignment-viewer.coffee
-- [ ] ./src/app/config/privacy-policy/privacy-policy.coffee
-- [ ] ./src/app/config/config.coffee
-- [ ] ./src/app/config/runtime/runtime.coffee
-- [ ] ./src/app/config/root-controller/root-controller.coffee
-- [ ] ./src/app/config/local-storage/local-storage.coffee
-- [ ] ./src/app/config/routing/routing.coffee
-- [ ] ./src/app/config/vendor-dependencies/vendor-dependencies.coffee
-- [ ] ./src/app/config/analytics/analytics.coffee
-- [ ] ./src/app/config/debug/debug.coffee
-- [ ] ./src/app/projects/projects.coffee
-- [ ] ./src/app/projects/project-progress-dashboard/project-progress-dashboard.coffee
-- [ ] ./src/app/projects/states/states.coffee
-- [ ] ./src/app/projects/states/all/directives/directives.coffee
-- [ ] ./src/app/projects/states/all/directives/all-projects-list/all-projects-list.coffee
-- [ ] ./src/app/projects/states/all/all.coffee
-- [ ] ./src/app/projects/states/groups/groups.coffee
-- [ ] ./src/app/projects/states/feedback/feedback.coffee
-- [ ] ./src/app/projects/states/dashboard/directives/directives.coffee
-- [ ] ./src/app/projects/states/dashboard/directives/progress-dashboard/progress-dashboard.coffee
-- [ ] ./src/app/projects/states/dashboard/directives/student-task-list/student-task-list.coffee
-- [ ] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/directives.coffee
-- [ ] ./src/app/projects/states/dashboard/directives/task-dashboard/directives/task-outcomes-card/task-outcomes-card.coffee
-- [ ] ./src/app/projects/states/dashboard/directives/task-dashboard/task-dashboard.coffee
-- [ ] ./src/app/projects/states/dashboard/dashboard.coffee
-- [ ] ./src/app/projects/states/outcomes/outcomes.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/portfolio-review-step/portfolio-review-step.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/directives.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/portfolio-learning-summary-report-step/portfolio-learning-summary-report-step.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/portfolio-add-extra-files-step/portfolio-add-extra-files-step.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/portfolio-grade-select-step/portfolio-grade-select-step.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/portfolio-welcome-step/portfolio-welcome-step.coffee
-- [ ] ./src/app/projects/states/portfolio/directives/portfolio-tasks-step/portfolio-tasks-step.coffee
-- [ ] ./src/app/projects/states/portfolio/portfolio.coffee
-- [ ] ./src/app/projects/states/index/index.coffee
-- [ ] ./src/app/projects/states/tutorials/tutorials.coffee
-- [ ] ./src/app/projects/project-outcome-alignment/project-outcome-alignment.coffee
-- [ ] ./src/app/admin/modals/modals.coffee
-- [ ] ./src/app/admin/modals/create-unit-modal/create-unit-modal.coffee
-- [ ] ./src/app/admin/states/states.coffee
-- [ ] ./src/app/admin/states/units/units.coffee
-- [ ] ./src/app/admin/states/users/users.coffee
-- [ ] ./src/app/admin/admin.coffee
-- [ ] ./src/app/groups/group-selector/group-selector.coffee
-- [ ] ./src/app/groups/group-set-manager/group-set-manager.coffee
-- [ ] ./src/app/groups/group-member-contribution-assigner/group-member-contribution-assigner.coffee
-- [ ] ./src/app/groups/group-member-list/group-member-list.coffee
-- [ ] ./src/app/groups/group-set-selector/group-set-selector.coffee
-- [ ] ./src/app/groups/tutor-group-manager/tutor-group-manager.coffee
-- [ ] ./src/app/groups/groups.coffee
-- [ ] ./src/app/units/modals/unit-student-enrolment-modal/unit-student-enrolment-modal.coffee
-- [ ] ./src/app/units/modals/modals.coffee
-- [ ] ./src/app/units/modals/unit-ilo-edit-modal/unit-ilo-edit-modal.coffee
-- [ ] ./src/app/units/units.coffee
-- [ ] ./src/app/units/states/states.coffee
-- [ ] ./src/app/units/states/tasks/inbox/inbox.coffee
-- [ ] ./src/app/units/states/tasks/tasks.coffee
-- [ ] ./src/app/units/states/tasks/viewer/directives/directives.coffee
-- [ ] ./src/app/units/states/tasks/viewer/directives/task-sheet-view/task-sheet-view.coffee
-- [ ] ./src/app/units/states/tasks/viewer/directives/task-details-view/task-details-view.coffee
-- [ ] ./src/app/units/states/tasks/viewer/directives/unit-task-list/unit-task-list.coffee
-- [ ] ./src/app/units/states/tasks/viewer/viewer.coffee
-- [ ] ./src/app/units/states/tasks/definition/definition.coffee
-- [ ] ./src/app/units/states/portfolios/portfolios.coffee
-- [ ] ./src/app/units/states/all/directives/all-units-list/all-units-list.coffee
-- [ ] ./src/app/units/states/all/directives/directives.coffee
-- [ ] ./src/app/units/states/all/all.coffee
-- [ ] ./src/app/units/states/groups/groups.coffee
-- [ ] ./src/app/units/states/edit/directives/directives.coffee
-- [ ] ./src/app/units/states/edit/directives/unit-group-set-editor/unit-group-set-editor.coffee
-- [ ] ./src/app/units/states/edit/directives/unit-details-editor/unit-details-editor.coffee
-- [ ] ./src/app/units/states/edit/directives/unit-staff-editor/unit-staff-editor.coffee
-- [ ] ./src/app/units/states/edit/directives/unit-ilo-editor/unit-ilo-editor.coffee
-- [ ] ./src/app/units/states/edit/edit.coffee
-- [ ] ./src/app/units/states/rollover/directives/directives.coffee
-- [ ] ./src/app/units/states/rollover/directives/unit-dates-selector/unit-dates-selector.coffee
-- [ ] ./src/app/units/states/rollover/rollover.coffee
-- [ ] ./src/app/units/states/index/index.coffee
-- [ ] ./src/app/units/states/students-list/students-list.coffee
-- [ ] ./src/app/units/states/analytics/analytics.coffee
-- [ ] ./src/app/common/filters/filters.coffee
-- [ ] ./src/app/common/content-editable/content-editable.coffee
-- [ ] ./src/app/common/alert-list/alert-list.coffee
-- [ ] ./src/app/common/modals/confirmation-modal/confirmation-modal.coffee
-- [ ] ./src/app/common/modals/comments-modal/comments-modal.coffee
-- [ ] ./src/app/common/modals/modals.coffee
-- [ ] ./src/app/common/modals/csv-result-modal/csv-result-modal.coffee
-- [ ] ./src/app/common/modals/progress-modal/progress-modal.coffee
-- [ ] ./src/app/common/grade-icon/grade-icon.coffee
-- [ ] ./src/app/common/file-uploader/file-uploader.coffee
-- [ ] ./src/app/common/common.coffee
-- [ ] ./src/app/common/services/grade-service.coffee
-- [ ] ./src/app/common/services/date-service.coffee
-- [ ] ./src/app/common/services/alert-service.coffee
-- [ ] ./src/app/common/services/media-service.coffee
-- [ ] ./src/app/common/services/recorder-service.coffee
-- [ ] ./src/app/common/services/outcome-service.coffee
-- [ ] ./src/app/common/services/listener-service.coffee
-- [ ] ./src/app/common/services/analytics-service.coffee
-- [ ] ./src/app/common/services/services.coffee
-- [ ] ./src/app/sessions/auth/http-auth-injector.coffee
-- [ ] ./src/app/sessions/sessions.coffee
-- [ ] ./src/app/errors/errors.coffee
-- [ ] ./src/app/errors/states/states.coffee
-- [ ] ./src/app/errors/states/unauthorised/unauthorised.coffee
-- [ ] ./src/app/errors/states/not-found/not-found.coffee
-- [ ] ./src/app/errors/states/timeout/timeout.coffee
-
## Table of Contents
-1. [Getting Started](#getting-started)
-2. [Resources](#resources)
-3. [Contributing](#contributing)
-4. [Deployment](#deployment)
-5. [License](#license)
+- [Getting Started](#getting-started)
+- [Checks](#checks)
+- [Production build and handover](#production-build-and-handover)
+- [Demo and live feature boundaries](#demo-and-live-feature-boundaries)
+- [Resources](#resources)
+- [Contributing](#contributing)
+- [License](#license)
## Getting Started
-If you will be using [Docker](https://github.com/doubtfire-lms/doubtfire-api/#getting-started-via-docker), follow the instructions there.
-
-Before you get started, make sure you have the [Doubtfire API](https://github.com/doubtfire-lms/doubtfire-api) up and running. You will need to do this before continuing.
-
-First, clone the web repository, and change to the root directory:
+OnTrack Web requires Node.js 22.22.3 or newer. The exact handover version is in
+`.nvmrc`. Clone recursively because the JPlag report viewer is a production
+asset supplied by a git submodule:
```sh
-git clone https://github.com/doubtfire-lms/doubtfire-web.git
-cd ./doubtfire-web
+git clone --recurse-submodules https://github.com/doubtfire-lms/doubtfire-web.git
+cd doubtfire-web
+git submodule update --init --recursive
+nvm install
+nvm use
+npm ci
```
-You can automate the installation process by running the automated setup script:
+Run the [Doubtfire API](https://github.com/doubtfire-lms/doubtfire-api) on
+`http://localhost:3000`, then start the Angular development server:
```sh
-./setup.sh
+npm start
```
-Or, you can continue following the below steps to manually install `doubtfire-web`.
+Open . `proxy.conf.json` forwards `/api` to the local API.
+It also forwards `/lti/api` to the optional local LTI service on port 3001; LTI
+requests will fail if that service is not running.
-Install [Node.js](http://nodejs.org/) either by [downloading it](http://nodejs.org/download/) and installing it manually, or via [Homebrew](http://brew.sh) on OS X:
+The legacy root `docker-compose.yml` also defaults to local database
+authentication. Optional AAF development must use a dedicated non-production
+registration supplied through an ignored `.env` file copied from
+`.env.example`. Any AAF secret ever committed to Git must be treated as
+compromised and rotated by its identity owner.
-```sh
-brew install node
-```
+## Checks
-_or_ by using `apt-get` on Linux:
+Run the same checks required for a release before handing the branch over:
```sh
-curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
-sudo apt-get install nodejs
+npm run verify:deployment-config
+npm run lint
+npm run typecheck
+npm run test:ci
+npm run deploy
```
-Install Ruby [SASS](http://sass-lang.com):
+## Production build and handover
-```sh
-gem install sass
-```
+The production application deliberately uses the browser origin for both API
+contracts:
-If `gem` fails, you should read the Doubtfire API README to install ruby. If you are _not_ using `rbenv`, e.g., using Docker instead, you may need to prepend `sudo` to the above commands to have root write access.
+- `/api` must be reverse-proxied to Doubtfire API.
+- `/lti/api` must be reverse-proxied to the LTI service when LTI is enabled.
-If using `rbenv`, rehash to ensure each of the gems are on your `PATH`:
+`DF_API_URL` is not a runtime switch for the Angular production build. Hosting
+the API on another origin requires an explicit web configuration and matching
+CORS policy. Production must use HTTPS for service workers, installation, and
+Web Push.
+
+Build the static production bundle directly with:
```sh
-rbenv rehash
+npm ci
+git submodule update --init --recursive
+npm run deploy
```
-Install all node dependencies using `npm`, as well as [grunt-cli](http://gruntjs.com/using-the-cli) globally:
+Or build the production Nginx image, which is the release artifact used by the
+deployment repository:
```sh
-npm install
+docker build -f deploy.Dockerfile -t doubtfire-web:handover .
+docker run --rm -p 8080:80 doubtfire-web:handover
```
-**Note:** You may need to install `grunt-cli` globally in Linux using `sudo`.
+The root `Dockerfile` is the bind-mounted development image: it contains the
+source tree, npm, and development dependencies so that local Compose workflows
+can rebuild on startup. Do not publish or assess it as the production runtime.
+Release vulnerability scans must build `deploy.Dockerfile` and scan its final
+Nginx image digest; build-stage and development-image findings should be tracked
+separately.
+
+The Dockerfile pins its Node and Nginx base images by multi-architecture digest
+and performs the build as the unprivileged `node` user. Refresh those digests
+deliberately as part of a reviewed dependency update; do not replace them with
+floating tags for a release.
-Lastly, to compile and run a watch server and web server, use `npm start`:
+Sentry is optional. Direct production builds leave it disabled. The image build
+can substitute `SENTRY_DSN`, `SENTRY_RELEASE`, and `SENTRY_DIST`; source-map
+upload additionally requires the documented Sentry build arguments and a
+BuildKit `sentry_auth_token` secret.
+
+Before publishing, verify the generated service-worker files and JPlag assets:
```sh
-npm start
+test -f dist/browser/index.html
+test -f dist/browser/ngsw.json
+test -f dist/browser/ngsw-worker.js
+test -f dist/browser/manifest.webmanifest
+test -n "$(find dist/browser/JPlag -mindepth 1 -maxdepth 1 -print -quit)"
```
-This will automatically run the angular 1 `grunt watch`, and the angular 7 `ng serve`.
+Against the running image, confirm application routes fall back to `index.html`,
+each named control-file URL returns 404 rather than HTML if its build output is
+missing, and these files return `Cache-Control: no-store, no-cache` when present:
-You can then navigate to the Doubtfire web interface at **http://localhost:8000**.
+- `/index.html`
+- `/ngsw.json`
+- `/ngsw-worker.js`
+- `/safety-worker.js`
+- `/worker-basic.min.js`
+- `/manifest.webmanifest`
-## Deployment
+The external proxy or CDN must preserve that cache policy. Pin the tested image
+digest in `doubtfire-deploy`, then smoke-test sign-in, API routing, service-worker
+registration/update, and any enabled LTI or Web Push integration.
-To compile the front-end, ensure `doubtfire-api` is placed as a sibling directory to `doubtfire-web`, then run:
-
-```sh
-cd /path/to/repos
-ls
-doubtfire-api doubtfire-web
-cd ./doubtfire-api
-grunt deploy
-```
+## Demo and live feature boundaries
-You may prefix this command with the following environment variables:
+Demo source, fixtures, controls, and evidence remain in the repository for local
+demonstrations. Production builds set `production: true` and
+`enableDemoTools: false`; `/demo-controls` is unavailable and genuine API data
+passes through unchanged. Do not change that flag from deployment tooling.
-- `DF_API_URL` - the URL of the API (e.g., `https://doubtfire.com/api`). This will default to `window.location.host` if not set and dynamically generate a URL.
-- `DF_EXTERNAL_NAME` - a new name that removes references to the _Doubtfire_ name should you so want to not use such its original name (😢).
+Task-level peer progress is live when the matching API feature and privacy
+configuration are enabled. The unit summary and burndown peer-median displays
+are explicitly demo-only fixtures. The push-notification demo preview is visual
+only; real Web Push requires API VAPID configuration, HTTPS, a running worker,
+and browser permission.
## Resources
-Doubtfire Web is an [Angular](http://angularjs.org) application built using [Bootstrap](http://getbootstrap.com). It uses many Open Source libraries, which you can read up on:
-
-- [Lodash](http://lodash.com/docs)
-- [Moment.js](http://momentjs.com)
-- [Font Awesome](http://fontawesome.io)
-- [UI Router](https://github.com/angular-ui/ui-router)
-- [UI Bootstrap](http://angular-ui.github.io/bootstrap/versioned-docs/0.13.4/)
-- [UI Select](https://github.com/angular-ui/ui-select)
-- [NVD3 Charts](http://krispo.github.io/angular-nvd3/#/)
-- [Angular X-Editable](http://vitalets.github.io/angular-xeditable/)
-- [Angular Filters](https://github.com/a8m/angular-filter)
-- [Angular Markdown Filter](https://github.com/vpegado/angular-markdown-filter)
+Doubtfire Web is an [Angular](https://angular.dev) application using
+[Angular Material](https://material.angular.dev/). Production is served as a
+static progressive web application by Nginx.
## Contributing
diff --git a/angular.json b/angular.json
index 2c0b1f0b92..6c5694d24c 100644
--- a/angular.json
+++ b/angular.json
@@ -10,44 +10,50 @@
"prefix": "f",
"schematics": {
"@schematics/angular:application": {
- "strict": false
+ "strict": false,
+ "standalone": false,
+ "style": "scss",
+ "skipTests": true
}
},
"architect": {
"build": {
- "builder": "@angular-devkit/build-angular:application",
+ "builder": "@angular/build:application",
"options": {
"outputPath": "dist",
- "index": "build/index.html",
+ "index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["src/polyfills.ts"],
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/assets",
- "src/manifest.webmanifest"
+ "src/manifest.webmanifest",
+ {
+ "input": "./JPlag-Report-Viewer",
+ "output": "/JPlag",
+ "glob": "**/!(*.gitignore|README.md|.git)"
+ },
+ {
+ "glob": "**/*",
+ "input": "./node_modules/monaco-editor/min",
+ "output": "/assets/monaco/min/"
+ }
],
+ "loader": {
+ ".ttf": "binary"
+ },
+ "stylePreprocessorOptions": {
+ "includePaths": ["src"]
+ },
"styles": [
"src/theme.scss",
"src/styles.scss",
- "./node_modules/bootstrap/dist/css/bootstrap.css",
- "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css",
- "./build/assets/doubtfire.css",
- "./build/assets/node_modules/angular-xeditable/dist/css/xeditable.css",
- "./build/assets/node_modules/codemirror/lib/codemirror.css",
- "./build/assets/node_modules/codemirror/theme/xq-light.css",
- "./build/assets/node_modules/nvd3/build/nv.d3.css",
- "node_modules/@ctrl/ngx-emoji-mart/picker.css"
+ "node_modules/@ctrl/ngx-emoji-mart/picker.css",
+ "node_modules/@worktile/gantt/styles/index.scss"
],
"scripts": [
"node_modules/moment/moment.js",
- "node_modules/d3/d3.js",
- "node_modules/lodash/lodash.js",
- "node_modules/underscore.string/dist/underscore.string.min.js",
- "node_modules/nvd3/build/nv.d3.js",
- "node_modules/es5-shim/es5-shim.js",
- "node_modules/codemirror/lib/codemirror.js",
- "node_modules/codemirror/addon/display/placeholder.js",
- "node_modules/codemirror/mode/markdown/markdown.js",
+ "node_modules/d3/dist/d3.min.js",
"node_modules/canvas-confetti/dist/confetti.browser.js"
],
"extractLicenses": false,
@@ -68,12 +74,12 @@
"with": "src/environments/environment.prod.ts"
},
{
- "replace": "src/app/config/constants/apiURL.ts",
- "with": "src/app/config/constants/apiURL.prod.ts"
+ "replace": "src/app/config/constants/hostUrl.ts",
+ "with": "src/app/config/constants/hostUrl.prod.ts"
}
],
"optimization": true,
- "outputHashing": "bundles",
+ "outputHashing": "all",
"sourceMap": false,
"extractLicenses": true,
"serviceWorker": "ngsw-config.json"
@@ -81,7 +87,15 @@
"development": {
"optimization": false,
"extractLicenses": false,
- "sourceMap": true
+ "sourceMap": true,
+ "serviceWorker": "ngsw-config.json"
+ },
+ "testing": {
+ "aot": false,
+ "optimization": false,
+ "extractLicenses": false,
+ "sourceMap": true,
+ "polyfills": ["zone.js"]
},
"devcontainer": {
"optimization": false,
@@ -89,15 +103,15 @@
"sourceMap": true,
"fileReplacements": [
{
- "replace": "src/app/config/constants/apiURL.ts",
- "with": "src/app/config/constants/apiURL.devcontainer.ts"
+ "replace": "src/app/config/constants/hostUrl.ts",
+ "with": "src/app/config/constants/hostUrl.devcontainer.ts"
}
]
}
}
},
"serve": {
- "builder": "@angular-devkit/build-angular:dev-server",
+ "builder": "@angular/build:dev-server",
"options": {
"buildTarget": "doubtfire:build",
"port": 4200,
@@ -121,60 +135,65 @@
}
},
"extract-i18n": {
- "builder": "@angular-devkit/build-angular:extract-i18n",
+ "builder": "@angular/build:extract-i18n",
"options": {
"buildTarget": "doubtfire:build"
}
},
"test": {
- "builder": "@angular-devkit/build-angular:karma",
+ "builder": "@angular/build:unit-test",
"options": {
- "main": "src/test.ts",
- "polyfills": "src/polyfills.ts",
+ "buildTarget": "doubtfire:build:testing",
"tsConfig": "src/tsconfig.spec.json",
- "karmaConfig": "src/karma.conf.js",
- "styles": [
- "./node_modules/bootstrap/dist/css/bootstrap.css",
- "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css"
- ],
- "scripts": [],
- "assets": [
- "src/favicon.ico",
- "src/assets",
- "src/manifest.webmanifest"
- ]
+ "runner": "vitest",
+ "setupFiles": ["src/vitest-setup.ts"]
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
- "lintFilePatterns": [
- "src/**/*.ts",
- "src/**/*.component.html"
- ]
+ "lintFilePatterns": ["src/**/*.ts", "src/**/*.component.html", "src/**/*.html"]
}
},
"eslint": {
"builder": "@angular-eslint/builder:lint",
"options": {
- "lintFilePatterns": [
- "src/**/*.ts",
- "src/**/*.component.html"
- ]
+ "lintFilePatterns": ["src/**/*.ts", "src/**/*.component.html"]
}
}
}
}
},
"schematics": {
- "@schematics/angular:component": {
- "style": "scss"
- },
"@angular-eslint/schematics:application": {
"setParserOptionsProject": true
},
"@angular-eslint/schematics:library": {
"setParserOptionsProject": true
+ },
+ "@schematics/angular:component": {
+ "type": "component"
+ },
+ "@schematics/angular:directive": {
+ "type": "directive"
+ },
+ "@schematics/angular:service": {
+ "type": "service"
+ },
+ "@schematics/angular:guard": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:interceptor": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:module": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:pipe": {
+ "typeSeparator": "."
+ },
+ "@schematics/angular:resolver": {
+ "typeSeparator": "."
}
},
"cli": {
@@ -182,8 +201,6 @@
"cache": {
"enabled": true
},
- "schematicCollections": [
- "@angular-eslint/schematics"
- ]
+ "schematicCollections": ["@angular-eslint/schematics"]
}
}
diff --git a/build.config.js b/build.config.js
index 2f9b32b333..ffb10f91a4 100644
--- a/build.config.js
+++ b/build.config.js
@@ -24,9 +24,9 @@ module.exports = {
api: {
src: ['build/src/app/api/api-url.js', 'build/src/app/config/external-name/external-name.js'],
options: {
- inline: true
- }
- }
+ inline: true,
+ },
+ },
},
/**
@@ -38,31 +38,15 @@ module.exports = {
* app's unit tests.
*/
app_files: {
- js: [
- 'src/**/*.js',
- '!src/**/*.spec.js',
- '!src/assets/**/*.js'
- ],
- jsunit: [
- 'src/**/*.spec.js'
- ],
+ js: ['src/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js'],
+ jsunit: ['src/**/*.spec.js'],
- coffee: [
- 'src/**/*.coffee',
- '!src/**/*.spec.coffee',
- '!src/**/*.old.coffee'
- ],
- coffeeunit: [
- 'src/**/*.spec.coffee'
- ],
+ coffee: ['src/**/*.coffee', '!src/**/*.spec.coffee', '!src/**/*.old.coffee'],
+ coffeeunit: ['src/**/*.spec.coffee'],
- atpl: [
- 'src/app/**/*.tpl.html'
- ],
+ atpl: ['src/app/**/*.tpl.html'],
- html: [
- 'src/index.html'
- ],
+ html: ['src/index.html'],
scss: [
// Do not modify the order
@@ -70,8 +54,8 @@ module.exports = {
'src/styles/common/**/*.scss',
'src/styles/modules/**/*.scss',
'src/app/**/*.scss',
- '!src/app/**/*.component.scss'
- ]
+ '!src/app/**/*.component.scss',
+ ],
},
/**
@@ -92,26 +76,22 @@ module.exports = {
*/
vendor_files: {
compile: {
- js: [
- ],
- jsmap: [
- ],
+ js: [],
+ jsmap: [],
scss: [
'node_modules/bootstrap-sass/**/_bootstrap.scss',
- 'node_modules/font-awesome/**/font-awesome.scss'
+ 'node_modules/font-awesome/**/font-awesome.scss',
],
},
copy: {
- js: [
- ],
- jsmap: [
- ],
+ js: [],
+ jsmap: [],
css: [
- "node_modules/nvd3/build/nv.d3.css",
- "node_modules/angular-xeditable/dist/css/xeditable.css",
- "node_modules/codemirror/lib/codemirror.css",
- "node_modules/codemirror/theme/xq-light.css"
- ]
- }
- }
+ 'node_modules/nvd3/build/nv.d3.css',
+ 'node_modules/angular-xeditable/dist/css/xeditable.css',
+ 'node_modules/codemirror/lib/codemirror.css',
+ 'node_modules/codemirror/theme/xq-light.css',
+ ],
+ },
+ },
};
diff --git a/commitlint.config.js b/commitlint.config.js
index 28fe5c5bf9..3347cb961c 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -1 +1 @@
-module.exports = {extends: ['@commitlint/config-conventional']}
+module.exports = {extends: ['@commitlint/config-conventional']};
diff --git a/deploy.Dockerfile b/deploy.Dockerfile
index 2326fca4ec..d0e431c68d 100644
--- a/deploy.Dockerfile
+++ b/deploy.Dockerfile
@@ -1,5 +1,14 @@
### STAGE 1: Build ###
-FROM node:20 AS build
+FROM node:22.23.2-bookworm-slim@sha256:83f487e0a63425e5b4d146fb5e5be574bcbe1b7b843d3ebafdd95eaf7767a7e5 AS build
+
+# Keep the build package manager aligned with the development image. npm 11
+# removes the vulnerable dependencies bundled with this Node 22 base.
+RUN npm install --global npm@11.19.1 \
+ && npm cache clean --force
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gettext-base \
+ && rm -rf /var/lib/apt/lists/*
USER node
@@ -7,17 +16,41 @@ USER node
# Copy in doubtfire-web code
WORKDIR /doubtfire-web
COPY package.json package-lock.json ./
-RUN npm ci --force --include=optional
+RUN npm ci --include=optional
COPY --chown=node:node . .
-RUN chmod 777 src
+
+ARG SENTRY_DSN
+ARG SENTRY_ORG
+ARG SENTRY_PROJECT
+ARG SENTRY_RELEASE
+ARG SENTRY_DIST
+ARG UPLOAD_SENTRY_SOURCEMAPS=false
+ENV SENTRY_DSN=$SENTRY_DSN
+ENV SENTRY_ORG=$SENTRY_ORG
+ENV SENTRY_PROJECT=$SENTRY_PROJECT
+ENV SENTRY_RELEASE=$SENTRY_RELEASE
+ENV SENTRY_DIST=$SENTRY_DIST
+RUN envsubst '${SENTRY_DSN} ${SENTRY_RELEASE} ${SENTRY_DIST}' < src/environments/environment.prod.ts > src/environments/environment.prod.ts.tmp && mv src/environments/environment.prod.ts.tmp src/environments/environment.prod.ts
# Launch - build to dist folder
-RUN npm run-script deploy
+RUN --mount=type=secret,id=sentry_auth_token,uid=1000 \
+ if [ "$UPLOAD_SENTRY_SOURCEMAPS" = "true" ]; then \
+ npm run deploy:build2api:sourcemaps; \
+ SENTRY_AUTH_TOKEN="$(cat /run/secrets/sentry_auth_token)" npm run sentry:sourcemaps; \
+ find dist/browser -name '*.map' -delete; \
+ npx ngsw-config dist/browser ngsw-config.json /; \
+ else \
+ npm run-script deploy; \
+ fi
## STAGE 2: Host ###
-FROM nginx:1.21.5-alpine
+FROM nginx:1.30.4-alpine@sha256:97d490c12ba55b4946b01546d1c3ed324e8d41ab1c9fcb2a616aa470620e5b46
+
+# The pinned Nginx image predates Alpine's OpenSSL 3.5.8 security update.
+# Upgrade only the affected runtime libraries rather than the whole base.
+RUN apk upgrade --no-cache libcrypto3 libssl3
# Remove the default Nginx configuration file
RUN rm -v /etc/nginx/nginx.conf
diff --git a/docker-compose.yml b/docker-compose.yml
index b97ff4e52c..e478833212 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,9 @@
-version: '3'
services:
doubtfire-api:
container_name: web-doubtfire-api
image: lmsdoubtfire/doubtfire-api:7.0-dev
ports:
- - "3000:3000"
+ - '3000:3000'
volumes:
- ../data/tmp:/doubtfire/tmp
- ../data/student-work:/student-work
@@ -14,22 +13,24 @@ services:
RAILS_ENV: 'development'
DF_STUDENT_WORK_DIR: /student-work
- DF_INSTITUTION_HOST: http://localhost:3000
+ DF_INSTITUTION_HOST: http://localhost:4200
DF_INSTITUTION_PRODUCT_NAME: OnTrack
DF_SECRET_KEY_BASE: test-secret-key-test-secret-key!
DF_SECRET_KEY_ATTR: test-secret-key-test-secret-key!
DF_SECRET_KEY_DEVISE: test-secret-key-test-secret-key!
- # Authentication method - can set to AAF or ldap
- DF_AUTH_METHOD: database
- DF_AAF_ISSUER_URL: https://rapid.test.aaf.edu.au
- DF_AAF_AUDIENCE_URL: http://localhost:3000
- DF_AAF_CALLBACK_URL: http://localhost:3000/api/auth/jwt
- DF_AAF_IDENTITY_PROVIDER_URL: https://signon-uat.deakin.edu.au/idp/shibboleth
- DF_AAF_UNIQUE_URL: https://rapid.test.aaf.edu.au/jwt/authnrequest/research/Ag4EJJhjf0zXHqlKvKZEbg
- DF_AAF_AUTH_SIGNOUT_URL: https://sync-uat.deakin.edu.au/auth/logout
- DF_SECRET_KEY_AAF: v4~LMFLzzwRGZdju\5QBa@FiHIN9
+ # Database authentication is the safe local default. Optional AAF
+ # development values must come from an ignored .env file; never commit an
+ # institution credential or registration URL here.
+ DF_AUTH_METHOD: ${DF_AUTH_METHOD:-database}
+ DF_AAF_ISSUER_URL: ${DF_AAF_ISSUER_URL:-}
+ DF_AAF_AUDIENCE_URL: ${DF_AAF_AUDIENCE_URL:-http://localhost:4200}
+ DF_AAF_CALLBACK_URL: ${DF_AAF_CALLBACK_URL:-http://localhost:4200/api/auth/jwt}
+ DF_AAF_IDENTITY_PROVIDER_URL: ${DF_AAF_IDENTITY_PROVIDER_URL:-}
+ DF_AAF_UNIQUE_URL: ${DF_AAF_UNIQUE_URL:-}
+ DF_AAF_AUTH_SIGNOUT_URL: ${DF_AAF_AUTH_SIGNOUT_URL:-}
+ DF_SECRET_KEY_AAF: ${DF_SECRET_KEY_AAF:-}
# Database settings - for development env
DF_DEV_DB_ADAPTER: mysql2
@@ -74,8 +75,8 @@ services:
build: .
command: /bin/bash -c 'npm install; npm start'
ports:
- - "4200:4200"
- - "9876:9876"
+ - '4200:4200'
+ - '9876:9876'
depends_on:
- doubtfire-api
volumes:
diff --git a/docs/A11Y-V01-validation-notes.md b/docs/A11Y-V01-validation-notes.md
new file mode 100644
index 0000000000..eb85c305b2
--- /dev/null
+++ b/docs/A11Y-V01-validation-notes.md
@@ -0,0 +1,203 @@
+# A11Y-V01 — Independent Accessibility Validation
+
+**Date:** 18 September 2026
+
+**Validation branch:** `test/a11y-phase1-validation`
+
+**Integrated branch:** `origin/11.0.x`
+
+**Baseline:** `518abe8f1ab870175379a5aed992e1035bae987d`
+
+
+## 1. Validation setup
+
+Validation was completed against the integrated `origin/11.0.x` baseline. No application code changes were made on the validation branch.
+
+**Environment:** Node `v22.23.1`, npm `10.9.8`, Linux, Orca 42.0, 200% browser zoom, iPhone 15 Pro mobile viewport.
+
+**Test data:** Synthetic local development data only, 40 users, 4 units, 51 projects and 9 tutorials. Student and tutor test accounts were used. No real student data or identifying participant information was used.
+
+## 2. Automated validation
+
+| Check | Result |
+| ----------------- | --------------------------------------------- |
+| `npm run lint` | **PASS**. All files pass linting |
+| `npm run test:ci` | **PASS**. 141 test files, 1,057 tests, 33.16s |
+
+No dedicated axe, Pa11y, Lighthouse, Playwright or Cypress accessibility scanner is configured in the repository.
+
+## 3. Targeted accessibility tests
+
+### Task visualisation
+
+```text
+✓ doubtfire src/app/visualisations/task-visualisation/task-visualisation.component.spec.ts (3 tests)
+ ✓ complete status labels without chart ellipsis/false click affordance
+ ✓ status cards above 4.5:1 contrast floor
+ ✓ route reuse updates counts
+
+Test Files 1 passed (1)
+Tests 3 passed (3)
+```
+
+**Command:**
+
+```bash
+cd /workspace/doubtfire-web && npx ng test --no-watch --no-progress --include='src/app/visualisations/task-visualisation/task-visualisation.component.spec.ts'
+```
+
+### PDF viewer
+
+```text
+✓ doubtfire src/app/common/pdf-viewer/pdf-viewer.component.spec.ts (3 tests)
+ ✓ zoom buttons have different accessible names
+ ✓ search field has mat-label and spinner accessible name
+ ✓ embedded PDF object titled
+
+Test Files 1 passed (1)
+Tests 3 passed (3)
+```
+
+**Command:**
+
+```bash
+cd /workspace/doubtfire-web && npx ng test --no-watch --no-progress --include='src/app/common/pdf-viewer/pdf-viewer.component.spec.ts'
+```
+
+### Unit task list
+
+```text
+✓ doubtfire src/app/units/task-viewer/directives/unit-task-list/unit-task-list.component.spec.ts (31 tests)
+ ✓ task logic tests
+ ✓ status badges accessible names
+ ✓ decorative glyphs hidden
+ ✓ deadline states use different glyph for non-colour signal
+ ✓ collapsed list badges named
+
+Test Files 1 passed (1)
+Tests 31 passed (31)
+```
+
+**Command:**
+
+```bash
+cd /workspace/doubtfire-web && npx ng test --no-watch --no-progress --include='src/app/units/task-viewer/directives/unit-task-list/unit-task-list.component.spec.ts'
+```
+
+### Staff task list
+
+```text
+✓ doubtfire src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.component.spec.ts (22 tests)
+ ✓ keyboard focus reveals row actions without hover
+ ✓ focus/blur
+ ✓ overflow menu focus
+ ✓ empty state descriptive text
+ ✓ full message accessible in narrow icon-only sidebar
+
+Test Files 1 passed (1)
+Tests 22 passed (22)
+```
+
+**Command:**
+
+```bash
+cd /workspace/doubtfire-web && npx ng test --no-watch --no-progress --include='src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.component.spec.ts'
+```
+
+### Profile and sign-in forms
+
+```text
+✓ doubtfire src/app/common/edit-profile-form/edit-profile-form.component.spec.ts
+✓ doubtfire src/app/sessions/states/sign-in/sign-in.component.spec.ts
+
+Test Files 2 passed (2)
+Tests 24 passed (24)
+```
+
+These tests included the A11Y-FORM06 autocomplete-purpose checks.
+
+**Command:**
+
+```bash
+cd /workspace/doubtfire-web && npx ng test --no-watch --no-progress --include='src/app/common/edit-profile-form/edit-profile-form.component.spec.ts' --include='src/app/sessions/states/sign-in/sign-in.component.spec.ts'
+```
+
+
+These tests covered accessible names, keyboard focus, status information, PDF controls, forms and contrast-related behaviour.
+
+## 4. Manual validation
+
+| Journey | Keyboard | Screen reader | 200% zoom | Mobile/reflow | Colour | Motion |
+| ----------- | -------- | ------------- | --------- | ------------- | --------- | -------- |
+| Student | **PASS** | **PASS** | **PASS** | **PASS*** | **PASS*** | **PASS** |
+| Staff/tutor | **PASS** | **PASS** | **PASS** | **PASS*** | **PASS*** | **PASS** |
+
+* Minor visual observations are documented below.
+
+Both journeys remained usable with keyboard navigation, Orca, increased zoom and normal interaction. No keyboard trap or screen-reader blocker was identified.
+
+The light/dark theme toggle was not visible at 200% zoom or on the mobile layout.
+
+## 5. Defect and visual observations
+
+### D01: Mobile PDF search-box visual issue
+
+**Severity:** Proposed P2 / minor visual issue
+
+**Status:** Open
+
+**Owner:** To be assigned
+
+**Blocks Phase 1 sign-off:** No, based on current evidence
+
+At the mobile viewport, **Task Details → Task Sheet** showed three magnifying-glass icons and minor visual glitching around the left side of the PDF search box when folding/expanding. The search function remained usable.
+
+
+
+
+**Reproduction:** Open a student/staff Task Sheet at the iPhone 15 Pro viewport and fold/expand the PDF search area.
+
+### Progress Dashboard observation
+
+
+
+The Progress Dashboard showed a strong visual difference between the white surrounding area and dark/black component boxes.
+
+## 6. Inclusive-UX review
+
+The required review by at least **two independent people** remains outstanding.
+
+Four tasks must be completed:
+
+1. Find the current unit.
+2. Find a task to work on.
+3. Open the task details/task sheet.
+4. Find progress/status information.
+
+My exploratory check is not counted towards this requirement.
+
+**Status: Outstanding.**
+
+## 7. Integration and independence
+
+The validated baseline was:
+
+`origin/11.0.x @ 518abe8f1ab870175379a5aed992e1035bae987d`
+
+Merged accessibility changes in this baseline were included in validation. Changes remaining on separate branches were not merged or used as evidence for integrated sign-off.
+
+The validation branch contains no application code changes or commits beyond the integrated baseline.
+
+A repository search did not identify written A11Y-L01/MISC-X01 approval naming the exact integration branch, so no unverified approval is claimed.
+
+## 8. Validation status
+
+**Status: Partially complete. Independent inclusive-UX review outstanding.**
+
+**Completed:** Automated tests, targeted accessibility tests, student/staff manual journeys, keyboard, screen reader, zoom, reflow, colour and motion checks, defect documentation and keyboard re-test.
+
+**Outstanding:** Two independent reviewers to complete the four inclusive-UX tasks and assignment of an owner for D01.
+
+No P0 or P1 accessibility defects were identified.
+
+This validation does not claim full accessibility compliance or formal accessibility certification.
diff --git a/docs/CAL-D00-webcal-feed-test.md b/docs/CAL-D00-webcal-feed-test.md
new file mode 100644
index 0000000000..3e81fabea1
--- /dev/null
+++ b/docs/CAL-D00-webcal-feed-test.md
@@ -0,0 +1,41 @@
+# CAL-D00: What the existing WebCal feed produces
+
+Before treating the WebCal feed as limited, this records what the implementation generates based on the source (`doubtfire-api/app/models/webcal.rb`). Live calendar-client verification is still required and is tracked below.
+
+## Feed shape
+
+The feed is a single per-student iCalendar (`Webcal#to_ical`). The calendar declares a product id from the institution config. API PR #147 corrects the refresh value to `PT4H` on both `X-PUBLISHED-TTL` and `REFRESH-INTERVAL`. This is a four-hour refresh hint; each calendar client decides when it actually refreshes.
+
+## Which tasks appear
+
+`Webcal#task_definitions` selects the task definitions for the student's enrolled, active projects, in active and currently-running units, and applies two filters:
+
+- units the student has excluded are left out (via `WebcalUnitExclusion`),
+- only task definitions at or below the student's target grade for that unit are included (`task_definitions.target_grade <= projects.target_grade`).
+
+## Events per task
+
+Each task definition produces one **end (due) event**, and also a **start event** if the student has enabled start dates.
+
+- **Title:** `"{unit code}: {task abbreviation}: {task name}"`, for example `COS10001: 2.3P: Pass Task 2.3 - My Drawing Procedure`. With start dates enabled, titles are prefixed `Start:` and `End:`.
+- **Date:** all-day, with `DTSTART` equal to `DTEND` in `YYYYMMDD` form and no time zone. The due-date chain is the task's `local_due_date` when a task record exists, then the target-grade-specific date for a flexible-date unit, then the task definition's `target_date`. Start events use the analogous `local_start_date`, target-grade-specific start date, and task definition `start_date` chain.
+- **UID:** `E-{taskDefinitionId}` for the end event and `S-{taskDefinitionId}` for the start event. These are keyed on the task definition, so the same task shares a UID across students, which is what lets a client update the event in place on refresh.
+- **Status:** `CONFIRMED`.
+- **Reminder:** if the student has set a reminder, each event carries a display alarm that triggers the configured time before the event.
+- **Custom properties:** `X-DOUBTFIRE-UNIT` (unit id) and `X-DOUBTFIRE-TASK` (task definition id).
+
+The feed does not set a description, URL, or location on events, and it does not exclude completed tasks; every applicable task definition appears regardless of submission state.
+
+## Calendar-client verification status
+
+The source analysis above has not yet been confirmed in a live calendar client. To complete that evidence:
+
+1. Subscribe as a seeded student and record the client, client version, and test date.
+2. Confirm the event titles and all-day dates against the generated feed.
+3. Exclude a unit through the modal, refresh the subscription, and record whether its events disappear.
+
+Replace this section with the actual observations before claiming client verification is complete.
+
+## Takeaway
+
+Source inspection shows that the feed covers a student's current units, filtered by target grade and by any units they exclude, as all-day events with stable UIDs and optional reminders and start dates. Its limits are narrow and specific: it always uses the saved target grade rather than a chosen one, and it includes completed tasks. Those two points are the subject of CAL-D02.
diff --git a/docs/CAL-D01-webcal-entry-point-audit.md b/docs/CAL-D01-webcal-entry-point-audit.md
new file mode 100644
index 0000000000..9ed2185b4a
--- /dev/null
+++ b/docs/CAL-D01-webcal-entry-point-audit.md
@@ -0,0 +1,257 @@
+# CAL-D01: WebCal entry point audit
+
+Recon and documentation only. No feature code or component changes were made for this
+ticket. All findings below are from reading the actual frontend source on
+`feature/CAL-D01-webcal-entry-point-audit` (branched off `feature/calendar-integration`),
+quoting real file paths and real code.
+
+## What WebCal is
+
+WebCal is an existing (since 2020, per project history) feed-subscription feature. A
+student enables it, gets a personal `.ics` feed URL, and can point any external calendar
+app (Google Calendar, Apple Calendar, Outlook) at that URL to subscribe. The subscribed
+calendar then pulls task due dates on a refresh interval. This is different from a one-off
+"add this single event" action (which is what CAL-F01 builds): WebCal is a standing
+subscription to a feed, not a per-task button.
+
+The entire frontend surface for WebCal is two things:
+
+- `src/app/api/services/webcal.service.ts`, a thin CRUD service for the user's webcal
+ settings (enabled, guid, includeStartDates, reminder, unitExclusions). It does not
+ render anything itself.
+- `src/app/common/modals/calendar-modal/calendar-modal.component.ts` (and its
+ `.component.html`), a Material dialog that is the actual settings UI: an enable toggle,
+ the `.ics` URL with a copy-to-clipboard button, reminder configuration, and per-unit
+ exclusion controls. Confirmed by reading the component directly, every mutation in it
+ (`this.webcalService.update(...)`, `this.webcalService.get({})`) goes through
+ `WebcalService`, and the dialog's own title, read from
+ `calendar-modal.component.html:4`, is:
+
+ ```html
+
Web calendar
+ ```
+
+ Note this title says "Web calendar", not "Calendar", a small labeling inconsistency
+ from the entry points that lead to it, covered below.
+
+The modal is opened exclusively through `CalendarModalService`
+(`src/app/common/modals/calendar-modal/calendar-modal.service.ts`):
+
+```ts
+public show(_task?: Task) {
+ this.dialog.open(CalendarModalComponent, {
+ height: 'h-min',
+ maxHeight: '90vh',
+ width: '800px',
+ maxWidth: '95vw',
+ });
+}
+```
+
+The `_task` parameter is unused (underscore-prefixed, never read inside `show`), and both
+real call sites pass `null`. This confirms the modal always shows the same
+account-level webcal settings, it does not open scoped to a specific task in any way,
+regardless of where it was triggered from.
+
+## Entry point 1: header avatar menu, "Calendar"
+
+**File:** `src/app/common/header/header.component.html`, lines 91-102, wired to
+`src/app/common/header/header.component.ts`, `openCalendar()` at lines 243-245.
+
+```html
+
+
+
+
+ ...
+
+```
+
+- **Visible label:** "Calendar", with a `calendar_month` Material icon.
+- **UI hierarchy:** top-right user avatar icon in the global header (always present,
+ `