diff --git a/JobTrackerApi.Tests/ApplicationCalendarControllerTests.cs b/JobTrackerApi.Tests/ApplicationCalendarControllerTests.cs index 06577ec6..c8fe9419 100644 --- a/JobTrackerApi.Tests/ApplicationCalendarControllerTests.cs +++ b/JobTrackerApi.Tests/ApplicationCalendarControllerTests.cs @@ -4,6 +4,7 @@ using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Data.Sqlite; using Moq; using Xunit; @@ -11,6 +12,75 @@ namespace JobTrackerApi.Tests; public sealed class ApplicationCalendarControllerTests { + [Fact] + public async Task Agenda_date_window_translates_on_sqlite_and_keeps_legacy_wall_clock_date() + { + await using var connection = new SqliteConnection("Data Source=:memory:"); + await connection.OpenAsync(); + var current = new Mock(); current.SetupGet(service => service.UserId).Returns("owner-1"); + await using var db = new JobTrackerContext(new DbContextOptionsBuilder().UseSqlite(connection).Options, current.Object); + await db.Database.EnsureCreatedAsync(); + var job = await SeedJobAsync(db, "owner-1"); + job.Status = "Saved"; job.FollowUpAt = null; job.Deadline = new DateTime(2026, 9, 30, 23, 59, 59); + await db.SaveChangesAsync(); db.ChangeTracker.Clear(); + var controller = CreateController(db, "owner-1", new Mock(MockBehavior.Strict).Object, new Mock(MockBehavior.Strict).Object); + var result = await controller.Agenda(new DateOnly(2026, 9, 1), new DateOnly(2026, 9, 30), default); + var row = Assert.Single(Assert.IsAssignableFrom>(Assert.IsType(result.Result).Value)); + Assert.Equal("2026-09-30", row.Date); + Assert.Equal(new DateTime(2026, 9, 30, 23, 59, 59), row.RecordedAt); + } + + [Fact] + public async Task Agenda_includes_saved_deadlines_and_preserves_recorded_dates_without_provider_access() + { + await using var db = CreateDb("owner-1"); + var job = await SeedJobAsync(db, "owner-1"); + job.Status = "Saved"; + job.FollowUpAt = new DateTime(2026, 9, 2, 0, 0, 0, DateTimeKind.Unspecified); + job.Deadline = new DateTime(2026, 9, 15, 23, 30, 0, DateTimeKind.Unspecified); + await db.SaveChangesAsync(); + var controller = CreateController(db, "owner-1", new Mock(MockBehavior.Strict).Object, new Mock(MockBehavior.Strict).Object); + + var response = await controller.Agenda(new DateOnly(2026, 9, 1), new DateOnly(2026, 9, 30), default); + var rows = Assert.IsAssignableFrom>(Assert.IsType(response.Result).Value).ToList(); + + Assert.Equal(2, rows.Count); + Assert.Equal("follow-up", rows[0].Kind); + Assert.Equal("2026-09-02", rows[0].Date); + Assert.Equal("deadline", rows[1].Kind); + Assert.Equal("2026-09-15", rows[1].Date); + Assert.Equal(job.Deadline, rows[1].RecordedAt); + Assert.Equal(DateTimeKind.Unspecified, rows[1].RecordedAt.Kind); + Assert.All(rows, row => { Assert.Equal(job.Id, row.JobApplicationId); Assert.Equal("Acme", row.CompanyName); }); + } + + [Fact] + public async Task Agenda_excludes_other_owners_deleted_and_out_of_range_dates() + { + await using var db = CreateDb("owner-1"); + await SeedJobAsync(db, "owner-2"); + var deleted = await SeedJobAsync(db, "owner-1"); deleted.IsDeleted = true; + var outside = await SeedJobAsync(db, "owner-1"); outside.FollowUpAt = new DateTime(2026, 8, 31); outside.Deadline = new DateTime(2026, 10, 1); + var edge = await SeedJobAsync(db, "owner-1"); edge.FollowUpAt = null; edge.Deadline = new DateTime(2026, 9, 30, 23, 59, 59); + await db.SaveChangesAsync(); + var controller = CreateController(db, "owner-1", Mock.Of(), Mock.Of()); + var response = await controller.Agenda(new DateOnly(2026, 9, 1), new DateOnly(2026, 9, 30), default); + var rows = Assert.IsAssignableFrom>(Assert.IsType(response.Result).Value); + Assert.Equal(edge.Id, Assert.Single(rows).JobApplicationId); + } + + [Theory] + [InlineData("2026-09-30", "2026-09-01")] + [InlineData("2026-01-01", "2026-12-31")] + [InlineData("9999-12-01", "9999-12-31")] + public async Task Agenda_rejects_unbounded_or_invalid_ranges(string from, string to) + { + await using var db = CreateDb("owner-1"); + var controller = CreateController(db, "owner-1", Mock.Of(), Mock.Of()); + var response = await controller.Agenda(DateOnly.Parse(from), DateOnly.Parse(to), default); + Assert.IsType(response.Result); + } + [Fact] public async Task Status_distinguishes_connection_from_calendar_consent() { diff --git a/JobTrackerApi/Controllers/ApplicationCalendarController.cs b/JobTrackerApi/Controllers/ApplicationCalendarController.cs index 9aeaa265..713fd300 100644 --- a/JobTrackerApi/Controllers/ApplicationCalendarController.cs +++ b/JobTrackerApi/Controllers/ApplicationCalendarController.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using System.Globalization; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; @@ -18,6 +19,34 @@ public sealed class ApplicationCalendarController( IGmailOAuthService google, IMicrosoftGraphOAuthService microsoft) : ControllerBase { + [HttpGet("agenda")] + public async Task>> Agenda([FromQuery] DateOnly from, [FromQuery] DateOnly to, CancellationToken cancellationToken) + { + var owner = RequiredOwner(); + if (from == default || to < from || to.DayNumber - from.DayNumber > 61 || to == DateOnly.MaxValue) + return BadRequest("Choose a calendar range of at most 62 days."); + var start = from.ToDateTime(TimeOnly.MinValue); + var end = to.AddDays(1).ToDateTime(TimeOnly.MinValue); + var jobs = await db.JobApplications.AsNoTracking() + .Where(job => job.OwnerUserId == owner && !job.IsDeleted) + .Where(job => (job.Deadline >= start && job.Deadline < end) || (job.FollowUpAt >= start && job.FollowUpAt < end)) + .Select(job => new { job.Id, job.JobTitle, CompanyName = job.Company.OwnerUserId == owner ? job.Company.Name : null, job.Deadline, job.FollowUpAt }) + .ToListAsync(cancellationToken); + var rows = new List(); + foreach (var job in jobs) + { + Add("deadline", job.Deadline); + Add("follow-up", job.FollowUpAt); + void Add(string kind, DateTime? recorded) + { + if (recorded is not { } date || date < start || date >= end) return; + // Calendar grouping uses the stored calendar date; legacy timezone/precision is not reconstructed. + rows.Add(new(job.Id, job.JobTitle, job.CompanyName, kind, date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), date)); + } + } + return Ok(rows.OrderBy(row => row.Date).ThenBy(row => row.Kind).ThenBy(row => row.JobApplicationId).ToList()); + } + [HttpGet("status")] public async Task> Status(CancellationToken cancellationToken) { @@ -141,4 +170,5 @@ public sealed class ApplicationCalendarController( public sealed record CalendarStatusDto(CalendarProviderStatusDto Google, CalendarProviderStatusDto Microsoft); public sealed record CreateCalendarEventRequest(string? Provider, string? Kind); public sealed record CalendarEventDto(string Provider, string? Id, string? WebUrl); + public sealed record CalendarAgendaItemDto(int JobApplicationId, string JobTitle, string? CompanyName, string Kind, string Date, DateTime RecordedAt); } diff --git a/docs/ui-modernisation/CALENDAR_VALIDATION.md b/docs/ui-modernisation/CALENDAR_VALIDATION.md new file mode 100644 index 00000000..f849b02c --- /dev/null +++ b/docs/ui-modernisation/CALENDAR_VALIDATION.md @@ -0,0 +1,29 @@ +# A3-IM07 calendar subpackage — local qualification + +3 November 2026. Owner approved progression to A3-IM07 after UI06. This calendar subpackage is independently implemented and locally qualified; **A3-IM07 remains open** for correspondence/document presentation and combined preservation evidence. No next milestone acceptance is requested yet. + +The existing `/reminders` route now opens a date-level agenda above the unchanged workflow reminders. Agenda is default, with a persisted owner-scoped Month toggle. Upcoming recorded dates precede earlier dates; month selection and native calendar-day buttons retain linked application access. No view action creates a provider event or alters a reminder. Earlier reminder snooze/clear/retry/focus behavior remains with RemindersView. + +Added authenticated `GET /api/calendar/agenda?from=YYYY-MM-DD&to=YYYY-MM-DD` to the existing ApplicationCalendarController. The inclusive range is limited to62 days and translated to an exclusive next-day query boundary. Explicit owner and nondeleted filters precede a narrow projection; company labels also require matching ownership. Deadline-only Saved applications are included even though the reminder attention feed excludes them. The response contains jobApplicationId,jobTitle,companyName,kind(deadline/follow-up),date and unchanged recordedAt. There are no schema, migration, model, writer or provider changes. Existing ICS and consent-scoped provider event endpoints are unchanged. + +Calendar grouping retains the stored date; the view does not convert an unzoned legacy timestamp into an inferred timezone or appointment time. Original recordedAt stays on the time element and linked application. Current data has no interview appointment date and lacks original source precision/timezone metadata; that remains an explicit capability gap. Interviews are not fabricated from stage or note text. Undated work remains in the existing reminder workflow, not a guessed event. + +| Evidence | Result | +|---|---| +| Baseline |70 existing reminders/calendar/documents/inbox cases across4 suites passed; not additional to overlapping later checks | +| New contract red |Backend compilation failed because Agenda/DTO did not exist. Frontend suite could not resolve new component. These are missing-contract red checks, not executed runtime test failures | +| Intermediate defect |Initial new month-navigation test failed: shared resource hook requires explicit dependency values, not just a new callback. Supplying from/to deps fixed actual missing month reload; no shared hook behavior changed | +| Final frontend |25 selected cases/4 suites passed: new agenda8, existing reminder/calendar preservation and catalogue coverage. Reviewed7-case rerun overlaps and predates final upcoming-order case | +| Backend |10 passed, zero failed/skipped. InMemory/isolated SQLite: saved deadlines, raw wall-clock date, inclusive last-day boundary, sibling-owner/deleted/outside exclusion, invalid/oversized ranges, and unchanged provider/ICS contracts. Strict provider mocks prove no calendar-read provider access | +| Types/full lint/build |Passed after source fixes. Initial typecheck found unsupported test query options and missing required error title; corrected, not relabelled passing. Final build includes TypeScript validation | +| Rendered React |16 configs320/390/768/1440 × EN/NB × light/dark;48 whole-page axe scans/captures, zero violations/overflow/page errors/unexpected writes. Final rerun after upcoming-first order supersedes prior overlapping browser report | +| Connected controlled journey |Three occupations; agenda date provenance, month/day selection, next-month empty and previous-month read, persisted view on reload, keyboard-linked deadline to application detail/context. Every API intercepted; no live backend/provider or mutations | +| Independent review |No important/critical source findings;7 overlapping frontend cases passed. Awaiting empty state fixed reviewer's test-only act warning; no provider/backend/browser run independently repeated | + +Native frontend/backend/browser evidence and source manifest are under [evidence/calendar](evidence/calendar). Native API tests use the repository SDK at C:\Users\Connor\.dotnet and no restore/network. Browser verifier: `rtk proxy node scripts/verify-a3-calendar.mjs` from job-tracker-ui against static export localhost4338. Pilot/previous-source reports overlap and are not added to totals. Selected browser fixture dates are recorded synthetic dates, never advertised as live appointments. + +Rollback reverts this subpackage; no database/data rollback. Existing route, reminder rules/actions, provider consent, calendar export behavior, Career/CV/letter revisions and frozen package contracts remain. Full Phase10, Product Review, European localisation, Global Jobs, final integrated security and release acceptance remain open. + +NOT RUN: real backend/browser integration, MariaDB, hosted CI, native devices/screen readers/touch/zoom, actual reminder delivery, Google/Outlook live calendar, timezone/precision migration, interview scheduling, final security/release qualification. The existing legacy follow-up export precision/timezone behavior is preserved, not newly qualified by the date-only calendar. + +Next authorised work: narrowly reconcile validated document date patch ca519cac, complete document/inbox connected presentation and preservation checks, then publish complete IM07 evidence for its required Gate F. No provider activation or final UI08 gate is implied. diff --git a/docs/ui-modernisation/UI-07.md b/docs/ui-modernisation/UI-07.md new file mode 100644 index 00000000..636111cf --- /dev/null +++ b/docs/ui-modernisation/UI-07.md @@ -0,0 +1,18 @@ +# A3-IM07 Calendar, correspondence and documents + +3 November 2026. Owner explicitly answered **"Approve Career/CV/letter and A3-IM07"**. Isolated `codex/a3-ui-07` starts at accepted/published UI06 receipt19dad316. Approval record7e410e79 is published/remotely verified. UI06 implementation6f801251 and all earlier approvals remain accepted; no repeat gate. + +- [x] Inspect current reminder, calendar/provider, document and inbox owners and approved A3 journeys/coverage. +- [ ] Establish selected baseline; preserve existing reminder mutation/retry/focus and correspondence/document trust boundaries. +- [ ] Calendar: agenda first with persisted view-only preference and accessible optional month view; include recorded deadlines even for saved applications missing from attention feed. Keep workflow reminders available, linked context, raw date provenance, unknown-date disclosure, loading/error/retry. Do not derive interview times from stage or assume timezone/precision lost in legacy DateTime storage. +- [ ] Qualify owner-scoped bounded calendar read and server/local date boundaries, including soft-deleted and sibling-owner exclusion. No schema migration/provider activation/calendar writes needed for calendar display. +- [ ] Correspondence: quiet inbox/context presentation using current read/draft/send owners; preserve uncertain outcomes/conflicts/explicit confirmed-send and provider permissions. No live send. +- [ ] Documents: connected library/filter/source/download presentation, preserve URL allowlists, frozen data, private/public state and failures. Reconcile the independently validated ca519cac date patch narrowly before combining it. +- [ ] Targeted frontend/backend/types/lint/build and controlled rendered EN/NB/themes/widths/focus/accessibility journeys; independent review. +- [ ] Publish coherent validated checkpoints, verify remote refs, update evidence and durable handoff. Next mandatory Gate F before A3-IM08. + +Scope reaches existing RemindersView, ApplicationCalendarController, correspondence/document presentation and their tests/fixtures/catalogues/navigation. Existing reminder attention feed excludes deadline-only Saved applications; using it as a complete calendar would silently omit deadlines. Add a narrow bounded read to the existing calendar owner instead of changing the attention feed's meaning or fetching an unbounded full board. Existing calendar ICS/provider exports remain unchanged and require explicit user actions/consent. + +Current JobApplication fields store Deadline/FollowUpAt as nullable DateTime, with no independent interview appointment or original source precision/timezone metadata. Display recorded calendar dates with raw values retained, label the date-level boundary, and do not manufacture appointments. Any later interview scheduling/data-model qualification must be recorded separately. Unknown-date attention remains the existing reminder workflow, not a guessed calendar event. + +Rollback: revert this feature increment; no database/data rollback for presentation and bounded read. Preserve canonical Career Profile, CV/letter history/settings, RD21 and all previous routes. Full roadmap/security/release acceptance remains open. diff --git a/docs/ui-modernisation/evidence/calendar/CHECKS.json b/docs/ui-modernisation/evidence/calendar/CHECKS.json new file mode 100644 index 00000000..e3272bb0 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/CHECKS.json @@ -0,0 +1,44 @@ +{ + "recordedAtUtc": "2026-11-03T13:50:59.687157+00:00", + "frontend": { + "passed": 25, + "failed": 0, + "pending": 0, + "suites": 4 + }, + "backend": { + "passed": 10, + "failed": 0, + "skipped": 0, + "provider": "InMemory and isolated SQLite, strict provider mocks" + }, + "typecheck": { + "exitCode": 0, + "earlierFailure": "test ByRoleOptions and required ViewStateNotice title corrected; final production build type validation passed" + }, + "lint": { + "exitCode": 0 + }, + "build": { + "exitCode": 0 + }, + "graphify": { + "exitCode": 0, + "limits": [ + "optional SQL parser missing", + "144 symbol-less files; not semantic completeness" + ] + }, + "backendRed": { + "status": "FAILED compilation before endpoint/DTO existed", + "executedTests": 0, + "nativeTrx": "NOT GENERATED" + }, + "browser": { + "configurations": 16, + "scans": 48, + "violations": 0, + "writes": 0, + "liveBackend": false + } +} diff --git a/docs/ui-modernisation/evidence/calendar/SOURCE_MANIFEST.json b/docs/ui-modernisation/evidence/calendar/SOURCE_MANIFEST.json new file mode 100644 index 00000000..b061af93 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/SOURCE_MANIFEST.json @@ -0,0 +1,169 @@ +{ + "base": "7e410e795dd3f2e8c07091e9d276ce05bb418866", + "recordedAtUtc": "2026-11-03T13:50:59.687157+00:00", + "policy": "Text LF-normalised SHA256; PNG/export raw bytes", + "sources": { + "JobTrackerApi.Tests/ApplicationCalendarControllerTests.cs": "cb0b9e34142a3604041b6696a5b52490f86dffd1a895de6075a750eb7efde498", + "JobTrackerApi/Controllers/ApplicationCalendarController.cs": "137ff5b81f0249d29e790e1b89c75e0a426cbaecdf5dd51c62b11e43a694d13f", + "docs/ui-modernisation/CALENDAR_VALIDATION.md": "91092f4b24713025830db28cc928d71287ad3c6172f83ec1514d8f2b7998d2a9", + "docs/ui-modernisation/UI-07.md": "135a44fe24b71606982cf0e4b7bafed2813e0f0f91d6e2fef6e90ae462277837", + "job-tracker-ui/package-lock.json": "bf31e90d7af03e8af059db2aaf893925b9e8881a30a1688034ed88ef45d4fcab", + "job-tracker-ui/package.json": "3745394e406cefa40d30a87e840bccb359be147bac6ffba736e2ae90898a8626", + "job-tracker-ui/scripts/verify-a3-calendar.mjs": "e3fd86fee1efffbf2a3326c99bc93c9d6ffe19af31d8ea347fb084111222065b", + "job-tracker-ui/src/App.tsx": "a176e52d81c4b6ded63c5f38cb27e853aa09312f2cc45fc7e5b5e44573e0fef3", + "job-tracker-ui/src/application-calendar.test.tsx": "d82d87ccd456ada0797fa32e1d437224cae1edf62e40c05e1cb87f233a0eb40e", + "job-tracker-ui/src/auth.ts": "1cbdd59f73c5a961613b2569679f96630cded407974876a4ca8abccf28780b0e", + "job-tracker-ui/src/calendar-agenda.test.tsx": "c9726ab21c6623a0cdfc992e8412f3aecb7b06c587ccb1a9b6e085c55d9d9298", + "job-tracker-ui/src/components/CalendarAgenda.tsx": "383eba95ea8b5b9594f2f9f79a481dff4b7468091a3d757868825257dead2680", + "job-tracker-ui/src/components/RemindersView.tsx": "e550e76cb28cb4236100bb1a35158a329e4ecd3a12be56f6cdaf28fb75fa0091", + "job-tracker-ui/src/hooks/useViewResource.ts": "5912131c525ba85ae6994efc38306a10ec2e673cc0082686226b59f00df5da99", + "job-tracker-ui/src/i18n/translations.ts": "1dd55d184eec8e43ba5aaa4d71b60a66615791b87b712bdc6c50be0aa4cfbbeb", + "job-tracker-ui/src/reminders-presentation.test.tsx": "700594101a38593e950ee10340fa0d22bafbd18f928912bf4af58172642ace9e", + "job-tracker-ui/src/views/CalendarPage.tsx": "45c827d8268bc6be93735508dd45c464a3c40246a7e530555a0ff90bcd03359e" + }, + "evidence": { + "backend/a3-calendar-final.trx": "4c3e091d637cb32700ccf2531b56434b48ac4a7756611ac188cf874d40fd01a9", + "backend/a3-calendar-green.trx": "356e5cee5ae69ac0f318836d3d7e09eefb925e7a6b916283820f1f0c33a5fba4", + "browser/agenda-en-dark-1440.png": "4c2cdba871ce4c7fb13a9cead97fa8775057a54cbf01ea9d5c9d6ae2d78300bf", + "browser/agenda-en-dark-320.png": "bbd32e62b13d7919a191b0990aef5d0c5f26a9c0532187b40de1329ae1a0c9aa", + "browser/agenda-en-dark-390.png": "7e6cdb2d60b76475234247400499e4f5e1ecd8eda9d57dc4539ec507e3ef572d", + "browser/agenda-en-dark-768.png": "1b8e4fefc6d0389b08c23ff43193875a7248177c717baf25f800f4a9afabc6ac", + "browser/agenda-en-light-1440.png": "f6be3cf29572729e5923698a8d237db4ee72783b85b56a66d707e69b57359752", + "browser/agenda-en-light-320.png": "1a0d543c73bce4e1f28b0e13bb93d7956f28b9438e60be4315abb22947086662", + "browser/agenda-en-light-390.png": "7c7d74d4f07ce16fd7f555d659f2b60657465f3c0a5be08e328b908746052946", + "browser/agenda-en-light-768.png": "3fc0bb5601992c24f2fb77323a8c5e798431c225bd518ec22bacd006f70e54e3", + "browser/agenda-nb-dark-1440.png": "f1ff22897c671115ddc87f55f0097d60e4bac6c891d6388a0a6b5d7e4b683c1e", + "browser/agenda-nb-dark-320.png": "a22e8585a331a53164a3c154d7f6d3436bc2b4fc7e230a353990e8f86b13ef8a", + "browser/agenda-nb-dark-390.png": "fbea15f1fa3840370f07bf05aba704c49c6fcc3193dd8e5d95bbd9cd6a290766", + "browser/agenda-nb-dark-768.png": "d436706161b8ff353ed3828c28067a2daf6ade88643529b2f2388660c006124e", + "browser/agenda-nb-light-1440.png": "ec12f39aa16094bb43c8f46d494a049082e99ed3a27a56071fde3b221f20d2e1", + "browser/agenda-nb-light-320.png": "ca093b4581be0fbb1545d7cec9c62c3381ef4c7091eb9808fc7a8e4da7433efa", + "browser/agenda-nb-light-390.png": "10ec8e205314ec6e0cc0deabbb565e635eb7a1b63da36704255ede9d07230185", + "browser/agenda-nb-light-768.png": "d35711ff5c7ce81d03ab7351479e167ec58fb57ca17383106a95b91d891b364a", + "browser/BROWSER.json": "dc24786edeb8afa886c5033e5d7928962c87c325141cad319ddc1c9b6e8d8765", + "browser/linked-application-en-dark-1440.png": "f1064f2f79853d48b8d15919b51db03a5dea800fa60b607d7cf8271411903304", + "browser/linked-application-en-dark-320.png": "ce1f364624b5ccb3d7f2a5abbbc268ef05e3d19c8ed799b2586a05fa12850673", + "browser/linked-application-en-dark-390.png": "5617dce472c4f7a117259b89f6a702eff90c2e8e82ae58f65df5bafc1527346b", + "browser/linked-application-en-dark-768.png": "d31e67b030d3dea61df81e1c38a314143d74455cbe80e6f2175752e1332b532b", + "browser/linked-application-en-light-1440.png": "58523ce12f40975a8966a17e00ec2546e460c0e0ef37120ae850e70fb956a0cb", + "browser/linked-application-en-light-320.png": "441b76a051aeb553e203e1bc1fe46ee45e4bb4b0b4d17e883ed572ee9a8d1ed3", + "browser/linked-application-en-light-390.png": "26122d43c985f71306e7707fb11352665fa51b75cf421586e257cb314d33c82e", + "browser/linked-application-en-light-768.png": "b94555fbd0d2a7d7581de9f2f25aacb10c7b194cf6ef2b5604ea258759bdbeef", + "browser/linked-application-nb-dark-1440.png": "da3d806e4f4f931ef037d498409be38e0e07fbcb7efe6110bfd754359f1b020d", + "browser/linked-application-nb-dark-320.png": "acfa5c1152af3d8f3b97377da94b0bf9ba4bcf2dba62a1842b6e6206a18cb91f", + "browser/linked-application-nb-dark-390.png": "b9c21a5cfd07c56b2dc4ee59b00f6bcd102fe931e73f3ac28826f3ee726e8043", + "browser/linked-application-nb-dark-768.png": "fadf9ed9cec6172e5d0500564c56de33e8ce6eaa05cc924d905c0e28ebae0f4a", + "browser/linked-application-nb-light-1440.png": "a76cabe6225ec6864d8a71edec5c91803d4ba85bddd0e887089030b78d8a80b7", + "browser/linked-application-nb-light-320.png": "75ce3778f211b2ce53c67ace7cda310848ffa2d32bdecbb3403d3b59bc057498", + "browser/linked-application-nb-light-390.png": "08efcd767ca130e0aff0cff212ced33190c3236cfd2f2b54b0f0bbd471b65809", + "browser/linked-application-nb-light-768.png": "c3059ddaae94da9f205d928156ab5e9056021f8659faa3c2220f97601e3113b7", + "browser/month-en-dark-1440.png": "fe71b1bdf88857cee9198dca37e3cd9dd7b0f7519515106197adbcd44b354d40", + "browser/month-en-dark-320.png": "84f6e6a7f2d3bcbb8d0bbb96d24376dec7920af540effd278146fafc13f8eb95", + "browser/month-en-dark-390.png": "8c6103e95bef123fc67ba8e5dbb342c828b695be2975b0e351c6c3a9023119be", + "browser/month-en-dark-768.png": "23032481b2426b3644dfd34f5bf000aba04027d3db490638afd98da092b2a308", + "browser/month-en-light-1440.png": "a6cb881e9fd85531a28375dc8128a7d68526d1ed20d959f07cd89a80749caeac", + "browser/month-en-light-320.png": "2dc90a543b38e251ad39f764df01bdf1a131630b9cc0ec5262b30f3dcef1661b", + "browser/month-en-light-390.png": "2eab1e18a90c567167825497a89eddf5eebc064b02e59b2036ea595c56cebb53", + "browser/month-en-light-768.png": "8d6feb49163d539c73297be0aaa3d35391e01faceb84fd79236ac7edf176fb89", + "browser/month-nb-dark-1440.png": "44d59e386c2b4f49f7a466f40fa78e4d134c938bace90b8e2085bcb9c4e97a6b", + "browser/month-nb-dark-320.png": "fb70fe794d2a8a10a4f2a6dd58c5fa23cd4e8123189e83ccf44fd49bde8dbace", + "browser/month-nb-dark-390.png": "da5ec5924038880c9444b00f085d36daa439da7a3824caf43564f26729131f9a", + "browser/month-nb-dark-768.png": "ae19af78a6a156e8225c3fd193464447ec12283ee902effbd07cc12ff5816e2f", + "browser/month-nb-light-1440.png": "508c85bf253e32a4d1d2515cc9220227021db3a3f706ac5197f58adc7d27fb5e", + "browser/month-nb-light-320.png": "0bdffca7386e00f84294cee52a68a97049a3f089737190ee88db1ae537d45e68", + "browser/month-nb-light-390.png": "2fa7f775b6c5c6bea1ff92b0e4d81176c886a64385000e23655188fb49a5af9e", + "browser/month-nb-light-768.png": "bb21a2bf2752e2641e18dad30c285cecc596468f0359c58dee2b95178693f711", + "browser/PILOT.json": "d692a8540bc2e412327c34c5eb7787ddd7ffb0da0dafc595c8d63186bfe6c33f", + "browser/PRE-UPCOMING-ORDER.json": "dc24786edeb8afa886c5033e5d7928962c87c325141cad319ddc1c9b6e8d8765", + "CHECKS.json": "b452adef2c74d513ae8a8ac0e0fa00d47353be2abe58b0cef908ee5c8413acff", + "frontend/a3-calendar-ui-final.json": "5029c7b8afd5a1bf5ef6fe9f506795548f60b337d2f9613a5415197f9b50893d", + "frontend/a3-calendar-ui-green.json": "c9c9f7c38d8eff473226364f96957715c63e106d27b1a61e74adfa766ead6f88", + "frontend/a3-calendar-ui-qualified-final.json": "8365d6913c7dc7bac0b38f1f99ff1b57eecad8d2276974d7e46d7a75d535b159", + "frontend/a3-calendar-ui-qualified.json": "2bf0b2ab0d2e0c573eaca07f67242f691fd5b673ba2c4119f05338cbd5b1c996", + "frontend/a3-calendar-ui-red.json": "278d7fde84ae5f678e5004448b53d758b7cf336436e0a0c033c6d01eb6c5c4ca", + "frontend/a3-calendar-ui-reviewed.json": "87d031d4ba08acd1dbecf601b8f4d0fcfc9a831d18e2ffdc07e96aa116cc494d", + "frontend/a3-workspace-baseline.json": "9fc273f5f2e0a77b9271b8b1985a5b9dcab781a023accafd8b00d3a64d1fc38c" + }, + "export": { + "404.html": "a9116ceba97e13b799aeddfc5acb9ac49b920f2e741d228c081ed21ce5ec38b2", + "__next.__PAGE__.txt": "e15dff657a85232f110c89d5617633e0e9858ea8f64627cd01f4766a0d5753e4", + "__next._full.txt": "030eb6a898b15f225c0e642e757f41c76546ac0c08bef77dd2eb4a866099043f", + "__next._tree.txt": "553ed7e7f8ae324983bce4a56cb39f819d7ea03ca3291627f0e782b59c735f5c", + "_next/static/chunks/0-_aq6ipzcshj.js": "39f90c215bf52aef6274ed3dcc0f8af32e21a37fe8845b3bca0d6ea60da6a8c2", + "_next/static/chunks/0ah1fzqmie05a.js": "5c65505a7dcbac63ac71d254e619a4c800943bfb4fa16cd40c10cdbe7e9b6e31", + "_next/static/chunks/0bdt7ln111t48.js": "6184b1a61f22e2a640c17941634fdc75678ce93d142848d9f07f163e0d1fd201", + "_next/static/chunks/0bwvt3mysnclc.js": "7705667a1b75812b7833d9c9dff47fe023015e58e382c40f4507d9948c973af4", + "_next/static/chunks/0cz1d0mv5g_q7.js": "0973c1d64c88adc8e3c950410cb58b288f72118d5965b78049438deb8f2f9683", + "_next/static/chunks/0h_-smu-bk788.js": "970ef0fa47ce6dfff261c624e08054841b6969b4afc5b0ebfad89126d7543bbc", + "_next/static/chunks/0jcvc3qm6-lyk.js": "7c27e1fe373b822adc68c63096a5bf38444ad605b9743f0920271652989c56f1", + "_next/static/chunks/0lmxsmlypasq-.js": "695c697c2e38fea85325dc210c9d3ff38fe5d4295e386041c7f187322f0f537b", + "_next/static/chunks/0q20g9o8x-k8z.js": "3d4d9086985419aa8866b032158196c774acb8bf318f3c8245508c8daa9c5148", + "_next/static/chunks/0q_pwu9jxv4-d.js": "42a43b3d7245a413c7c29b4cbe87823037a6dbc637e2e282c92405470e0f575d", + "_next/static/chunks/0s7d19phl1gc6.js": "270ac30d2cfd8392d59853086c1cf59e91ade44fc569151f14806f6684643ef0", + "_next/static/chunks/0yg1x7i96lj87.js": "2aad3c0f9e8b7b5a5123a2a439970c49db0be353236e22f9b6eafaaf01328c15", + "_next/static/chunks/11-48p8kygxgp.js": "57eb9059599b5a5a30598d0a334912d58af571c0ffe28448ca76ad77c8ea8df9", + "_next/static/chunks/14-lfugawba2w.js": "6a5780ca3c84d7f130f3c0a14431c85c7a1e799e2645b8c171161dab40a64360", + "_next/static/chunks/1435in_k_-dhc.js": "4757476512d5c4dd25f9264a18b304ed4824b23aff1c09e134fcbff21d9c36b6", + "_next/static/chunks/145oicobmjvm7.js": "d6bbbdeed1381652dc7412e5ce673a077579ecd5cee8934cb6b533c814098764", + "_next/static/chunks/14e7ovj0s3elm.js": "070567dd4d23fb9fa0b9ba132185a697af6578045d43b2b1c7bbb2a1594d935d", + "_next/static/chunks/16lfece6cnlln.js": "716363ba044f30b0e70440756b4e5eafa2d25f2e1628e855e3bc4ab9eabee3d3", + "_next/static/chunks/19fpz66d6-1lv.js": "591aa30f32b95af8a55173c0da5ef146996c6161405261102cb2abea8a12bad5", + "_next/static/chunks/1d0xrdyyzw7qc.js": "c11668ff1e152008e0d7b4454af12eb36c97df46a5860938e2eb5d8429b374a8", + "_next/static/chunks/1fvwm-pcm8rrv.js": "1027c6b5c28e184a75ede28980fcf6e32e8cfde7da39191911b171668c76d888", + "_next/static/chunks/1ke8pzecwofrg.css": "32e9ab963ebadebcbe2c68dffb24cfbfc8cc24255ad91b48b7941fc777bf7439", + "_next/static/chunks/1p3h755jnx-jb.js": "81db4740766fddca1b4f31a1a686e35a3f18ee078ed74561b5d2daee5fd01bc0", + "_next/static/chunks/1req80b9jj38r.js": "eee4d2e3789e1215195dba99ad0f09d34f8c83da848322b76054722233e9dae3", + "_next/static/chunks/1w3q8yrcjn2jk.js": "365200e241ee9290e93d7c2fdba169f2b0a8e774541e3cbf3f6f2781110fa644", + "_next/static/chunks/1xn023_28rgzj.js": "4f7f2108d030a48d039930736c92a55710975dddf419fe673f84d92899b1f575", + "_next/static/chunks/1xwc-b795syag.css": "f73c002233f931ca14d0ae58815c47ca9f2659f25b1547a6d1fdca12a2a00b3f", + "_next/static/chunks/1zi8i6dnxiac_.js": "8aea28bbe6a138e5d4b619429ca71570883a7aee974b86d5a4a746d90f704dbc", + "_next/static/chunks/22wx8--1b9tnq.js": "3f7d8e762371262c6a740b4dac21725b424847319afac46055e97074a99ff5f5", + "_next/static/chunks/27n3w-k3wc2if.js": "941c18edde0feb228b8aa5d3ec0e636bcb4dc2901134c2ad63224420a34eb5b1", + "_next/static/chunks/280cq9ekejv1f.js": "01de8c1c3555cdd8d711763065ea9d8aec2968c3053f61254e43e26c999027bf", + "_next/static/chunks/2br3ad_-bc7k9.js": "80fa55f7a32db4d743fda36747c36994fde00a0f53f2eb25b2a6d07c6fb97cd7", + "_next/static/chunks/2gd9s_n-0nl1p.js": "31e2811be51a3c4d0ff2d1e3cea756e7fd8d57ef89141b5de69f638c1301d039", + "_next/static/chunks/2ghgf2s9b2iou.js": "23d8ff31e2128d0541c1887785fb75885323fea7c5d4f35826513b75c12c2ae3", + "_next/static/chunks/2itnratrnd9c5.js": "8be0b191c2d54f7233ebf2f1e169b3bfef680fa93da21c08d8c43b05c3ec174f", + "_next/static/chunks/2kpcazmi6n6mk.js": "bea0de681fceff50056bfeccc5d035aff54d20918288051cb64bb259edd250c7", + "_next/static/chunks/2rz71hs6q-h-c.js": "822e01feeeccf06e5634ebb76a5deeda6ba27c5d9f6575047eb203dd8af18f91", + "_next/static/chunks/2s2gjr6ro25dj.js": "7489e2ac285a05b563c2bcb4eaf9274bf930f2a7385f17d6d5e6850064b88b10", + "_next/static/chunks/2u5qve004opku.js": "be388d9a6aa423082bafb4584c3facc65ddfed71bcfa5c1d06b097b408c5ff97", + "_next/static/chunks/2xlg4_1fj8fxh.js": "8ab4ba521db0167f3dbf684787ebf643cbc66fa7ffff7cdc65a8d8076a926155", + "_next/static/chunks/2z42el0b2mp8_.js": "86ff990ef32c90c4839583e4f1d1bcbddb165dad3141b9fcd2ecec67f4ff24b8", + "_next/static/chunks/2z9e_b61gtc22.js": "b9d717dad26828438d4ff56cabca41a8055f5eaa54420fd0aeb6b4ba7d5baa0d", + "_next/static/chunks/32_ah983ce851.js": "0c3cd0d2a5ad789e31226a9ea0506c50016804311a58548c7784b7b45aaecd14", + "_next/static/chunks/334vtqp3-g1j6.js": "c33d42da97f3af7388e29273297e9f42c67f54b7f5ec77a5d0637e2da06d7f7e", + "_next/static/chunks/357yqeklh7414.js": "0192ab7198ebbe299d34a934c504dd4b562d1cccebb6e9d4c5163930c881b14c", + "_next/static/chunks/3720519r84mh6.js": "54358db392a13914e3363046d594e824b1a3ef987913d95002bf48267dded4aa", + "_next/static/chunks/3721l8sfm8icl.js": "06b67611b3a93515a375b803b54fe7063c049824f1ebf5c7f7db8701b5091e7c", + "_next/static/chunks/3bqn9b34na0b9.js": "f1b8ebcc1b22251c33c9f3a5d11c1f893cad2da2e8744a441e790f7347aa9e73", + "_next/static/chunks/3ckk_j5ril9ag.js": "87f631dae7b648d32171e9e05a3e000753ecb54f8d3b86a4401c0e4ab4d73553", + "_next/static/chunks/3gkylew_qn7cw.js": "7e6c74c81be0c41ab433e36d65b914f62c48f332922e27f7cc953476f4dd78cb", + "_next/static/chunks/3la4obitihzpv.js": "9a5a57b46db389b98f1fd5baa86eba1a360300dae9d1ff3ffeae7bc3378135b9", + "_next/static/chunks/3q4v10rrqy40n.js": "d87ff7b7a0f07d0d92eab3876481eb686a12d9c43d8b92cbe406aefa227a11c1", + "_next/static/chunks/3u61o8t7m_jza.js": "86231c12e0f8cacc8638bed0bc4b34f645fc35e60de342dc154d3cce0c9a5afa", + "_next/static/chunks/40y_aqqilokr2.js": "1f5a5260316134d6bffe949400752ee6b42addf908ae714ef4ae4ebde916dddd", + "_next/static/chunks/43b5x8qjjh3ax.js": "6ee7261cf9a63e0e6ea181bbb09125e09948588b6e10b5c02727c686a6062a3a", + "_next/static/chunks/442d7ylb3po0z.js": "d95cedfbdcd00c674fd718cbbf391ec01c0728018b0db2461a06df7074049132", + "_next/static/chunks/turbopack-25fzg9ay_1ss-.js": "c40f39e993a30c96d6247bb3a7b581538bb046efe8ef8ec07bea3ba32291750f", + "_next/static/UaBN5JCH6ak16uUfBqNLe/_buildManifest.js": "9476e0afd3f9da68a64e6ec58631191fd3eaca62c9f5a377e7517fe9a89e59ea", + "_next/static/UaBN5JCH6ak16uUfBqNLe/_clientMiddlewareManifest.js": "5483e3295dc35dfe29ea932efc85341c916f10ff78ff5b0e965e003710893a4a", + "_next/static/UaBN5JCH6ak16uUfBqNLe/_ssgManifest.js": "678f6ce2cb80b1fe72fc67e7412be6e2ab6ada083111b64f7c40d35e3cba5e00", + "_not-found/__next._full.txt": "91df8a13d382182604ebcb64955f1c3c3061039a77baf2dc3cf7b538f697b714", + "_not-found/__next._not-found/__PAGE__.txt": "df1add43ba5b477152317ef94c947cbd4ee0ed710d3637e251470ae6bb324e7c", + "_not-found/__next._tree.txt": "6d813cb1846cf7239fa4707821197719a9f1360022c9b7c4c5e3d69ca5769954", + "_not-found.html": "a9116ceba97e13b799aeddfc5acb9ac49b920f2e741d228c081ed21ce5ec38b2", + "_not-found.txt": "91df8a13d382182604ebcb64955f1c3c3061039a77baf2dc3cf7b538f697b714", + "favicon.ico": "8d4923e2c487805ea9f852a685b2cf0fb10512ded8290d2a5c37d1cc8fd0702a", + "favicon.svg": "63ecf8f37f8bb146ecfae3917187c6b692d4e801641e8a314486a4007912f3a3", + "index.html": "8f6c7594f4b55f3a61b938d2876a3e23fbdd31cbba38c637a4e4eecfb5517a78", + "index.txt": "030eb6a898b15f225c0e642e757f41c76546ac0c08bef77dd2eb4a866099043f", + "logo192.png": "bc652031597fabd19b9f14e22a71f809490d218037521b3b64d013418c1390b4", + "logo512.png": "5129e9bc1ba812b711fd9a646d3a38a906d365a26ee685df183b8752458944d4", + "manifest.json": "2667ce63c2e0835b7007239816a5597639321d333da396a78638dfa9e8a7c935", + "mockups/dashboard.svg": "318be3adb1324c26eb30f05572a29528a3e38331c6a604652eef75b54f8c29dc", + "mockups/pipeline.svg": "db2ef41faabeb90e7ce85413e168581686c54535d970fc7d8deda3888a479de4", + "mockups/workspace.svg": "9bbab22349147c7b01d6e18c93c622a6f8aed6dedaf23715189ab54d60e1ddbd", + "robots.txt": "e5ab0d231eeb01b4a982d1c79a6729cac9797ad15a69247e4f28ba6afc149b4c" + } +} diff --git a/docs/ui-modernisation/evidence/calendar/backend/a3-calendar-final.trx b/docs/ui-modernisation/evidence/calendar/backend/a3-calendar-final.trx new file mode 100644 index 00000000..9901bc46 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/backend/a3-calendar-final.trx @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 9.0.19) +[xUnit.net 00:00:00.11] Discovering: JobTrackerApi.Tests +[xUnit.net 00:00:00.25] Discovered: JobTrackerApi.Tests +[xUnit.net 00:00:00.26] Starting: JobTrackerApi.Tests +[xUnit.net 00:00:01.91] Finished: JobTrackerApi.Tests + + + + \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/calendar/backend/a3-calendar-green.trx b/docs/ui-modernisation/evidence/calendar/backend/a3-calendar-green.trx new file mode 100644 index 00000000..4e2fc53e --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/backend/a3-calendar-green.trx @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 9.0.19) +[xUnit.net 00:00:00.19] Discovering: JobTrackerApi.Tests +[xUnit.net 00:00:00.35] Discovered: JobTrackerApi.Tests +[xUnit.net 00:00:00.35] Starting: JobTrackerApi.Tests +[xUnit.net 00:00:01.39] Finished: JobTrackerApi.Tests + + + + \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/calendar/browser/BROWSER.json b/docs/ui-modernisation/evidence/calendar/browser/BROWSER.json new file mode 100644 index 00000000..5a6cf9df --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/browser/BROWSER.json @@ -0,0 +1,929 @@ +{ + "screens": [ + { + "width": 320, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + } + ], + "journeys": [ + { + "width": 320, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + } + ], + "errors": [], + "unexpectedWrites": [], + "reads": [ + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + } + ], + "blockedExternal": [ + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com" + ], + "limits": [ + "Controlled API fixtures, not a live backend/provider or reminder delivery.", + "Date-level recorded deadline/follow-up display; no fabricated interview appointments or source timezone reconstruction.", + "No native-device, physical rendering, final security or release qualification." + ] +} \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/calendar/browser/PILOT.json b/docs/ui-modernisation/evidence/calendar/browser/PILOT.json new file mode 100644 index 00000000..3171b831 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/browser/PILOT.json @@ -0,0 +1,74 @@ +{ + "screens": [ + { + "width": 320, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + } + ], + "journeys": [ + { + "width": 320, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + } + ], + "errors": [], + "unexpectedWrites": [], + "reads": [ + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + } + ], + "blockedExternal": [ + "https://fonts.googleapis.com", + "https://fonts.googleapis.com" + ], + "limits": [ + "Controlled API fixtures, not a live backend/provider or reminder delivery.", + "Date-level recorded deadline/follow-up display; no fabricated interview appointments or source timezone reconstruction.", + "No native-device, physical rendering, final security or release qualification." + ] +} \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/calendar/browser/PRE-UPCOMING-ORDER.json b/docs/ui-modernisation/evidence/calendar/browser/PRE-UPCOMING-ORDER.json new file mode 100644 index 00000000..5a6cf9df --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/browser/PRE-UPCOMING-ORDER.json @@ -0,0 +1,929 @@ +{ + "screens": [ + { + "width": 320, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "linked-application", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "agenda", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "month", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "linked-application", + "violations": [] + } + ], + "journeys": [ + { + "width": 320, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS" + } + ], + "errors": [], + "unexpectedWrites": [], + "reads": [ + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-12-01", + "to": "2026-12-31" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "from": "2026-11-01", + "to": "2026-11-30" + } + ], + "blockedExternal": [ + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com" + ], + "limits": [ + "Controlled API fixtures, not a live backend/provider or reminder delivery.", + "Date-level recorded deadline/follow-up display; no fabricated interview appointments or source timezone reconstruction.", + "No native-device, physical rendering, final security or release qualification." + ] +} \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-1440.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-1440.png new file mode 100644 index 00000000..f400f487 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-320.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-320.png new file mode 100644 index 00000000..e1bac655 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-390.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-390.png new file mode 100644 index 00000000..e888b8c8 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-768.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-768.png new file mode 100644 index 00000000..4cf75b1a Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-1440.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-1440.png new file mode 100644 index 00000000..ca96c0d3 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-320.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-320.png new file mode 100644 index 00000000..a2208753 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-390.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-390.png new file mode 100644 index 00000000..b3a6630c Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-768.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-768.png new file mode 100644 index 00000000..7c3ceba5 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-en-light-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-1440.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-1440.png new file mode 100644 index 00000000..dbb11241 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-320.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-320.png new file mode 100644 index 00000000..238e47ef Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-390.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-390.png new file mode 100644 index 00000000..aea1d3d6 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-768.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-768.png new file mode 100644 index 00000000..0b3f6385 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-1440.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-1440.png new file mode 100644 index 00000000..fe3a4d99 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-320.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-320.png new file mode 100644 index 00000000..ad417891 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-390.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-390.png new file mode 100644 index 00000000..15782d17 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-768.png b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-768.png new file mode 100644 index 00000000..6f3defc3 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/agenda-nb-light-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-1440.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-1440.png new file mode 100644 index 00000000..a102a2e6 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-320.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-320.png new file mode 100644 index 00000000..3ae6fe53 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-390.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-390.png new file mode 100644 index 00000000..064880bd Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-768.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-768.png new file mode 100644 index 00000000..c6b48f9e Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-1440.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-1440.png new file mode 100644 index 00000000..b09be2f7 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-320.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-320.png new file mode 100644 index 00000000..d02dc337 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-390.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-390.png new file mode 100644 index 00000000..fc5d90fb Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-768.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-768.png new file mode 100644 index 00000000..60da0fdc Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-en-light-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-1440.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-1440.png new file mode 100644 index 00000000..40ac3e5c Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-320.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-320.png new file mode 100644 index 00000000..dc0bf0da Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-390.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-390.png new file mode 100644 index 00000000..a7dcccb5 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-768.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-768.png new file mode 100644 index 00000000..e70c4ac7 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-1440.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-1440.png new file mode 100644 index 00000000..d0643b5b Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-320.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-320.png new file mode 100644 index 00000000..c57bcee2 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-390.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-390.png new file mode 100644 index 00000000..0c9b02fd Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-768.png b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-768.png new file mode 100644 index 00000000..632bdac6 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/linked-application-nb-light-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-1440.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-1440.png new file mode 100644 index 00000000..40a1f444 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-320.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-320.png new file mode 100644 index 00000000..d1e8c8cc Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-390.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-390.png new file mode 100644 index 00000000..cb061e36 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-768.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-768.png new file mode 100644 index 00000000..1f0b9bc9 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-light-1440.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-1440.png new file mode 100644 index 00000000..b73cefdf Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-light-320.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-320.png new file mode 100644 index 00000000..a958c3ed Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-light-390.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-390.png new file mode 100644 index 00000000..0954d48c Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-en-light-768.png b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-768.png new file mode 100644 index 00000000..a65474fc Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-en-light-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-1440.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-1440.png new file mode 100644 index 00000000..9e04f1c5 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-320.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-320.png new file mode 100644 index 00000000..2fd6a467 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-390.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-390.png new file mode 100644 index 00000000..9a207881 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-768.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-768.png new file mode 100644 index 00000000..050974a8 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-1440.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-1440.png new file mode 100644 index 00000000..e4aedac6 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-320.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-320.png new file mode 100644 index 00000000..8680b801 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-320.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-390.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-390.png new file mode 100644 index 00000000..015063f6 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-390.png differ diff --git a/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-768.png b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-768.png new file mode 100644 index 00000000..b70fdb82 Binary files /dev/null and b/docs/ui-modernisation/evidence/calendar/browser/month-nb-light-768.png differ diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-final.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-final.json new file mode 100644 index 00000000..bf95a385 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-final.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":4,"numPassedTests":23,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":4,"numTotalTests":23,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793713188793,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":164,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"agenda defaults to recorded dates and links exact application without a fabricated appointment time","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793713192701,"status":"passed","title":"agenda defaults to recorded dates and links exact application without a fabricated appointment time"},{"ancestorTitles":[],"duration":267,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month toggle persists only view preference and selecting a day keeps linked events available","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713192865,"status":"passed","title":"month toggle persists only view preference and selecting a day keeps linked events available"},{"ancestorTitles":[],"duration":47,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"locale changes translate controls without repeating the read or translating source text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713193132,"status":"passed","title":"locale changes translate controls without repeating the read or translating source text"},{"ancestorTitles":[],"duration":91,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed read offers bounded retry and never claims an empty successful calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713193179,"status":"passed","title":"failed read offers bounded retry and never claims an empty successful calendar"},{"ancestorTitles":[],"duration":31,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid and unknown-kind fixture data is not turned into guessed events","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713193270,"status":"passed","title":"invalid and unknown-kind fixture data is not turned into guessed events"},{"ancestorTitles":[],"duration":58,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month navigation requests only the selected calendar range","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713193301,"status":"passed","title":"month navigation requests only the selected calendar range"}],"endTime":1793713193361,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx","startTime":1793713189945,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":104,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713194148,"status":"passed","title":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim"},{"ancestorTitles":[],"duration":311,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback","invocations":1,"location":null,"numPassingAsserts":41,"retryReasons":[],"startAt":1793713194252,"status":"passed","title":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback"},{"ancestorTitles":[],"duration":177,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 3-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713194563,"status":"passed","title":"a held 3-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 7-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713194740,"status":"passed","title":"a held 7-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held null-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713194878,"status":"passed","title":"a held null-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":197,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793713195016,"status":"passed","title":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock"},{"ancestorTitles":[],"duration":82,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793713195213,"status":"passed","title":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH"},{"ancestorTitles":[],"duration":38,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793713195295,"status":"passed","title":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes"},{"ancestorTitles":[],"duration":54,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a generic read failure translates live without GET and explicit read Retry preserves its original request","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793713195333,"status":"passed","title":"a generic read failure translates live without GET and explicit read Retry preserves its original request"},{"ancestorTitles":[],"duration":82,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"when an action removes its row, focus stays in the named results region instead of a removed button","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793713195387,"status":"passed","title":"when an action removes its row, focus stays in the named results region instead of a removed button"},{"ancestorTitles":[],"duration":90,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713195469,"status":"passed","title":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held"}],"endTime":1793713195559,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\reminders-presentation.test.tsx","startTime":1793713193372,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"adds the selected application date to a writable connected calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713196339,"status":"passed","title":"adds the selected application date to a writable connected calendar"},{"ancestorTitles":[],"duration":18,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keeps portable calendar download available without a provider connection","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713196416,"status":"passed","title":"keeps portable calendar download available without a provider connection"},{"ancestorTitles":[],"duration":28,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"explains when an existing connection needs renewed calendar consent","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713196434,"status":"passed","title":"explains when an existing connection needs renewed calendar consent"}],"endTime":1793713196462,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\application-calendar.test.tsx","startTime":1793713195567,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":7,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"English and Bokmål expose the same UI message keys","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713196695,"status":"passed","title":"English and Bokmål expose the same UI message keys"},{"ancestorTitles":[],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål preserves every English substitution variable","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713196702,"status":"passed","title":"Bokmål preserves every English substitution variable"},{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"supported UI catalogues contain no blank messages","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713196707,"status":"passed","title":"supported UI catalogues contain no blank messages"}],"endTime":1793713196709,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\translation-catalogue.test.ts","startTime":1793713196469,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-green.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-green.json new file mode 100644 index 00000000..9e7158d3 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-green.json @@ -0,0 +1 @@ +{"numFailedTestSuites":1,"numFailedTests":1,"numPassedTestSuites":3,"numPassedTests":22,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":4,"numTotalTests":23,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793713133956,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":280,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"agenda defaults to recorded dates and links exact application without a fabricated appointment time","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793713140050,"status":"passed","title":"agenda defaults to recorded dates and links exact application without a fabricated appointment time"},{"ancestorTitles":[],"duration":558,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month toggle persists only view preference and selecting a day keeps linked events available","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713140331,"status":"passed","title":"month toggle persists only view preference and selecting a day keeps linked events available"},{"ancestorTitles":[],"duration":129,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"locale changes translate controls without repeating the read or translating source text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713140889,"status":"passed","title":"locale changes translate controls without repeating the read or translating source text"},{"ancestorTitles":[],"duration":193,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed read offers bounded retry and never claims an empty successful calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713141018,"status":"passed","title":"failed read offers bounded retry and never claims an empty successful calendar"},{"ancestorTitles":[],"duration":44,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid and unknown-kind fixture data is not turned into guessed events","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713141211,"status":"passed","title":"invalid and unknown-kind fixture data is not turned into guessed events"},{"ancestorTitles":[],"duration":64,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(jest.fn()).toHaveBeenLastCalledWith(...expected)\n\n- Expected\n+ Received\n\n \"/calendar/agenda\",\n Object {\n \"params\": Object {\n- \"from\": \"2026-12-01\",\n- \"to\": \"2026-12-31\",\n+ \"from\": \"2026-11-01\",\n+ \"to\": \"2026-11-30\",\n },\n },\n\nNumber of calls: 1","pass":false}}],"failureMessages":["Error: expect(jest.fn()).toHaveBeenLastCalledWith(...expected)\n\n- Expected\n+ Received\n\n \"/calendar/agenda\",\n Object {\n \"params\": Object {\n- \"from\": \"2026-12-01\",\n- \"to\": \"2026-12-31\",\n+ \"from\": \"2026-11-01\",\n+ \"to\": \"2026-11-30\",\n },\n },\n\nNumber of calls: 1\n at Object.toHaveBeenLastCalledWith (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx:68:15)"],"fullName":"month navigation requests only the selected calendar range","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793713141255,"status":"failed","title":"month navigation requests only the selected calendar range"}],"endTime":1793713141336,"message":" ● month navigation requests only the selected calendar range\n\n expect(jest.fn()).toHaveBeenLastCalledWith(...expected)\n\n - Expected\n + Received\n\n \"/calendar/agenda\",\n Object {\n \"params\": Object {\n - \"from\": \"2026-12-01\",\n - \"to\": \"2026-12-31\",\n + \"from\": \"2026-11-01\",\n + \"to\": \"2026-11-30\",\n },\n },\n\n Number of calls: 1\n\n 66 | const next = new Date(now.getFullYear(), now.getMonth() + 1, 1);\n 67 | const prefix = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}`;\n > 68 | expect(get).toHaveBeenLastCalledWith('/calendar/agenda', { params: { from: `${prefix}-01`, to: `${prefix}-${new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate()}` } });\n | ^\n 69 | });\n 70 |\n\n at Object.toHaveBeenLastCalledWith (src/calendar-agenda.test.tsx:68:15)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx","startTime":1793713135877,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":219,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713142823,"status":"passed","title":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim"},{"ancestorTitles":[],"duration":590,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback","invocations":1,"location":null,"numPassingAsserts":41,"retryReasons":[],"startAt":1793713143042,"status":"passed","title":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback"},{"ancestorTitles":[],"duration":249,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 3-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713143632,"status":"passed","title":"a held 3-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":199,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 7-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713143881,"status":"passed","title":"a held 7-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":154,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held null-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713144080,"status":"passed","title":"a held null-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":199,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793713144234,"status":"passed","title":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock"},{"ancestorTitles":[],"duration":143,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793713144433,"status":"passed","title":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH"},{"ancestorTitles":[],"duration":59,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793713144576,"status":"passed","title":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes"},{"ancestorTitles":[],"duration":64,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a generic read failure translates live without GET and explicit read Retry preserves its original request","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793713144635,"status":"passed","title":"a generic read failure translates live without GET and explicit read Retry preserves its original request"},{"ancestorTitles":[],"duration":78,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"when an action removes its row, focus stays in the named results region instead of a removed button","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793713144700,"status":"passed","title":"when an action removes its row, focus stays in the named results region instead of a removed button"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713144778,"status":"passed","title":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held"}],"endTime":1793713144855,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\reminders-presentation.test.tsx","startTime":1793713141356,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":70,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"adds the selected application date to a writable connected calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713145641,"status":"passed","title":"adds the selected application date to a writable connected calendar"},{"ancestorTitles":[],"duration":26,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keeps portable calendar download available without a provider connection","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713145711,"status":"passed","title":"keeps portable calendar download available without a provider connection"},{"ancestorTitles":[],"duration":32,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"explains when an existing connection needs renewed calendar consent","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713145738,"status":"passed","title":"explains when an existing connection needs renewed calendar consent"}],"endTime":1793713145771,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\application-calendar.test.tsx","startTime":1793713144864,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"English and Bokmål expose the same UI message keys","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713145990,"status":"passed","title":"English and Bokmål expose the same UI message keys"},{"ancestorTitles":[],"duration":5,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål preserves every English substitution variable","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713145998,"status":"passed","title":"Bokmål preserves every English substitution variable"},{"ancestorTitles":[],"duration":3,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"supported UI catalogues contain no blank messages","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713146003,"status":"passed","title":"supported UI catalogues contain no blank messages"}],"endTime":1793713146006,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\translation-catalogue.test.ts","startTime":1793713145778,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-qualified-final.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-qualified-final.json new file mode 100644 index 00000000..e366c2f7 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-qualified-final.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":4,"numPassedTests":25,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":4,"numTotalTests":25,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793713566236,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":166,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"agenda prioritises upcoming recorded dates while retaining earlier dates in the month","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713569991,"status":"passed","title":"agenda prioritises upcoming recorded dates while retaining earlier dates in the month"},{"ancestorTitles":[],"duration":58,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"agenda defaults to recorded dates and links exact application without a fabricated appointment time","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793713570157,"status":"passed","title":"agenda defaults to recorded dates and links exact application without a fabricated appointment time"},{"ancestorTitles":[],"duration":237,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month toggle persists only view preference and selecting a day keeps linked events available","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713570215,"status":"passed","title":"month toggle persists only view preference and selecting a day keeps linked events available"},{"ancestorTitles":[],"duration":58,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"locale changes translate controls without repeating the read or translating source text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713570452,"status":"passed","title":"locale changes translate controls without repeating the read or translating source text"},{"ancestorTitles":[],"duration":74,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed read offers bounded retry and never claims an empty successful calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713570510,"status":"passed","title":"failed read offers bounded retry and never claims an empty successful calendar"},{"ancestorTitles":[],"duration":32,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid and unknown-kind fixture data is not turned into guessed events","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713570584,"status":"passed","title":"invalid and unknown-kind fixture data is not turned into guessed events"},{"ancestorTitles":[],"duration":79,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month navigation requests only the selected calendar range","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713570616,"status":"passed","title":"month navigation requests only the selected calendar range"},{"ancestorTitles":[],"duration":105,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"view preference is scoped to the signed-in owner","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713570695,"status":"passed","title":"view preference is scoped to the signed-in owner"}],"endTime":1793713570801,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx","startTime":1793713567181,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":91,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713571511,"status":"passed","title":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim"},{"ancestorTitles":[],"duration":299,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback","invocations":1,"location":null,"numPassingAsserts":41,"retryReasons":[],"startAt":1793713571602,"status":"passed","title":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback"},{"ancestorTitles":[],"duration":165,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 3-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713571902,"status":"passed","title":"a held 3-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 7-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713572068,"status":"passed","title":"a held 7-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":140,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held null-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713572206,"status":"passed","title":"a held null-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":197,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793713572346,"status":"passed","title":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock"},{"ancestorTitles":[],"duration":111,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793713572543,"status":"passed","title":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH"},{"ancestorTitles":[],"duration":35,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793713572654,"status":"passed","title":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes"},{"ancestorTitles":[],"duration":59,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a generic read failure translates live without GET and explicit read Retry preserves its original request","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793713572689,"status":"passed","title":"a generic read failure translates live without GET and explicit read Retry preserves its original request"},{"ancestorTitles":[],"duration":63,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"when an action removes its row, focus stays in the named results region instead of a removed button","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793713572748,"status":"passed","title":"when an action removes its row, focus stays in the named results region instead of a removed button"},{"ancestorTitles":[],"duration":75,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713572811,"status":"passed","title":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held"}],"endTime":1793713572887,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\reminders-presentation.test.tsx","startTime":1793713570813,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":63,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"adds the selected application date to a writable connected calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713573590,"status":"passed","title":"adds the selected application date to a writable connected calendar"},{"ancestorTitles":[],"duration":27,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keeps portable calendar download available without a provider connection","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713573654,"status":"passed","title":"keeps portable calendar download available without a provider connection"},{"ancestorTitles":[],"duration":29,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"explains when an existing connection needs renewed calendar consent","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713573681,"status":"passed","title":"explains when an existing connection needs renewed calendar consent"}],"endTime":1793713573711,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\application-calendar.test.tsx","startTime":1793713572894,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":7,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"English and Bokmål expose the same UI message keys","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713573922,"status":"passed","title":"English and Bokmål expose the same UI message keys"},{"ancestorTitles":[],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål preserves every English substitution variable","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713573929,"status":"passed","title":"Bokmål preserves every English substitution variable"},{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"supported UI catalogues contain no blank messages","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713573933,"status":"passed","title":"supported UI catalogues contain no blank messages"}],"endTime":1793713573936,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\translation-catalogue.test.ts","startTime":1793713573719,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-qualified.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-qualified.json new file mode 100644 index 00000000..eb694d91 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-qualified.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":4,"numPassedTests":24,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":4,"numTotalTests":24,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793713333726,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":169,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"agenda defaults to recorded dates and links exact application without a fabricated appointment time","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793713337332,"status":"passed","title":"agenda defaults to recorded dates and links exact application without a fabricated appointment time"},{"ancestorTitles":[],"duration":319,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month toggle persists only view preference and selecting a day keeps linked events available","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713337502,"status":"passed","title":"month toggle persists only view preference and selecting a day keeps linked events available"},{"ancestorTitles":[],"duration":51,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"locale changes translate controls without repeating the read or translating source text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713337821,"status":"passed","title":"locale changes translate controls without repeating the read or translating source text"},{"ancestorTitles":[],"duration":119,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed read offers bounded retry and never claims an empty successful calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713337872,"status":"passed","title":"failed read offers bounded retry and never claims an empty successful calendar"},{"ancestorTitles":[],"duration":31,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid and unknown-kind fixture data is not turned into guessed events","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713337991,"status":"passed","title":"invalid and unknown-kind fixture data is not turned into guessed events"},{"ancestorTitles":[],"duration":72,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month navigation requests only the selected calendar range","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713338022,"status":"passed","title":"month navigation requests only the selected calendar range"},{"ancestorTitles":[],"duration":110,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"view preference is scoped to the signed-in owner","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713338095,"status":"passed","title":"view preference is scoped to the signed-in owner"}],"endTime":1793713338206,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx","startTime":1793713334650,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":97,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713338970,"status":"passed","title":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim"},{"ancestorTitles":[],"duration":325,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback","invocations":1,"location":null,"numPassingAsserts":41,"retryReasons":[],"startAt":1793713339068,"status":"passed","title":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback"},{"ancestorTitles":[],"duration":192,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 3-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713339393,"status":"passed","title":"a held 3-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":168,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 7-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713339585,"status":"passed","title":"a held 7-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":152,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held null-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793713339754,"status":"passed","title":"a held null-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":211,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793713339906,"status":"passed","title":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock"},{"ancestorTitles":[],"duration":97,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793713340117,"status":"passed","title":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH"},{"ancestorTitles":[],"duration":60,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793713340214,"status":"passed","title":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes"},{"ancestorTitles":[],"duration":79,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a generic read failure translates live without GET and explicit read Retry preserves its original request","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793713340274,"status":"passed","title":"a generic read failure translates live without GET and explicit read Retry preserves its original request"},{"ancestorTitles":[],"duration":79,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"when an action removes its row, focus stays in the named results region instead of a removed button","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793713340353,"status":"passed","title":"when an action removes its row, focus stays in the named results region instead of a removed button"},{"ancestorTitles":[],"duration":91,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793713340432,"status":"passed","title":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held"}],"endTime":1793713340524,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\reminders-presentation.test.tsx","startTime":1793713338217,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":86,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"adds the selected application date to a writable connected calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713341276,"status":"passed","title":"adds the selected application date to a writable connected calendar"},{"ancestorTitles":[],"duration":26,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keeps portable calendar download available without a provider connection","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713341363,"status":"passed","title":"keeps portable calendar download available without a provider connection"},{"ancestorTitles":[],"duration":34,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"explains when an existing connection needs renewed calendar consent","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713341389,"status":"passed","title":"explains when an existing connection needs renewed calendar consent"}],"endTime":1793713341423,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\application-calendar.test.tsx","startTime":1793713340532,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"English and Bokmål expose the same UI message keys","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713341649,"status":"passed","title":"English and Bokmål expose the same UI message keys"},{"ancestorTitles":[],"duration":12,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål preserves every English substitution variable","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713341658,"status":"passed","title":"Bokmål preserves every English substitution variable"},{"ancestorTitles":[],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"supported UI catalogues contain no blank messages","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713341670,"status":"passed","title":"supported UI catalogues contain no blank messages"}],"endTime":1793713341674,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\translation-catalogue.test.ts","startTime":1793713341431,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-red.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-red.json new file mode 100644 index 00000000..c749d2ee --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-red.json @@ -0,0 +1 @@ +{"numFailedTestSuites":1,"numFailedTests":0,"numPassedTestSuites":0,"numPassedTests":0,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":1,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":0,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793713050322,"success":false,"testResults":[{"assertionResults":[],"coverage":{},"endTime":1793713052868,"message":" ● Test suite failed to run\n\n Cannot find module './components/CalendarAgenda' from 'src/calendar-agenda.test.tsx'\n\n 4 | import { MemoryRouter } from 'react-router-dom';\n 5 | import { api } from './api';\n > 6 | import CalendarAgenda from './components/CalendarAgenda';\n | ^\n 7 | import { I18nProvider } from './i18n/I18nProvider';\n 8 | import { translations } from './i18n/translations';\n 9 |\n\n at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/index.js:895:11)\n at Object.require (src/calendar-agenda.test.tsx:6:1)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx","startTime":1793713052868,"status":"failed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-reviewed.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-reviewed.json new file mode 100644 index 00000000..bcf2d3a8 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-calendar-ui-reviewed.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":7,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":7,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793713482046,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":178,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"agenda defaults to recorded dates and links exact application without a fabricated appointment time","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793713485898,"status":"passed","title":"agenda defaults to recorded dates and links exact application without a fabricated appointment time"},{"ancestorTitles":[],"duration":290,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month toggle persists only view preference and selecting a day keeps linked events available","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713486076,"status":"passed","title":"month toggle persists only view preference and selecting a day keeps linked events available"},{"ancestorTitles":[],"duration":59,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"locale changes translate controls without repeating the read or translating source text","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713486366,"status":"passed","title":"locale changes translate controls without repeating the read or translating source text"},{"ancestorTitles":[],"duration":105,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed read offers bounded retry and never claims an empty successful calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713486425,"status":"passed","title":"failed read offers bounded retry and never claims an empty successful calendar"},{"ancestorTitles":[],"duration":33,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid and unknown-kind fixture data is not turned into guessed events","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793713486530,"status":"passed","title":"invalid and unknown-kind fixture data is not turned into guessed events"},{"ancestorTitles":[],"duration":84,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"month navigation requests only the selected calendar range","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793713486563,"status":"passed","title":"month navigation requests only the selected calendar range"},{"ancestorTitles":[],"duration":130,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"view preference is scoped to the signed-in owner","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793713486647,"status":"passed","title":"view preference is scoped to the signed-in owner"}],"endTime":1793713486778,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\calendar-agenda.test.tsx","startTime":1793713483064,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/calendar/frontend/a3-workspace-baseline.json b/docs/ui-modernisation/evidence/calendar/frontend/a3-workspace-baseline.json new file mode 100644 index 00000000..8da92898 --- /dev/null +++ b/docs/ui-modernisation/evidence/calendar/frontend/a3-workspace-baseline.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":4,"numPassedTests":70,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":4,"numTotalTests":70,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793712837569,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["CorrespondenceInboxPage"],"duration":463,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage [P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793712841520,"status":"passed","title":"[P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":324,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage renders correspondence inbox items and reloads with filters","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712841983,"status":"passed","title":"renders correspondence inbox items and reloads with filters"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":107,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage keeps saved correspondence visible when provider status is unavailable","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712842307,"status":"passed","title":"keeps saved correspondence visible when provider status is unavailable"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":95,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage opens provider-backed plain-text message detail in the hub","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793712842414,"status":"passed","title":"opens provider-backed plain-text message detail in the hub"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":185,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage navigates beyond the first correspondence page","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793712842509,"status":"passed","title":"navigates beyond the first correspondence page"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":233,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage unlinks a Gmail thread only after confirmation and returns it to review","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712842694,"status":"passed","title":"unlinks a Gmail thread only after confirmation and returns it to review"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":200,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage falls back to the saved copy when provider detail is unavailable","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712842927,"status":"passed","title":"falls back to the saved copy when provider detail is unavailable"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":940,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage keeps replies editable and requires final send confirmation","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793712843127,"status":"passed","title":"keeps replies editable and requires final send confirmation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":407,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage saves an incomplete reply with its server delivery identity","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712844067,"status":"passed","title":"saves an incomplete reply with its server delivery identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":345,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage resumes a saved draft and surfaces revision conflicts without overwriting it","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793712844474,"status":"passed","title":"resumes a saved draft and surfaces revision conflicts without overwriting it"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":587,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage persists a new delivery identity only after a definitive failed attempt","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712844819,"status":"passed","title":"persists a new delivery identity only after a definitive failed attempt"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":476,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage starts a new-message draft only from an owned job and send-capable provider choice","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793712845406,"status":"passed","title":"starts a new-message draft only from an owned job and send-capable provider choice"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":676,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage searches the complete server-side job choice set while composing","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712845882,"status":"passed","title":"searches the complete server-side job choice set while composing"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":673,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage does not offer retry when delivery is uncertain","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712846558,"status":"passed","title":"does not offer retry when delivery is uncertain"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":243,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage uses one hub for linked messages and recruitment suggestions","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712847231,"status":"passed","title":"uses one hub for linked messages and recruitment suggestions"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage localizes inbox controls while preserving correspondence content","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712847474,"status":"passed","title":"localizes inbox controls while preserving correspondence content"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":134,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage legacy Gmail review route redirects into the consolidated hub filter","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793712847612,"status":"passed","title":"legacy Gmail review route redirects into the consolidated hub filter"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":219,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage failed inbox reads retain a current-language error and recover with one explicit GET-only Retry","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793712847746,"status":"passed","title":"failed inbox reads retain a current-language error and recover with one explicit GET-only Retry"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":316,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a late old page cannot overwrite the latest query and page-one inbox snapshot","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793712847965,"status":"passed","title":"a late old page cannot overwrite the latest query and page-one inbox snapshot"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":276,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage message Retry replays the captured owned detail read without hiding it and localizes saved-copy fallback","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793712848281,"status":"passed","title":"message Retry replays the captured owned detail read without hiding it and localizes saved-copy fallback"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":545,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage hidden or inactive details never resume a saved-copy fallback and hub return preserves inbox context","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793712848557,"status":"passed","title":"hidden or inactive details never resume a saved-copy fallback and hub return preserves inbox context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":228,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage provider status Retry is isolated from saved messages and drafts and live locale changes cause no reads","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712849102,"status":"passed","title":"provider status Retry is isolated from saved messages and drafts and live locale changes cause no reads"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":501,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage confirmed unlink owns pending actions and a failed successful-write refresh recovers with GET only","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793712849330,"status":"passed","title":"confirmed unlink owns pending actions and a failed successful-write refresh recovers with GET only"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":607,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage literal all search preserves source and hash and hides another query snapshot while retaining same-query refresh data","invocations":1,"location":null,"numPassingAsserts":29,"retryReasons":[],"startAt":1793712849831,"status":"passed","title":"literal all search preserves source and hash and hides another query snapshot while retaining same-query refresh data"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":128,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage typing an inbox search keeps one natural Back step to the prior screen and preserves filter context","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793712850438,"status":"passed","title":"typing an inbox search keeps one natural Back step to the prior screen and preserves filter context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":563,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage held draft Save captures one write and locks edits, Send, Discard and every draft replacement entry","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793712850566,"status":"passed","title":"held draft Save captures one write and locks edits, Send, Discard and every draft replacement entry"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1036,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage double Review creates one confirmation, Cancel sends nothing and explicit double Send owns one captured attempt","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793712851129,"status":"passed","title":"double Review creates one confirmation, Cancel sends nothing and explicit double Send owns one captured attempt"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":3758,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage recipient, subject and body edits cannot erase any recorded delivery state or rotate its attempt identity","invocations":1,"location":null,"numPassingAsserts":70,"retryReasons":[],"startAt":1793712852165,"status":"passed","title":"recipient, subject and body edits cannot erase any recorded delivery state or rotate its attempt identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1460,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage generic 409 cannot prepare a new attempt while an authoritative failed result requires one explicit server rotation","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793712855923,"status":"passed","title":"generic 409 cannot prepare a new attempt while an authoritative failed result requires one explicit server rotation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":2042,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage recipient-only and subject-only unsaved changes require replacement and discard confirmation whose Cancel retains the draft","invocations":1,"location":null,"numPassingAsserts":22,"retryReasons":[],"startAt":1793712857383,"status":"passed","title":"recipient-only and subject-only unsaved changes require replacement and discard confirmation whose Cancel retains the draft"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":740,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage saved-draft initial read and post-save refresh failures recover with GET-only Retry without replaying the successful Save","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793712859425,"status":"passed","title":"saved-draft initial read and post-save refresh failures recover with GET-only Retry without replaying the successful Save"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":434,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a late Save after inbox unmount cannot refresh drafts or attach its result to another page","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793712860165,"status":"passed","title":"a late Save after inbox unmount cannot refresh drafts or attach its result to another page"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":637,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a held managed-draft 409 retains recipient, subject, body and revision with current-language original Save recovery","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712860599,"status":"passed","title":"a held managed-draft 409 retains recipient, subject, body and revision with current-language original Save recovery"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1536,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage server attempt rotation preserves three unsaved fields and their dirty state while the next send uses the new identity","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793712861236,"status":"passed","title":"server attempt rotation preserves three unsaved fields and their dirty state while the next send uses the new identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":596,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a committed Save starts a fresh draft-list read despite an older pending GET and rejects the late old snapshot","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712862772,"status":"passed","title":"a committed Save starts a fresh draft-list read despite an older pending GET and rejects the late old snapshot"}],"endTime":1793712863369,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx","startTime":1793712838511,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":110,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793712864139,"status":"passed","title":"[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim"},{"ancestorTitles":[],"duration":293,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback","invocations":1,"location":null,"numPassingAsserts":41,"retryReasons":[],"startAt":1793712864249,"status":"passed","title":"preserves all four server-signal groups, raw reasons, contextual destinations and legacy fallback"},{"ancestorTitles":[],"duration":166,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 3-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793712864542,"status":"passed","title":"a held 3-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held 7-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793712864708,"status":"passed","title":"a held 7-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":139,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held null-day action submits one snapshot and locks every row action through refresh","invocations":1,"location":null,"numPassingAsserts":26,"retryReasons":[],"startAt":1793712864846,"status":"passed","title":"a held null-day action submits one snapshot and locks every row action through refresh"},{"ancestorTitles":[],"duration":196,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793712864985,"status":"passed","title":"a failed PATCH retains the row and durable localized error; Retry reuses its captured date while a fresh action uses the new clock"},{"ancestorTitles":[],"duration":81,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712865181,"status":"passed","title":"after PATCH 204 and failed refresh, Retry reads only reminders and never repeats the event-producing PATCH"},{"ancestorTitles":[],"duration":35,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793712865263,"status":"passed","title":"live EN/Bokmål presentation uses explicit date locale and canonical statuses while preserving raw reasons without GET or writes"},{"ancestorTitles":[],"duration":57,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a generic read failure translates live without GET and explicit read Retry preserves its original request","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793712865298,"status":"passed","title":"a generic read failure translates live without GET and explicit read Retry preserves its original request"},{"ancestorTitles":[],"duration":79,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"when an action removes its row, focus stays in the named results region instead of a removed button","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793712865355,"status":"passed","title":"when an action removes its row, focus stays in the named results region instead of a removed button"},{"ancestorTitles":[],"duration":75,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793712865434,"status":"passed","title":"a removed reminder row does not steal focus deliberately moved outside while its PATCH is held"}],"endTime":1793712865510,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\reminders-presentation.test.tsx","startTime":1793712863383,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":87,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"adds the selected application date to a writable connected calendar","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793712866215,"status":"passed","title":"adds the selected application date to a writable connected calendar"},{"ancestorTitles":[],"duration":26,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keeps portable calendar download available without a provider connection","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793712866302,"status":"passed","title":"keeps portable calendar download available without a provider connection"},{"ancestorTitles":[],"duration":32,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"explains when an existing connection needs renewed calendar consent","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793712866329,"status":"passed","title":"explains when an existing connection needs renewed calendar consent"}],"endTime":1793712866361,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\application-calendar.test.tsx","startTime":1793712865517,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["en unknown attachment purposes"],"duration":155,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for constructor","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712867187,"status":"passed","title":"uses the localized Other label for constructor"},{"ancestorTitles":["en unknown attachment purposes"],"duration":89,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for toString","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712867342,"status":"passed","title":"uses the localized Other label for toString"},{"ancestorTitles":["en unknown attachment purposes"],"duration":92,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for __proto__","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712867431,"status":"passed","title":"uses the localized Other label for __proto__"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":97,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for constructor","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712867523,"status":"passed","title":"uses the localized Other label for constructor"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":90,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for toString","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712867620,"status":"passed","title":"uses the localized Other label for toString"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":92,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for __proto__","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793712867710,"status":"passed","title":"uses the localized Other label for __proto__"},{"ancestorTitles":[],"duration":116,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] validates URL filters and reads one metadata page with literal Nordic search","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793712867802,"status":"passed","title":"[P7-07 documents] validates URL filters and reads one metadata page with literal Nordic search"},{"ancestorTitles":[],"duration":303,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] server pagination and filter history preserve unknown query, hash and router state","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793712867918,"status":"passed","title":"[P7-07 documents] server pagination and filter history preserve unknown query, hash and router state"},{"ancestorTitles":[],"duration":94,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] literal search all remains free text and preserves unrelated navigation context","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793712868221,"status":"passed","title":"[P7-07 documents] literal search all remains free text and preserves unrelated navigation context"},{"ancestorTitles":[],"duration":357,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a batched sort selection and immediate search preserve both filters and navigation context","invocations":1,"location":null,"numPassingAsserts":23,"retryReasons":[],"startAt":1793712868315,"status":"passed","title":"[P7-07 documents] a batched sort selection and immediate search preserve both filters and navigation context"},{"ancestorTitles":[],"duration":322,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] incompatible pending and failed queries hide old actions and late results cannot replace current metadata","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793712868672,"status":"passed","title":"[P7-07 documents] incompatible pending and failed queries hide old actions and late results cannot replace current metadata"},{"ancestorTitles":[],"duration":88,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] Retry is one guarded GET and a completion after leaving cannot publish or start follow-up work","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793712868994,"status":"passed","title":"[P7-07 documents] Retry is one guarded GET and a completion after leaving cannot publish or start follow-up work"},{"ancestorTitles":[],"duration":141,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a true empty index, filtered no-match and request failure remain distinct","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793712869082,"status":"passed","title":"[P7-07 documents] a true empty index, filtered no-match and request failure remain distinct"},{"ancestorTitles":[],"duration":90,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] English and Bokmål presentation changes preserve query context without a metadata refetch","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712869223,"status":"passed","title":"[P7-07 documents] English and Bokmål presentation changes preserve query context without a metadata refetch"},{"ancestorTitles":[],"duration":147,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] an out-of-range page offers explicit first-page recovery without claiming an empty index","invocations":1,"location":null,"numPassingAsserts":17,"retryReasons":[],"startAt":1793712869313,"status":"passed","title":"[P7-07 documents] an out-of-range page offers explicit first-page recovery without claiming an empty index"},{"ancestorTitles":[],"duration":176,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] seven resource kinds retain exact source actions, frozen provenance and honest date labels","invocations":1,"location":null,"numPassingAsserts":24,"retryReasons":[],"startAt":1793712869460,"status":"passed","title":"[P7-07 documents] seven resource kinds retain exact source actions, frozen provenance and honest date labels"},{"ancestorTitles":[],"duration":159,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] attachment download is explicit, API-relative and single-flight with original MIME, filename and URL disposal","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793712869636,"status":"passed","title":"[P7-07 documents] attachment download is explicit, API-relative and single-flight with original MIME, filename and URL disposal"},{"ancestorTitles":[],"duration":155,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a completed download preserves deliberate Search focus instead of restoring the old button","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793712869795,"status":"passed","title":"[P7-07 documents] a completed download preserves deliberate Search focus instead of restoring the old button"},{"ancestorTitles":[],"duration":201,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] download errors retry only that file and late download completion after leaving creates no URL","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793712869950,"status":"passed","title":"[P7-07 documents] download errors retry only that file and late download completion after leaving creates no URL"},{"ancestorTitles":[],"duration":57,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a published historical CV retains its percent-encoded Nordic public slug without fetching its body","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793712870151,"status":"passed","title":"[P7-07 documents] a published historical CV retains its percent-encoded Nordic public slug without fetching its body"},{"ancestorTitles":[],"duration":46,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] malformed external and mismatched resource paths never become source, public or download actions","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793712870208,"status":"passed","title":"[P7-07 documents] malformed external and mismatched resource paths never become source, public or download actions"}],"endTime":1793712870255,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\documents-page.test.tsx","startTime":1793712866368,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/job-tracker-ui/scripts/verify-a3-calendar.mjs b/job-tracker-ui/scripts/verify-a3-calendar.mjs new file mode 100644 index 00000000..b497dd4d --- /dev/null +++ b/job-tracker-ui/scripts/verify-a3-calendar.mjs @@ -0,0 +1,65 @@ +// Built React, recorded-date fixtures only; every API/external request is intercepted. +import { chromium, expect } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; +import ts from 'typescript'; +import fs from 'node:fs'; +import path from 'node:path'; +import assert from 'node:assert/strict'; +const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText; +const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`); +const out = path.resolve(import.meta.dirname, '../../docs/ui-modernisation/evidence/calendar/browser'); fs.mkdirSync(out, { recursive: true }); +const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], reads: [], blockedExternal: [] }; +const now = new Date(), month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; +const browser = await chromium.launch(); +try { + for (const width of (process.env.A3_PILOT ? [320] : [320, 390, 768, 1440])) for (const language of (process.env.A3_PILOT ? ['en'] : ['en', 'nb'])) for (const mode of (process.env.A3_PILOT ? ['light'] : ['light', 'dark'])) { + console.log(`Checking ${width}/${language}/${mode}`); + const t = (key, params = {}) => translations[language][key].replace(/\{(\w+)\}/g, (_, name) => String(params[name] ?? `{${name}}`)); + const context = await browser.newContext({ viewport: { width, height: 1000 }, reducedMotion: 'reduce' }); const page = await context.newPage(); + await page.addInitScript(({ language, mode }) => { localStorage.setItem('uiLanguage', language); localStorage.setItem('jobtracker.themeMode', mode); }, { language, mode }); + page.on('pageerror', error => report.errors.push(error.message)); + const events = ['Primary teacher', 'Community nurse', 'Electrical engineer'].map((jobTitle, index) => ({ jobApplicationId: 42 + index, jobTitle, companyName: 'Synthetic employer', kind: index === 2 ? 'follow-up' : 'deadline', date: `${month}-${index === 2 ? '20' : '15'}`, recordedAt: `${month}-${index === 2 ? '20' : '15'}T00:00:00` })); + await page.route('**/*', route => { + const request = route.request(), url = new URL(request.url()), p = url.pathname; + if (!['localhost', '127.0.0.1'].includes(url.hostname)) { report.blockedExternal.push(url.origin); return route.abort(); } + if (!p.startsWith('/api/')) return route.continue(); + if (request.method() !== 'GET') { report.unexpectedWrites.push(p); return route.abort(); } + const answer = data => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(data) }); + if (p === '/api/calendar/agenda') { + const from = url.searchParams.get('from'), to = url.searchParams.get('to'); assert.match(from, /^\d{4}-\d{2}-01$/); assert.ok(Date.parse(to) - Date.parse(from) <= 31 * 86400000); + report.reads.push({ width, language, mode, from, to }); return answer(events.filter(item => item.date >= from && item.date <= to)); + } + const fixtures = { + '/api/auth/config': { requireAuth: true }, '/api/auth/me': { id: 'a3-calendar-owner', sessionContext: 'a3-calendar-session', provider: 'local', firstName: 'Alex', displayName: 'Alex Sample', roles: [], uiLanguage: language === 'nb' ? 'nb-NO' : 'en', aiEnabled: false }, + '/api/companies': [], '/api/notifications/unread-count': { count: 0 }, '/api/jobapplications/reminders': [], + '/api/jobapplications/42/workspace': { id: 42, revision: 7, jobTitle: 'Primary teacher', company: 'Synthetic employer', status: 'Saved', location: 'Oslo', deadline: events[0].recordedAt, tags: [], hasJobDescription: true, description: 'Recorded source advertisement.', cv: { variantId: null }, recentActivity: [], nextStep: null, checklistProgress: { total: 0, completed: 0, dismissed: 0, percent: 0 } }, '/api/jobapplications/42/status-suggestion': null, + }; + return route.fulfill({ status: Object.hasOwn(fixtures, p) ? 200 : 404, contentType: 'application/json', body: JSON.stringify(fixtures[p] ?? {}) }); + }); + const scan = async state => { + assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false, state); + const axe = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze(); report.screens.push({ width, language, mode, state, violations: axe.violations }); assert.deepEqual(axe.violations, []); + await page.evaluate(() => window.scrollTo(0, 0)); await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: true }); + }; + await page.goto('http://localhost:4338/reminders'); + const calendar = page.getByRole('region', { name: t('calendarAgendaTitle'), exact: true }); await calendar.getByRole('link', { name: /Electrical engineer/ }).waitFor(); + await expect(page.getByRole('button', { name: t('calendarAgenda'), exact: true })).toHaveAttribute('aria-pressed', 'true'); + assert.equal(await calendar.getByRole('link').count(), 3); assert.equal(await calendar.locator('time').first().getAttribute('datetime'), events[0].recordedAt); await scan('agenda'); + await page.getByRole('button', { name: t('calendarMonth'), exact: true }).click(); + const table = page.getByRole('table', { name: t('calendarMonth'), exact: true }); await table.waitFor(); + const date = new Intl.DateTimeFormat(language === 'nb' ? 'nb-NO' : 'en-GB', { dateStyle: 'medium', timeZone: 'UTC' }).format(new Date(`${month}-15T12:00:00Z`)); + await table.getByRole('button', { name: t('calendarDayEvents', { date, count: 2 }), exact: true }).press('Enter'); + await expect(calendar.getByRole('link')).toHaveCount(2); await scan('month'); + await page.getByRole('button', { name: t('calendarNextMonth'), exact: true }).click(); await page.getByText(t('calendarAgendaEmpty'), { exact: true }).waitFor(); + await page.getByRole('button', { name: t('calendarPreviousMonth'), exact: true }).click(); await expect(calendar).toHaveAttribute('aria-busy', 'false'); + await page.reload(); await page.getByRole('table', { name: t('calendarMonth'), exact: true }).waitFor(); + await page.getByRole('button', { name: t('calendarAgenda'), exact: true }).click(); + await calendar.getByRole('link', { name: /Primary teacher/ }).press('Enter'); await page.waitForURL(/\/jobs\/42/); + await page.getByRole('complementary').last().waitFor(); + await expect(page.getByRole('heading', { name: 'Primary teacher', exact: true })).toBeVisible(); + assert.equal((await page.locator('main').innerText()).includes('Invalid Date'), false); await scan('linked-application'); + report.journeys.push({ width, language, mode, agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline: 'PASS' }); await context.close(); + } + assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com')); + console.log(`PASS:${report.journeys.length} calendar journeys,${report.screens.length} axe scans/captures,zero writes.`); +} finally { fs.writeFileSync(path.join(out, process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['Controlled API fixtures, not a live backend/provider or reminder delivery.', 'Date-level recorded deadline/follow-up display; no fabricated interview appointments or source timezone reconstruction.', 'No native-device, physical rendering, final security or release qualification.'] }, null, 2)); await browser.close(); } diff --git a/job-tracker-ui/src/App.tsx b/job-tracker-ui/src/App.tsx index 2baa4dd1..f69169c9 100644 --- a/job-tracker-ui/src/App.tsx +++ b/job-tracker-ui/src/App.tsx @@ -52,7 +52,7 @@ const AnalyticsPage = lazy(() => import("./views/AnalyticsPage")); const JobDiscoveryPage = lazy(() => import("./views/JobDiscoveryPage")); const CompaniesTable = lazy(() => import("./components/CompaniesTable")); const SettingsView = lazy(() => import("./components/SettingsView")); -const RemindersView = lazy(() => import("./components/RemindersView")); +const CalendarPage = lazy(() => import("./views/CalendarPage")); const QuickCommandDialog = lazy(() => import("./components/QuickCommandDialog")); const ProfilePage = lazy(() => import("./views/ProfilePage")); const CareerWorkspacePage = lazy(() => import("./views/CareerWorkspacePage")); @@ -434,7 +434,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/job-tracker-ui/src/calendar-agenda.test.tsx b/job-tracker-ui/src/calendar-agenda.test.tsx new file mode 100644 index 00000000..ae6b80e0 --- /dev/null +++ b/job-tracker-ui/src/calendar-agenda.test.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { act, fireEvent, render, screen, within } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { api } from './api'; +import CalendarAgenda from './components/CalendarAgenda'; +import { I18nProvider } from './i18n/I18nProvider'; +import { translations } from './i18n/translations'; +import { setAuthUserKey } from './auth'; + +jest.mock('./api', () => ({ api: { get: jest.fn() }, getApiErrorMessage: (_: unknown, fallback: string) => fallback })); +const get = api.get as jest.Mock; +const now = new Date(); +const month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; +const row = { jobApplicationId: 42, jobTitle: 'Recorded teacher role', companyName: 'School', kind: 'deadline', date: `${month}-15`, recordedAt: `${month}-15T23:30:00` }; +const mount = () => render(); +beforeEach(() => { localStorage.clear(); document.documentElement.lang = 'en'; get.mockReset(); get.mockResolvedValue({ data: [row] }); }); +afterEach(() => jest.restoreAllMocks()); + +test('agenda prioritises upcoming recorded dates while retaining earlier dates in the month', async () => { + jest.spyOn(Date, 'now').mockReturnValue(new Date(now.getFullYear(), now.getMonth(), 16, 12).getTime()); + get.mockResolvedValue({ data: [row, { ...row, jobApplicationId: 43, jobTitle: 'Upcoming nursing role', date: `${month}-20`, recordedAt: `${month}-20T00:00:00` }] }); + mount(); await screen.findByRole('link', { name: /Upcoming nursing role/ }); + expect(screen.getAllByRole('link').map(link => link.getAttribute('href'))).toEqual(['/jobs/43', '/jobs/42']); +}); + +test('agenda defaults to recorded dates and links exact application without a fabricated appointment time', async () => { + mount(); + const link = await screen.findByRole('link', { name: /Recorded teacher role/ }); + expect(link).toHaveAttribute('href', '/jobs/42'); + expect(screen.getByText(translations.en.calendarDateScope)).toBeVisible(); + expect(document.querySelector('time')).toHaveAttribute('datetime', row.recordedAt); + expect(screen.queryByText('23:30')).not.toBeInTheDocument(); + expect(get).toHaveBeenCalledWith('/calendar/agenda', { params: { from: `${month}-01`, to: `${month}-${new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()}` } }); +}); + +test('month toggle persists only view preference and selecting a day keeps linked events available', async () => { + const first = mount(); await screen.findByRole('link', { name: /Recorded teacher role/ }); + fireEvent.click(screen.getByRole('button', { name: translations.en.calendarMonth })); + const table = screen.getByRole('table', { name: translations.en.calendarMonth }); + fireEvent.click(within(table).getByRole('button', { name: /15.*1/ })); + expect(screen.getByRole('link', { name: /Recorded teacher role/ })).toBeVisible(); + expect(get).toHaveBeenCalledTimes(1); + first.unmount(); mount(); + expect(await screen.findByRole('table', { name: translations.en.calendarMonth })).toBeVisible(); +}); + +test('locale changes translate controls without repeating the read or translating source text', async () => { + mount(); await screen.findByRole('link', { name: /Recorded teacher role/ }); + act(() => window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: 'nb' }))); + expect(screen.getByRole('button', { name: translations.nb.calendarMonth })).toBeVisible(); + expect(screen.getByRole('link', { name: /Recorded teacher role/ })).toBeVisible(); + expect(get).toHaveBeenCalledTimes(1); +}); + +test('failed read offers bounded retry and never claims an empty successful calendar', async () => { + get.mockRejectedValueOnce(new Error('offline')); + mount(); await screen.findByRole('alert'); + expect(screen.queryByText(translations.en.calendarAgendaEmpty)).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: translations.en.retry })); + expect(await screen.findByRole('link', { name: /Recorded teacher role/ })).toBeVisible(); + expect(get).toHaveBeenCalledTimes(2); +}); + +test('invalid and unknown-kind fixture data is not turned into guessed events', async () => { + get.mockResolvedValue({ data: [{ ...row, date: `${month}-99` }, { ...row, kind: 'interview' }] }); + mount(); expect(await screen.findByRole('alert')).toBeVisible(); + expect(screen.queryByText(translations.en.calendarAgendaEmpty)).not.toBeInTheDocument(); + expect(screen.queryByRole('link', { name: /Recorded teacher role/ })).not.toBeInTheDocument(); +}); + +test('month navigation requests only the selected calendar range', async () => { + mount(); await screen.findByRole('link', { name: /Recorded teacher role/ }); + get.mockResolvedValue({ data: [] }); + fireEvent.click(screen.getByRole('button', { name: translations.en.calendarNextMonth })); + const next = new Date(now.getFullYear(), now.getMonth() + 1, 1); + const prefix = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}`; + expect(get).toHaveBeenLastCalledWith('/calendar/agenda', { params: { from: `${prefix}-01`, to: `${prefix}-${new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate()}` } }); + expect(await screen.findByText(translations.en.calendarAgendaEmpty)).toBeVisible(); +}); + +test('view preference is scoped to the signed-in owner', async () => { + setAuthUserKey('calendar-owner-one', false); + const first = mount(); await screen.findByRole('link', { name: /Recorded teacher role/ }); + fireEvent.click(screen.getByRole('button', { name: translations.en.calendarMonth })); first.unmount(); + setAuthUserKey('calendar-owner-two', false); mount(); + await screen.findByRole('link', { name: /Recorded teacher role/ }); + expect(screen.queryByRole('table', { name: translations.en.calendarMonth })).not.toBeInTheDocument(); +}); diff --git a/job-tracker-ui/src/components/CalendarAgenda.tsx b/job-tracker-ui/src/components/CalendarAgenda.tsx new file mode 100644 index 00000000..600bbb52 --- /dev/null +++ b/job-tracker-ui/src/components/CalendarAgenda.tsx @@ -0,0 +1,79 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import { Box, Button, Chip, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, TextField, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; +import { api } from '../api'; +import { getUserScopedStorageKey } from '../auth'; +import { useI18n } from '../i18n/I18nProvider'; +import { useViewResource } from '../hooks/useViewResource'; +import { PageHeader } from '../layout/PageLayout'; +import ViewStateNotice from './ViewStateNotice'; + +type CalendarItem = { jobApplicationId: number; jobTitle: string; companyName: string | null; kind: 'deadline' | 'follow-up'; date: string; recordedAt: string }; +const monthValue = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; +function calendarDate(value: string) { + if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null; + const date = new Date(`${value}T12:00:00Z`); + return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value ? date : null; +} + +export default function CalendarAgenda() { + const { language, t } = useI18n(); + const preferenceKey = getUserScopedStorageKey('jobtracker.calendarView'); + const [view, setView] = useState<'agenda' | 'month'>(() => { try { return localStorage.getItem(preferenceKey) === 'month' ? 'month' : 'agenda'; } catch { return 'agenda'; } }); + const [month, setMonth] = useState(() => monthValue(new Date())); + const [selectedDate, setSelectedDate] = useState(null); + const now = new Date(Date.now()); + const today = `${monthValue(now)}-${String(now.getDate()).padStart(2, '0')}`; + const [year, monthNumber] = month.split('-').map(Number); + const days = new Date(year, monthNumber, 0).getDate(); + const from = `${month}-01`, to = `${month}-${days}`; + const load = useCallback(async () => { + const response = await api.get('/calendar/agenda', { params: { from, to } }); + if (!Array.isArray(response.data) || response.data.some(item => !Number.isSafeInteger(item.jobApplicationId) || item.jobApplicationId <= 0 || typeof item.jobTitle !== 'string' || !['deadline', 'follow-up'].includes(item.kind) || typeof item.recordedAt !== 'string' || !calendarDate(item.date) || item.date < from || item.date > to)) throw new Error('Invalid calendar response'); + return response.data; + }, [from, to]); + const resource = useViewResource(load, { initialData: [] as CalendarItem[], errorMessage: '', deps: [from, to] }); + const locale = language === 'nb' ? 'nb-NO' : 'en-GB'; + const formatDate = (date: string) => new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone: 'UTC' }).format(calendarDate(date)!); + const byDay = useMemo(() => { + const result = new Map(); + resource.data.forEach(item => result.set(item.date, [...(result.get(item.date) ?? []), item])); + return new Map([...result.entries()].sort(([a], [b]) => Number(a < today) - Number(b < today) || a.localeCompare(b))); + }, [resource.data, today]); + const changeMonth = (value: string) => { if (/^[1-9]\d{3}-(0[1-9]|1[0-2])$/.test(value) && Number(value.slice(0, 4)) <= 9998) { setMonth(value); setSelectedDate(null); } }; + const offsetMonth = (offset: number) => changeMonth(monthValue(new Date(year, monthNumber - 1 + offset, 1))); + const firstWeekday = (calendarDate(from)!.getUTCDay() + 6) % 7; + const cells = Array.from({ length: Math.ceil((firstWeekday + days) / 7) * 7 }, (_, index) => { + const day = index - firstWeekday + 1; return day > 0 && day <= days ? `${month}-${String(day).padStart(2, '0')}` : null; + }); + const eventList = (date: string, items: CalendarItem[]) => + {formatDate(date)} + {items.map(item => + {formatDate(item.date)} + + )} + ; + + return + + + { if (!next) return; setView(next); try { localStorage.setItem(preferenceKey, next); } catch { /* View remains usable without storage. */ } }}> + {t('calendarAgenda')}{t('calendarMonth')} + + + changeMonth(event.target.value)} slotProps={{ inputLabel: { shrink: true }, htmlInput: { min: '1000-01', max: '9998-12' } }} sx={{ minWidth: 150, maxWidth: '100%' }} /> + + + + void resource.reload()} /> + {!resource.loading && !resource.refreshing && !resource.error && + {view === 'month' && + {Array.from({ length: 7 }, (_, index) => {new Intl.DateTimeFormat(locale, { weekday: 'narrow', timeZone: 'UTC' }).format(new Date(Date.UTC(2026, 10, 2 + index)))})} + {Array.from({ length: cells.length / 7 }, (_, week) => {cells.slice(week * 7, week * 7 + 7).map((date, day) => {date && })})} +
} + {view === 'agenda' ? [...byDay].map(([date, items]) => eventList(date, items)) : selectedDate ? (byDay.has(selectedDate) ? eventList(selectedDate, byDay.get(selectedDate)!) : {t('calendarDayEmpty')}) : {t('calendarSelectDay')}} + {!resource.data.length && {t('calendarAgendaEmpty')}} +
} +
+
; +} diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index e276a8f3..97fada3e 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -2,6 +2,19 @@ export type UiLanguage = "en" | "nb"; export const translations = { en: { + calendarAgendaTitle: "Calendar", + calendarDateScope: "Recorded application deadlines and follow-up dates, grouped by their stored calendar date. Times and original timezones are not inferred. Interview appointments are not stored here; undated workflow tasks remain in Reminders below.", + calendarView: "Calendar view", + calendarAgenda: "Agenda", + calendarMonth: "Month", + calendarPreviousMonth: "Previous month", + calendarNextMonth: "Next month", + calendarSelectedMonth: "Selected month", + calendarAgendaFailed: "Could not load recorded calendar dates.", + calendarAgendaEmpty: "No recorded deadlines or follow-up dates in this month.", + calendarDayEvents: "{date}, {count} recorded dates", + calendarDayEmpty: "No recorded dates on this day.", + calendarSelectDay: "Select a day to see linked applications.", processingTitle: "Processing monitor", processingSubtitle: "Document and AI operations, their owners and safe processing history.", processingDetails: "Processing details", @@ -3178,6 +3191,19 @@ export const translations = { coverAiReject: "Reject", }, nb: { + calendarAgendaTitle: "Kalender", + calendarDateScope: "Registrerte søknadsfrister og oppfølgingsdatoer, gruppert etter lagret kalenderdato. Klokkeslett og opprinnelige tidssoner utledes ikke. Intervjuavtaler lagres ikke her; oppgaver uten dato vises i Påminnelser nedenfor.", + calendarView: "Kalendervisning", + calendarAgenda: "Agenda", + calendarMonth: "Måned", + calendarPreviousMonth: "Forrige måned", + calendarNextMonth: "Neste måned", + calendarSelectedMonth: "Valgt måned", + calendarAgendaFailed: "Kunne ikke laste registrerte kalenderdatoer.", + calendarAgendaEmpty: "Ingen registrerte søknadsfrister eller oppfølgingsdatoer denne måneden.", + calendarDayEvents: "{date}, {count} registrerte datoer", + calendarDayEmpty: "Ingen registrerte datoer denne dagen.", + calendarSelectDay: "Velg en dag for å se tilknyttede søknader.", processingTitle: "Behandlingsovervåking", processingSubtitle: "Dokument- og KI-oppgaver, eiere og trygg behandlingshistorikk.", processingDetails: "Behandlingsdetaljer", diff --git a/job-tracker-ui/src/views/CalendarPage.tsx b/job-tracker-ui/src/views/CalendarPage.tsx new file mode 100644 index 00000000..c2f9948d --- /dev/null +++ b/job-tracker-ui/src/views/CalendarPage.tsx @@ -0,0 +1,7 @@ +import { Stack } from '@mui/material'; +import CalendarAgenda from '../components/CalendarAgenda'; +import RemindersView from '../components/RemindersView'; + +export default function CalendarPage() { + return ; +}