Files
pedwfrontend/context/journey-architecture-map.md
Robert Bond 27fceffbc9 Merged PR 2413: updated docs
updated docs

Related work items: #23754
2026-06-22 05:36:30 +00:00

208 KiB
Raw Permalink Blame History

Journey Architecture Map

This document is a maintainability-focused architecture map for two PEDW journeys:

  1. Public Search → Case Details
  2. My Portal Dashboard

It is a discovery-only slice.

It does not recommend refactor, implementation, API, state, or folder changes.


1. Public Search → Case Details

Purpose

Supports anonymous public discovery of published cases and transition from result browsing into a single case detail view.

For maintainers, this journey also includes the optional signed-in enrichment path where case detail pages preload account context for watch/portal-adjacent interactions.

Primary Entry Points

  • pages/searchresults.js
  • components/search/searchresults.js
  • pages/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • adjacent breadcrumb/state context:
    • components/breadcrumbs.js
    • store/currentView/*

Loaders / Initialisation

Search results page
  • pages/searchresults.js
    • getServerSideProps does light bootstrap only
    • captures request IP via getIP(req)
    • derives linked-case mode from query.lk
    • reads feature flags:
      • SHOWLOGIN
      • SHOWREPRESENTATIONS
    • dispatches:
      • setShowReps(showReps, showLoginCheck)
      • setSearch(query?.q || "")

Important maintainer note:

  • This loader does not fully hydrate search results itself.
  • Result retrieval is substantially driven in the client/component layer by components/search/searchresults.js via paged service calls.
Search result data bootstrap
  • components/search/searchresults.js
    • reads searchResultsObj and searchDetailsObj from Redux
    • derives pagination from @odata.nextLink
    • for paging/sorting calls:
      • getBasicSearchPaged(...)
      • getAdvancedSearchPaged(...)
    • after each result fetch, hydrates detail data with:
      • getSearchDetailsPaged(...)
    • dispatches:
      • setSearchResults(...)
      • setSearchDetails(...)
      • setCurrentPage(...)
Case details page
  • pages/case/[ticketnumber].js
    • getServerSideProps is the main loader for the detail page
    • captures request IP via getIP(req)
    • optionally resolves signed-in portal context:
      • getSession(ctx)
      • getPortalLogin(session.user.email)
      • getPersonalAccount(contactid)
      • setAccountDetails(accountDetails)
    • normalises the case identifier from route param to search form
    • retrieves the case through the search family, not a standalone case-by-ticket API:
      • getBasicSearch(developmentQuery)
      • getSearchDetails(searchResultsObj)
    • conditionally retrieves SIPS-specific enrichments:
      • getSIPSEvents(...)
      • getSIPSMedia(...)
    • retrieves messages:
      • getCaseMessage(incidentid)
    • dispatches:
      • setSearch(developmentQuery)
      • setSearchResults(searchResultsObj)
      • setSearchDetails(searchDetailsObj)
      • setEventDetails(eventsObj) when relevant
      • setMediaDetails(mediaObj) when relevant
      • setCurrentReference({...})
    • redirects to /404 if the search resolves to zero or multiple matches

State Ownership

Primary state slices
  • store/search/reducer.js

    • owns searchString
    • used as the retained current search input across search/case navigation
  • store/searchOutput/reducer.js

    • owns:
      • searchResultsObj
      • searchDetailsObj
      • documentDetailsObj
      • representationsObj
      • eventDetailsObj
      • mediaDetailsObj
    • this is the main read model for both results and case-detail rendering
  • store/currentView/reducer.js

    • owns:
      • caseReference
      • currentPage
      • showReps
      • showLogin
      • linkedCaseReferences
      • locale
    • caseReference is the key bridge from result selection into detail context
  • store/accountDetails/reducer.js

    • only participates when a user is signed in on the case detail route
    • owns signed-in account context:
      • accountDetails
      • loggedinUserId
      • containerID
  • store/watchedCases/reducer.js

    • participates when signed-in users watch/unwatch or manage email notifications from results
    • owns:
      • watchedCases
      • watchedCasesDetails
currentView usage
  • components/search/searchresults.js

    • sets currentReference on case link click
    • updates currentPage during pagination/sort
  • pages/case/[ticketnumber].js

    • sets canonical case reference context for the detail page
  • components/breadcrumbs.js

    • uses currentView.caseReference to reconstruct breadcrumb state and origin context
accountDetails usage
  • Not required for anonymous search or basic case reading
  • Used for optional signed-in enrichment on case pages and watchlist interactions in result views

Service Layer

Primary services
  • actions/services/searchDirectService.js

    • getBasicSearch(...)
    • getBasicSearchPaged(...)
    • getAdvancedSearchPaged(...)
    • getBasicSearchDetails(...)
    • getBasicSearchDetailsPaged(...)
    • getSearchDocumentDetails(...)
    • getLinkedCases(...)
  • actions/services/caseDirectService.js

    • getCaseMessage(...)
    • getCase(...)
    • getCaseByID(...)
    • getSIPSEvents(...)
    • getSIPSMedia(...)
    • getPortalModuleDetails(...) for adjacent case-detail enrichment patterns
  • actions/services/accountDirectService.js

    • getPortalLogin(...)
    • getPersonalAccount(...)
    • only used on the optional signed-in branch of case detail bootstrap
Supporting maintainability helper
  • components/utils/index.js
    • getSearchDetails(searchResultsObj)
    • expands result records into case-type-specific detail queries using collections.json
    • this is a key maintainability join point because it converts generic search rows into richer case detail payload lookups

API Layer

Principal route families
  • Public search reads

    • pages/api/endpoint/getbasicsearch_api.js
    • pages/api/endpoint/getbasicsearchpaged_api.js
    • pages/api/endpoint/getadvancedsearch_api.js
    • pages/api/endpoint/getadvancedsearchpaged_api.js
  • Search detail expansion / supporting reads

    • pages/api/endpoint/getbasicsearchdetails_api.js
    • pages/api/endpoint/getbasicsearchdetailspaged_api.js
    • pages/api/endpoint/getlinkedcases_api.js
  • Case-specific reads

    • pages/api/endpoint/getcase_api.js
    • pages/api/endpoint/getcasebyid_api.js
    • pages/api/endpoint/getcasemessage_api.js
    • pages/api/endpoint/getsipsevents_api.js
    • pages/api/endpoint/getsipsmedia_api.js
  • Signed-in account bootstrap on the case page

    • pages/api/endpoint/getportallogin_api.js
    • pages/api/endpoint/getpersonalaccount_api.js
Route-family characteristics
  • getbasicsearchpaged_api.js

    • helper-oriented CRM relay read
    • validates searchString, orderby, fieldSort, showNumberOfRecords
    • uses relayGet(...)
    • uses RELAY_POLICY_SEARCH_PAGED
    • normalises @odata.nextLink
  • getcase_api.js

    • narrow CRM relay read by incidentID
    • used as a supporting case lookup shape, though the public ticketnumber route primarily bootstraps via search

Integration Boundaries

  • CRM via Azure Relay

    • primary data source for search results, search detail expansion, case messages, case records, SIPS events, and SIPS media
    • touched because the journey is fundamentally a public case-discovery/read flow
  • NextAuth

    • touched only on the optional signed-in branch of pages/case/[ticketnumber].js
    • used to derive current session and then CRM contact context
  • Azure Storage

    • not part of the core public search → case details path in this slice
  • Azure Queue

    • not touched
  • GOV.UK Notify

    • not touched by the core read path
  • Local-only processing

    • Redux hydration and page state transitions
    • breadcrumb/view-state persistence
    • search result highlighting, sorting state, pagination state, and result/detail joining logic in the frontend

Architectural Flow

Public search results

User
pages/searchresults.js
→ Redux bootstrap (setSearch, setShowReps)
components/search/searchresults.js
searchDirectService.getBasicSearchPaged / getAdvancedSearchPaged
pages/api/endpoint/getbasicsearchpaged_api.js / related search routes
relayGet(...)
→ Azure Relay
→ Dynamics 365 CRM

Transition to case details

User
→ case link click in components/search/searchresults.js
→ Redux setCurrentReference(...)
pages/case/[ticketnumber].js SSR loader
searchDirectService.getBasicSearch(...) + components/utils.getSearchDetails(...)
→ supporting caseDirectService calls for messages / events / media
→ endpoint route family
→ Azure Relay
→ Dynamics 365 CRM

Optional signed-in enrichment

User session
getSession(ctx)
accountDirectService.getPortalLogin(email)
accountDirectService.getPersonalAccount(contactid)
→ Redux accountDetails
→ watchlist/account-aware UI behavior

Change Entry Set

First files to inspect
  • pages/searchresults.js
  • components/search/searchresults.js
  • pages/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • actions/services/searchDirectService.js
  • actions/services/caseDirectService.js
  • components/utils/index.js
  • store/search/reducer.js
  • store/searchOutput/reducer.js
  • store/currentView/reducer.js
Likely adjacent files
  • pages/api/endpoint/getbasicsearch_api.js
  • pages/api/endpoint/getbasicsearchpaged_api.js
  • pages/api/endpoint/getbasicsearchdetails_api.js
  • pages/api/endpoint/getbasicsearchdetailspaged_api.js
  • pages/api/endpoint/getcase_api.js
  • pages/api/endpoint/getcasemessage_api.js
  • pages/api/endpoint/getlinkedcases_api.js
  • pages/api/endpoint/getsipsevents_api.js
  • pages/api/endpoint/getsipsmedia_api.js
  • store/accountDetails/reducer.js
  • store/watchedCases/reducer.js
  • components/breadcrumbs.js
Highest-risk areas
  • Search contract shape and pagination assumptions (searchResultsObj, @odata.nextLink)
  • Result-to-detail expansion in components/utils/index.js
  • currentView.caseReference as the navigation/breadcrumb handoff
  • Case loader assumption that ticketnumber resolves uniquely through the search family
  • Optional signed-in account bootstrap on a nominally public page
  • Watched-case side interactions embedded in search results

Risk Classification

High

Reasoning:

  • public-facing and contract-sensitive
  • spans multiple read families rather than a single dedicated case-by-route loader
  • combines SSR and client-driven hydration patterns
  • includes subtle state handoff through Redux rather than only route params
  • optional signed-in behavior adds a second identity/bootstrap branch maintainers must understand

2. My Portal Dashboard

Purpose

Supports the authenticated portal landing experience for a signed-in user or LPA user by presenting:

  • my cases
  • watched cases
  • draft/awaiting-submission items
  • representation draft lists
  • submitted representation-related cards
  • account-contextual portal entry actions

This journey is the main authenticated dashboard bootstrap for portal-owned and draft-owned work.

Primary Entry Points

  • pages/myportal/index.js
  • components/myportal.js
  • components/myportal/mycases.js
  • components/myportal/watchedcases.js
  • components/myportal/myrepresentations.js
  • components/myportal/mysubmittedrepresentations.js
  • components/myportal/awaitingsubmissionfromblob.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js

Loaders / Initialisation

Dashboard page loader
  • pages/myportal/index.js
    • is the principal authenticated bootstrap for the dashboard
    • requires getSession(ctx)
    • redirects to /auth/signin when the session or session identity is absent
    • resolves CRM contact identity via:
      • getPortalLogin(thisSession.user.email)
    • resolves account record via:
      • getPersonalAccount(loggedInUser)
    • creates or ensures user storage container via:
      • createContainerProxy(thisSession.user.id)
    • branches between user and LPA case retrieval:
      • getMyCases(loggedInUser)
      • getMyLPACases(lpaId)
    • retrieves mixed-source dashboard datasets:
      • getRepsFromBlob(thisSession.user.id)
      • getWatchedCases(loggedInUser)
      • getAwaitingSubmissionFromBlob(thisSession.user.id)
    • classifies watchlist output using:
      • splitWatchedCasesBySubmissionState(watchedCases.value)
    • derives detail cards for multiple lists through bounded parallel getPortalModuleDetails(...) calls
    • stores locale and feature flags:
      • setLocale(locale)
      • setShowReps(showReps, showLoginCheck)
Dashboard detail expansion
  • inside pages/myportal/index.js, local getDetails(...)
    • determines per-list case reference form
    • maps each dashboard record to getPortalModuleDetails(collectionName, caseID)
    • this is a key aggregation step because it turns list rows into card/detail-ready case-specific data
Client-side dashboard navigation state
  • components/myportal/topthree.js

    • sets current case reference before navigating into detail or resume flows
    • refreshes watched cases and awaiting-submission lists after deletion actions
  • components/myportal/viewall.js

    • derives active list from currentView.currentView.viewKey or router.query.key
    • sets currentView and currentReference before navigating to case detail or representation edit flows

State Ownership

Primary state slices
  • store/accountDetails/reducer.js

    • owns:
      • accountDetails
      • loggedinUserId
      • containerID
    • this slice is the main ownership root for:
      • CRM contact identity
      • storage container identity
      • user display/account context
  • store/currentView/reducer.js

    • owns:
      • currentView
      • caseReference
      • currentPage
      • showReps
      • showLogin
      • locale
    • this slice drives which dashboard sub-view is active and what downstream case/representation context should be used
  • store/myCases/reducer.js

    • owns:
      • myCases
      • myCasesDetails
  • store/watchedCases/reducer.js

    • owns:
      • watchedCases
      • watchedCasesDetails
  • store/awaitingSubmission/reducer.js

    • owns:
      • awaitingSubmission
      • awaitingSubmissionDetails
      • awaitingSubmissionFromBlob
  • store/myRepresentations/*

    • not re-read in full for this slice, but used by pages/myportal/index.js as a primary journey state owner for:
      • myRepresentations
      • myRepresentationsDetails
      • mySubmittedReps
      • mySubmittedRepsDetails
currentView usage
  • components/myportal/topthree.js

    • sets currentReference before opening case/resume routes
  • components/myportal/viewall.js

    • uses currentView.viewKey to determine whether the page is showing:
      • my cases
      • watched cases
      • awaiting submission
      • my representations
      • submitted reps
    • updates currentView after list mutations to keep the dashboard sub-view stable
  • components/breadcrumbs.js

    • depends on currentView and caseReference to reconstruct myportal-origin breadcrumbs
accountDetails usage
  • pages/myportal/index.js

    • populates it at bootstrap time
  • components/myportal.js

    • uses it to determine LPA vs non-LPA rendering
    • uses user name, involvement type, and associated LPA display
  • components/myportal/topthree.js and components/myportal/viewall.js

    • use loggedinUserId for watched-case mutations and refreshes
    • use containerID for draft/blob deletion and resume pathways

Service Layer

Primary services
  • actions/services/accountDirectService.js

    • getPortalLogin(...)
    • getPersonalAccount(...)
  • actions/services/portalDirectService.js

    • getMyCases(...)
    • getMyLPACases(...)
    • getWatchedCases(...)
    • getWatchedCasesProxy(...)
    • getAwaitingSubmission(...)
    • getAwaitingSubmissionProxy(...)
    • createWatchedCases(...)
    • deleteWatchedCases(...)
  • actions/services/documentDirectService.js

    • createContainerProxy(...)
    • getRepsFromBlob(...)
    • getRepsFromBlobProxy(...)
    • getAwaitingSubmissionFromBlob(...)
    • getAwaitingSubmissionFromBlobProxy(...)
    • deleteAwaitingSubmissionsFromBlob(...)
    • deleteMyRepresentationsFromBlob(...)
  • actions/services/caseDirectService.js

    • getPortalModuleDetails(...)
    • getPortalModuleDetailsProxy(...)
    • used as the detail enrichment layer for dashboard cards and lists
Supporting domain helper
  • lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js
    • classifies watchlist records into watched cases vs submitted representations
    • maintainers should treat this as a journey-shaping policy boundary rather than just display logic

API Layer

Principal CRM-backed routes
  • pages/api/endpoint/getmycases_api.js
  • pages/api/endpoint/getmylpacases_api.js
  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/endpoint/getmyrepresentations_api.js
  • pages/api/endpoint/getawaitingsubmission_api.js
  • pages/api/endpoint/getportalmoduledetails_api.js
  • pages/api/endpoint/getpersonalaccount_api.js
  • pages/api/endpoint/getportallogin_api.js
Principal storage-backed routes
  • pages/api/file/setupcontainer.js
  • pages/api/file/getrepsblob.js
  • pages/api/file/getawaitingsubmissionfromblob.js
  • pages/api/file/getrepsblobproxy.js
  • pages/api/file/getawaitingsubmissionfromblobproxy.js
  • pages/api/file/deleteblobcase.js
  • pages/api/file/deleteblobrep.js
Route-family characteristics
  • getmycases_api.js

    • helper-oriented CRM relay read
    • requires loggedInUserId
    • filters incidents by CRM customer/contact ownership
    • transforms title into pinswg_title for downstream consumers
  • getwatchedcases_api.js

    • helper-oriented CRM relay read
    • requires loggedInUserId
    • reads watchlist rows and expands watched-case metadata
    • flattens nested watched case values into dashboard-friendly fields
  • getawaitingsubmissionfromblob.js

    • storage/blob read route
    • requires container and hash
    • validates signed hash before blob enumeration
    • reads draft progress files from Azure Storage

Integration Boundaries

  • NextAuth

    • required entry boundary for the dashboard
    • used to establish session.user.email and session.user.id
  • CRM via Azure Relay

    • used for:
      • portal login/contact resolution
      • account details
      • my cases
      • LPA cases
      • watched cases
      • portal module details
    • touched because the dashboard mixes user-owned and relationship-owned business records
  • Azure Storage

    • used for:
      • storage container creation/ensuring
      • representation draft blob lists
      • awaiting-submission draft lists
      • deletion of draft case/representation blobs
    • touched because dashboard content includes pre-submission work that is storage-owned rather than CRM-owned
  • Azure Queue

    • not directly touched by the dashboard landing slice reviewed here
  • GOV.UK Notify

    • not part of the dashboard landing bootstrap itself
  • Local-only processing

    • Redux hydration for all dashboard slices
    • current-view selection
    • watched-case classification
    • card/list sorting, list merges, and view transitions

Architectural Flow

User
pages/myportal/index.js
getSession(ctx)
accountDirectService.getPortalLogin(email)
accountDirectService.getPersonalAccount(contactid)
documentDirectService.createContainerProxy(session.user.id)
→ portal/document services fetch CRM-owned and storage-owned lists
caseDirectService.getPortalModuleDetails(...) for detail enrichment
→ Redux slices (accountDetails, myCases, watchedCases, myRepresentations, awaitingSubmission, currentView)
components/myportal.js and card/list components
→ endpoint/file routes
→ Azure Relay / Azure Storage

Change Entry Set

First files to inspect
  • pages/myportal/index.js
  • components/myportal.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js
  • actions/services/accountDirectService.js
  • actions/services/portalDirectService.js
  • actions/services/documentDirectService.js
  • actions/services/caseDirectService.js
  • store/accountDetails/reducer.js
  • store/currentView/reducer.js
  • store/myCases/reducer.js
  • store/watchedCases/reducer.js
  • store/awaitingSubmission/reducer.js
Likely adjacent files
  • pages/api/endpoint/getmycases_api.js
  • pages/api/endpoint/getmylpacases_api.js
  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/endpoint/getmyrepresentations_api.js
  • pages/api/endpoint/getportalmoduledetails_api.js
  • pages/api/endpoint/getportallogin_api.js
  • pages/api/endpoint/getpersonalaccount_api.js
  • pages/api/file/getawaitingsubmissionfromblob.js
  • pages/api/file/getrepsblob.js
  • pages/api/file/setupcontainer.js
  • pages/api/file/deleteblobcase.js
  • pages/api/file/deleteblobrep.js
  • lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js
  • components/myportal/mycases.js
  • components/myportal/watchedcases.js
  • components/myportal/myrepresentations.js
  • components/myportal/mysubmittedrepresentations.js
Highest-risk areas
  • Session → CRM contact bootstrap via getPortalLogin(...)
  • Mixed ownership model:
    • CRM-owned lists
    • Azure-storage-owned draft lists
  • LPA vs non-LPA branching in the page loader
  • currentView-driven list/view routing assumptions in viewall.js
  • Watchlist mutation/refresh behavior embedded in dashboard components
  • Container identity usage for draft deletion/resume paths
  • Detail enrichment fan-out using getPortalModuleDetails(...)

Risk Classification

High

Reasoning:

  • authenticated portal-critical journey
  • depends on both identity bootstrap and mixed integration data sources
  • mixes CRM-owned and blob-owned records in one page-level loader
  • multiple Redux slices must stay aligned for correct downstream navigation
  • list cards and view-all pages reuse the same state in several slightly different ways

Investigation Method

Files reviewed

Required context:

  • context/architecture.md
  • context/api-route-map.md
  • context/integration-map.md
  • context/portal-api-platform-assessment.md
  • memory-bank/change-log.md

Guardrails/context discipline:

  • .clinerules/refactor-branch-rules.md
  • GUARDRAILS.md

Journey pages and major components:

  • pages/searchresults.js
  • pages/case/[ticketnumber].js
  • pages/myportal/index.js
  • components/search/searchresults.js
  • components/case.js
  • components/myportal.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js

Supporting services/helpers:

  • actions/services/searchDirectService.js
  • actions/services/caseDirectService.js
  • actions/services/portalDirectService.js
  • actions/services/documentDirectService.js
  • actions/services/accountDirectService.js
  • components/utils/index.js

API handlers:

  • pages/api/endpoint/getbasicsearchpaged_api.js
  • pages/api/endpoint/getcase_api.js
  • pages/api/endpoint/getmycases_api.js
  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/file/getawaitingsubmissionfromblob.js

Redux ownership files:

  • store/accountDetails/reducer.js
  • store/currentView/reducer.js
  • store/search/reducer.js
  • store/searchOutput/reducer.js
  • store/watchedCases/reducer.js
  • store/myCases/reducer.js
  • store/awaitingSubmission/reducer.js

Searches performed

  • pages: getServerSideProps|getInitialProps
  • store: currentView|accountDetails|search|myportal|watchedCases
  • actions/services: getBasicSearchPaged|getAdvancedSearchPaged|getCase\(|getCaseByID|getMyCases|getMyRepresentations|getAwaitingSubmission|getWatchedCases
  • lib: loadMyPortal|resolveMyPortalAuthContext|search|case
  • components: currentView|accountDetails|getBasicSearch|getCase|getMyCases|getWatchedCases|pinsUser

Limitations

  • This slice was intentionally limited to the two requested journeys.
  • It did not trace unrelated journeys such as new appeal, representation submission, auth-only flows, or document-download deep paths beyond adjacent references.
  • It did not execute the application or produce runtime traces.
  • It did not generate a full route inventory.
  • It did not deeply inspect every nested component under case detail or myportal once the primary ownership and integration boundaries were established.
  • Some adjacent state slices, especially myRepresentations, were confirmed by usage in the page loader/component layer rather than fully re-read in this slice.

Recommendations

Documentation and understanding only:

  1. Treat this map as the maintainer-first companion to context/api-route-map.md.
  2. When changing either journey, start from the journey entry page and confirm the owning Redux slices before reading deeper API files.
  3. Preserve awareness that both journeys use aggregation patterns rather than single-source page loaders:
    • Public Search → Case Details uses search-family reads plus detail expansion.
    • My Portal Dashboard uses session/bootstrap plus mixed CRM and Azure Storage sources.
  4. Keep identity bootstrap and state ownership explicitly documented in future journey maps, because they are as important to maintainability as the page/component structure.
  5. If this document is extended later, continue documenting by journey and by ownership flow rather than by folder alone.

Slice 2 — Draft Appeal Creation and Appeal Submission / Finalisation

Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • The appeal lifecycle is split across two closely linked but distinct maintainability shapes:
    • Draft Appeal Creation is primarily a storage-owned journey rooted in session.user.id -> container identity.
    • Appeal Submission / Finalisation is an orchestration-owned transition from blob-backed draft state into queue-backed and CRM-backed submitted state.
  • The entry pages for both new and resumed appeals converge on the same page shell and flow components:
    • pages/newappeal/[appealtypes].js
    • pages/myportal/[appealtypes].js
    • components/newappeal/newAppealFlow.js
  • The strongest visible ownership model remains:
    • NextAuth session.user.id -> Azure Storage container
    • pinsUser cookie / CRM contact -> account context and submitted-case ownership context
  • Submission is not a single direct CRM write from the page layer. It visibly passes through:
    • check answers
    • PDF generation / finalisation prep
    • appeal-complete message route
    • Azure Queue message creation
    • downstream submitted-record processing

Draft Appeal Journey Map

Purpose

Business purpose:

  • allows an authenticated user to begin an appeal, build it over multiple sections, upload supporting files, save progress, exit, and later resume without immediate submission.

Maintainer purpose:

  • this journey is the main blob-backed draft lifecycle for appeals and is the clearest place to understand how PEDW uses session.user.id as storage/container ownership before CRM submission occurs.

Primary Entry Points

  • pages/newappeal/index.js
  • components/newappeal/createCase.js
  • pages/newappeal/[appealtypes].js
  • pages/myportal/[appealtypes].js
  • components/newappeal/newAppealFlow.js
  • components/newappeal/buildsection.js

Loaders / Initialisation

Draft creation entry
  • pages/newappeal/index.js
    • session-gated via getSession(ctx)
    • uses pinsUser cookie as CRM contact identity for account lookup
    • loads reference data for starting a draft:
      • getAppealsTypesForNewAppeal()
      • getLPA()
      • getPersonalAccount(loggedInUser)
    • dispatches:
      • setAppealType(...)
      • setLPA(...)
      • setLoggedInUserId(loggedInUser)
      • setAccountDetails(accountDetails)
      • setContainerID(thisSession.user.id)
Draft section loader
  • lib/newappeal/loadNewAppealPage.js

    • validates required query params:
      • appealtypes
      • apt
      • id
    • requires getSession(ctx)
    • requires session.user.id and session.user.email
    • requires pinsUser cookie presence
    • fetches in parallel:
      • appeal type reference data
      • mandatory fields
      • pick lists
      • getProgressFromBlob(session.user.id, query.id)
      • getPersonalAccount(pinsUser)
    • reads form XML through readFormXml(query.appealtypes)
  • pages/newappeal/[appealtypes].js

    • delegates SSR loading to loadNewAppealPage(ctx)
    • hydrates Redux using hydrateNewAppealStore(...)
Draft resume loader
  • lib/myportal/loadMyPortalAppealPage.js

    • validates required query params:
      • appealtypes
      • apt
      • casereference
    • requires getSession(ctx)
    • requires pinsUser cookie
    • fetches in parallel:
      • appeal type reference data
      • mandatory fields
      • pick lists
      • getFilesFromBlob(session.user.id, query.casereference)
      • getProgressFromBlob(session.user.id, query.casereference)
      • getPersonalAccount(pinsUser)
    • conditionally fetches getAwaitingSubmissionFromBlob(session.user.id) when query.key is present
    • reads form XML through readFormXml(query.appealtypes)
  • pages/myportal/[appealtypes].js

    • delegates SSR loading to loadMyPortalAppealPage(ctx)
    • hydrates Redux using hydrateMyPortalAppealStore(...)
Draft bootstrap logic
  • lib/newappeal/hydrateNewAppealStore.js

    • constructs appealType.caseReference as:
      • ticketnumber: query.id
      • incidentid: query.id
      • caseDetails: blobProgress
    • dispatches:
      • setLoggedInUserId(...)
      • setLoggedInUserEmail(...)
      • setAppealLPA(query.lpa)
      • setAppealTypeID(query.apt)
      • setCaseReference(...)
      • setForm(xmlStr, mandatoryFieldsData, pickListData)
      • setAppealType(appealTypeData)
      • setContainerID(session.user.id)
      • setAccountDetails(accountDetails)
  • lib/myportal/hydrateMyPortalAppealStore.js

    • builds equivalent case reference state for resume mode
    • additionally dispatches:
      • setFilesForAppeal(blobList)
      • setAwaitingSubmissionFromBlob(...)
      • setAwaitingSubmissionDetails(...)
      • setCurrentView({ viewName: "Awaiting Submission", viewKey: "awaitingSubmissionDetails" }) when resume is entered from that list context

State Ownership

Primary slices
  • store/appealType/reducer.js

    • main appeal-journey owner for:
      • appealTypeOptions
      • appealTypeID
      • currentSection
      • appealLPA
      • caseReference
      • formComplete
      • documentList
      • fileList
      • fileCount
      • progress
  • store/formData/reducer.js

    • owns:
      • formData
      • mandatoryFieldsData
      • pickListData
  • store/accountDetails/reducer.js

    • owns:
      • accountDetails
      • loggedinUserId
      • loggedinUserEmail
      • containerID
    • containerID is the strongest visible draft-ownership identifier in the UI/store layer
  • store/currentView/reducer.js

    • supports locale and resume-entry view state
    • used less as the main draft owner than in dashboard journeys, but still participates in:
      • locale
      • resume-origin context
  • store/awaitingSubmission/reducer.js

    • participates when a saved draft is resumed from myportal context
currentView usage
  • hydrateMyPortalAppealStore(...) sets currentView when the draft resume path originates from awaiting-submission UI
  • component flows use current section progression through appealType.currentSection rather than currentView
accountDetails usage
  • provides:
    • CRM contact identity (loggedinUserId)
    • email for partial-save/completion emails (loggedinUserEmail)
    • container ownership (containerID)
  • components/newappeal/buildsection.js and buildchecksection.js rely on accountDetails.containerID to persist and finalise draft material
Draft ownership state
  • draft ownership is represented across:
    • accountDetails.containerID
    • appealType.caseReference.ticketnumber
    • appealType.caseReference.caseDetails
    • appealType.fileList
    • blob-backed progress / file objects retrieved from storage

Service Layer

Primary service modules
  • actions/services/documentDirectService.js

    • createContainerProxy(...)
    • getProgressFromBlob(...)
    • getFilesFromBlob(...)
    • uploadFiles(...)
    • generateAppealPDF(...)
    • deleteAwaitingSubmissionsFromBlob(...)
  • actions/services/accountDirectService.js

    • getPersonalAccount(...)
  • actions/services/referenceDataService.js

    • getAppealsTypesForNewAppeal(...)
    • getMandatoryFields(...)
    • getPickLists(...)
    • getLPA(...)
  • lib/newappeal/journeyEffects.js

    • uploadAppealFilesEffect(...)
    • sendPartialSaveEmailEffect(...)
    • generateAppealPDFEffect(...)
    • sendCaseCompleteMessageEffect(...)
Major component/service interaction points
  • components/newappeal/buildsection.js
    • orchestrates section progression, progress persistence, partial-save email composition, and upload trigger behavior
  • components/newappeal/newAppealFlow.js
    • switches between section form, check answers, and completion views based on appealType.currentSection

API Layer

Principal routes
  • Azure Storage / draft persistence
    • pages/api/file/setupcontainer.js
    • pages/api/file/getprogressobjblob.js
    • pages/api/file/getbloblist.js
    • pages/api/file/upload.js
    • pages/api/file/uploadsinglefile.js
    • pages/api/file/deleteblobcase.js
Route-family classification
  • setupcontainer.js

    • Azure Storage
    • creates or ensures the user-owned container using a signed hash-protected path
  • getprogressobjblob.js

    • Azure Storage
    • retrieves the most recent draft appeal JSON for a case reference within the container
  • getbloblist.js

    • Azure Storage
    • lists uploaded files under a case folder
  • upload.js

    • Azure Storage
    • stores draft appeal or representation payload/file material into blob storage
  • deleteblobcase.js

    • Azure Storage
    • deletes all blobs under a draft case prefix

Integration Boundaries

  • NextAuth

    • required because draft ownership begins with session.user.id
  • CRM

    • touched during account bootstrap and reference/account lookup, but not yet as the primary owner of the draft itself
  • Azure Storage

    • primary persistence boundary for draft progress, files, case JSON, and generated PDFs before submission
  • Azure Queue

    • not part of draft creation itself
  • GOV.UK Notify

    • touched for partial-save and completion email helper paths in the UI/service layer
  • Local processing

    • form XML parsing
    • progress derivation
    • payload cleanup
    • section/state transitions

Ownership Model

NextAuth session.user.id
→ accountDetails.containerID
→ Azure Storage container
→ caseReference ticketnumber / casefolderID
→ draft JSON + uploaded files + case blob

Visible characteristics:

  • storage ownership is strongest at session.user.id -> containerID
  • draft identity is then refined by caseReference / casefolder prefix inside the container
  • CRM contact identity (pinsUser) supports account/bootstrap context but is not the main draft storage key

Architectural Flows

Draft creation / save

User
pages/newappeal/index.js
→ account/reference bootstrap
pages/newappeal/[appealtypes].js
loadNewAppealPage()
hydrateNewAppealStore()
→ Redux (appealType, formData, accountDetails)
components/newappeal/buildsection.js
uploadAppealFilesEffect() / progress persistence behavior
pages/api/file/upload.js + getprogressobjblob.js + getbloblist.js
→ Azure Storage

Draft resume

User
pages/myportal/[appealtypes].js
loadMyPortalAppealPage()
getFilesFromBlob() + getProgressFromBlob()
hydrateMyPortalAppealStore()
→ Redux hydration with blob progress and file list
components/newappeal/newAppealFlow.js

Change Entry Set

First files to inspect
  • pages/newappeal/index.js
  • pages/newappeal/[appealtypes].js
  • pages/myportal/[appealtypes].js
  • lib/newappeal/loadNewAppealPage.js
  • lib/myportal/loadMyPortalAppealPage.js
  • lib/newappeal/hydrateNewAppealStore.js
  • lib/myportal/hydrateMyPortalAppealStore.js
  • components/newappeal/buildsection.js
  • actions/services/documentDirectService.js
  • actions/azurestorage.js
  • store/appealType/reducer.js
  • store/formData/reducer.js
  • store/accountDetails/reducer.js
Adjacent files
  • pages/api/file/setupcontainer.js
  • pages/api/file/getprogressobjblob.js
  • pages/api/file/getbloblist.js
  • pages/api/file/upload.js
  • pages/api/file/uploadsinglefile.js
  • pages/api/file/deleteblobcase.js
  • components/newappeal/newAppealFlow.js
  • lib/newappeal/journeyEffects.js
Highest-risk areas
  • session/container ownership assumptions
  • caseReference prefix assumptions in blob naming
  • progress JSON shape versus form XML expectations
  • save/resume state handoff between storage and Redux hydration
  • file-list merging/deduplication in section progress

Risk Classification

High

Reasoning:

  • user-critical draft persistence journey
  • strong dependence on storage naming/path conventions
  • loader/bootstrap and hydration behavior must stay aligned
  • save/resume integrity depends on both storage and Redux state consistency

Appeal Submission / Finalisation Journey Map

Purpose

Business purpose:

  • converts a complete draft appeal into a submitted appeal and confirmation outcome.

Maintainer purpose:

  • this journey is the clearest orchestration boundary where PEDW transitions from storage-owned draft material into queue-backed submitted processing and CRM-backed case records.

Primary Entry Points

  • components/newappeal/buildchecksection.js
  • components/newappeal/complete.js
  • components/newappeal/newAppealFlow.js
  • resumed-entry shell:
    • pages/myportal/[appealtypes].js

Loaders / Initialisation

  • submission uses the same draft loader/hydration paths described above
  • no separate SSR loader exists just for finalisation
  • the submission preconditions are established by:
    • hydrated appealType.caseReference
    • hydrated accountDetails.containerID
    • hydrated formData
    • hydrated uploaded file list / draft progress state
Check answers bootstrap
  • components/newappeal/newAppealFlow.js

    • routes to BuildCheckSection when currentSection === sectionCount + 1
  • components/newappeal/buildchecksection.js

    • assembles final review payload from:
      • legacyFormState.appealForm.values
      • appealType.fileList
      • accountDetails.containerID
      • appealType.caseReference.ticketnumber
    • deduplicates file list before finalisation
    • requires explicit user confirmation before submission button becomes active

State Ownership

Primary slices
  • store/appealType/reducer.js

    • controls finalisation stage through:
      • currentSection
      • caseReference
      • fileList
      • formComplete
  • store/accountDetails/reducer.js

    • provides:
      • containerID
      • loggedinUserId
      • loggedinUserEmail
      • accountDetails.pinswg_typeofinvolvement
  • store/formData/reducer.js

    • provides mandatory fields/picklist/form shape used to render and validate final answers
Completion-state transition
  • BuildCheckSection.finaliseAppeal() sets setCurrentSection(9999) after PDF generation and finalisation message trigger
  • components/newappeal/newAppealFlow.js then switches to CompleteAppeal
  • SECTION_COMPLETE therefore acts as the visible client-side completion-state boundary

Service Layer

Primary services
  • actions/services/documentDirectService.js

    • generateAppealPDF(...)
  • actions/services/portalDirectService.js

    • sendCaseCompleteMessage(...)
    • constructs signed URL to createappealcompletemessage_api
  • actions/services/caseDirectService.js

    • createNewCase(...)
    • updateCase(...)
    • patchCase(...)
    • these are the visible CRM mutation helpers adjacent to finalisation ownership transition
  • lib/newappeal/journeyEffects.js

    • generateAppealPDFEffect(...)
    • sendCaseCompleteMessageEffect(...)
    • sendCompletionEmailEffect(...)
Major finalisation component behavior
  • components/newappeal/buildchecksection.js

    • generates appeal PDF
    • triggers case-complete message effect
    • advances to completion state
  • components/newappeal/complete.js

    • sends completion email on mount
    • reconstructs submitted payload context for display/side-effect continuity
    • visibly represents post-submission confirmation state

API Layer

Principal routes
  • queue/finalisation/orchestration

    • pages/api/file/createappealcompletemessage_api.js
    • pages/api/file/createappealcompletemessageproxy_api.js
  • CRM mutation support

    • pages/api/endpoint/createcase_api.js
    • pages/api/endpoint/updatecase_api.js
    • pages/api/endpoint/patchcase_api.js
  • draft storage dependencies used during finalisation

    • pages/api/file/getprogressobjblob.js
    • pages/api/file/getbloblist.js
    • pages/api/file/upload.js
Route-family classification
  • createappealcompletemessage_api.js

    • queue/finalisation
    • orchestration
    • reads draft progress blob and case blob
    • rewrites progress blob with appealComplete
    • updates case blob fields
    • may trigger account-type side effect
    • creates queue message for submitted application processing
  • createcase_api.js

    • CRM relay write
    • creates incident in CRM and writes case blob to storage
  • updatecase_api.js

    • CRM relay write
    • patches appeal-form-specific CRM entity by object ID
  • patchcase_api.js

    • CRM relay write
    • patches the incident servicestage

Integration Boundaries

  • NextAuth

    • still the root for container identity and authenticated access to the stored draft
  • CRM

    • visible submitted-record target via createcase_api, updatecase_api, patchcase_api
    • visible account update side effect in createappealcompletemessage_api
  • Azure Storage

    • source of truth for draft payload, uploaded files, and case blob before submission completes
    • also persists rewritten completion marker state
  • Azure Queue

    • explicit transition boundary through createCaseCompleteMessage(...)
    • carries message containing appealpath, casepath, and filespath
  • GOV.UK Notify

    • used for completion email from components/newappeal/complete.js
  • Local processing

    • deduplication, payload cleanup, confirmation-state UI, and client-side section completion transition

Ownership Model

Session identity
→ container identity
→ draft ownership
→ finalisation route reads blob-backed draft state
→ queue message points to storage artifacts
→ downstream submitted appeal ownership transitions toward CRM-backed records

Visible ownership transition:

Draft ownership (session.user.id container)
→ appealComplete marker written to draft blob
→ Azure Queue message created with storage paths
→ CRM case creation/update pathways become the submitted-record boundary

Important maintainer note:

  • the reviewed code makes the queue handoff explicit, but downstream consumer processing is out of scope of this frontend repository slice.
  • the visible transition point is therefore the queue message plus the adjacent CRM mutation routes, not a fully local end-to-end submitted pipeline implementation inside one file.

Architectural Flows

Check answers to finalisation

User
components/newappeal/buildchecksection.js
→ assemble final payload + files list
generateAppealPDFEffect()
documentDirectService.generateAppealPDF()
→ storage/PDF path
sendCaseCompleteMessageEffect()
portalDirectService.sendCaseCompleteMessage()
pages/api/file/createappealcompletemessage_api.js

Finalisation orchestration

createappealcompletemessage_api.js
→ read draft appeal blob
→ read case blob
→ rewrite completion state
→ persist updated blob state
→ build queue payload
actions/azurestorage.createCaseCompleteMessage()
→ Azure Queue
→ downstream submitted processing / CRM transition

Completion state

User
→ finalise action succeeds
setCurrentSection(9999)
components/newappeal/complete.js
→ completion email effect
→ confirmation UI

Change Entry Set

First files to inspect
  • components/newappeal/buildchecksection.js
  • components/newappeal/complete.js
  • lib/newappeal/journeyEffects.js
  • actions/services/portalDirectService.js
  • actions/services/documentDirectService.js
  • actions/services/caseDirectService.js
  • pages/api/file/createappealcompletemessage_api.js
  • actions/azurestorage.js
Adjacent files
  • pages/api/file/createappealcompletemessageproxy_api.js
  • pages/api/endpoint/createcase_api.js
  • pages/api/endpoint/updatecase_api.js
  • pages/api/endpoint/patchcase_api.js
  • components/newappeal/newAppealFlow.js
  • store/appealType/reducer.js
  • store/accountDetails/reducer.js
Highest-risk areas
  • queue payload creation and storage path assumptions
  • draft blob rewrite before submission handoff
  • case blob update logic
  • involvement/account side effects during finalisation
  • coordination between completion UI state and actual backend handoff
  • mixed storage + queue + CRM orchestration boundary

Risk Classification

Very High

Reasoning:

  • it is the core ownership transition boundary in the appeal lifecycle
  • it crosses storage, queue, and CRM concerns
  • subtle regressions can break submission without obviously breaking draft editing
  • completion UI state is near, but not identical to, true backend workflow completion

Ownership Model

Draft ownership root

NextAuth session.user.id
→ setContainerID(session.user.id)
→ storage container identity
→ casefolderID / ticketnumber prefix
→ draft appeal JSON
→ case blob
→ uploaded files

Submitted ownership transition

Draft blob state
→ finalisation route
→ queue message with storage artifact paths
→ submitted processing boundary
→ CRM case / appeal entity ownership context

Visible split:

  • before submission: ownership is primarily storage/container scoped
  • after submission boundary: ownership becomes increasingly CRM-scoped, with queue handoff as the clearest transition marker visible here

Architectural Flows

Draft Appeal Creation

User
→ New Appeal page
→ SSR loader
→ Redux hydration
→ BuildSection
→ document services
→ file APIs
→ Azure Storage

Draft Resume

User
→ My Portal resume page
→ SSR loader
→ getProgressFromBlob + getFilesFromBlob
→ Redux hydration
→ NewAppealFlow

Appeal Submission / Finalisation

User
→ Check Answers
→ generateAppealPDF
→ createappealcompletemessage_api
→ Azure Storage draft read/write
→ Azure Queue
→ CRM transition boundary

Change Entry Sets

Draft Appeal Creation

  • start with:
    • pages/newappeal/index.js
    • pages/newappeal/[appealtypes].js
    • lib/newappeal/loadNewAppealPage.js
    • components/newappeal/buildsection.js
    • actions/services/documentDirectService.js
    • pages/api/file/{setupcontainer,getprogressobjblob,getbloblist,upload,deleteblobcase}.js

Appeal Submission / Finalisation

  • start with:
    • components/newappeal/buildchecksection.js
    • components/newappeal/complete.js
    • lib/newappeal/journeyEffects.js
    • pages/api/file/createappealcompletemessage_api.js
    • actions/azurestorage.js
    • pages/api/endpoint/{createcase_api,updatecase_api,patchcase_api}.js

Risk Classification

  • Draft Appeal Creation: High
  • Appeal Submission / Finalisation: Very High

Investigation Method

Files reviewed for Slice 2

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Journey pages / loaders / hydration:

  • pages/newappeal/index.js
  • pages/newappeal/[appealtypes].js
  • pages/myportal/[appealtypes].js
  • lib/newappeal/loadNewAppealPage.js
  • lib/myportal/loadMyPortalAppealPage.js
  • lib/newappeal/hydrateNewAppealStore.js
  • lib/myportal/hydrateMyPortalAppealStore.js

Journey components / effects:

  • components/newappeal/newAppealFlow.js
  • components/newappeal/buildsection.js
  • components/newappeal/buildchecksection.js
  • components/newappeal/complete.js
  • lib/newappeal/journeyEffects.js

State / service files:

  • store/appealType/reducer.js
  • store/formData/reducer.js
  • store/accountDetails/reducer.js
  • store/currentView/reducer.js
  • store/awaitingSubmission/reducer.js
  • actions/services/documentDirectService.js
  • actions/services/portalDirectService.js
  • actions/services/caseDirectService.js
  • actions/services/accountDirectService.js
  • actions/azurestorage.js

API handlers:

  • pages/api/file/setupcontainer.js
  • pages/api/file/getprogressobjblob.js
  • pages/api/file/getbloblist.js
  • pages/api/file/upload.js
  • pages/api/file/deleteblobcase.js
  • pages/api/file/createappealcompletemessage_api.js
  • pages/api/endpoint/createcase_api.js
  • pages/api/endpoint/updatecase_api.js
  • pages/api/endpoint/patchcase_api.js

Searches performed for Slice 2

  • pages: newappeal|createappeal|checkanswers|appealtypes|casereference
  • lib: loadNewAppealPage|loadMyPortalAppealPage|hydrateNewAppealStore|hydrateMyPortalAppealStore|journeyEffects|session.user.id|containerID
  • actions/services: getProgressFromBlob|getFilesFromBlob|createContainerProxy|sendCaseCompleteMessage|createNewCase|updateCase|patchCase|uploadFiles|generateAppealPDF
  • pages/api: createappealcompletemessage_api|setupcontainer|getprogressobjblob|getbloblist|uploadsinglefile|upload\.js|deleteblobcase|createcase_api|updatecase_api|patchcase_api
  • store: appealType|containerID|caseReference|formData|currentView|accountDetails
  • components/newappeal: check|complete|upload|save|submit|partial|generateAppealPDF|sendCaseCompleteMessage
  • actions: createCaseCompleteMessage|getCaseBlob|downloadProgressFile|getProgressBlobs|createBlob\(|deleteBlobCase\(|createQueue|QueueClient|sendMessage

Limitations for Slice 2

  • This slice was intentionally limited to draft appeals and appeal finalisation only.
  • Representation journeys were not traced.
  • Downstream queue consumers outside the reviewed frontend repository were not inspected.
  • No runtime execution or queue-processing verification was performed.
  • No implementation changes, tests, or scripts were run because this remained documentation-only discovery.

Risks / Cautions

  1. The visible draft ownership model is strong at the loader/bootstrap level, but several storage APIs still accept caller-supplied container/casefolder values and rely on signed-path integrity.
  2. Finalisation is an orchestration boundary, not a simple page submit. Maintainers should expect interactions across UI state, storage state, queue handoff, and CRM write helpers.
  3. Completion UI state (currentSection = 9999) should not be treated as equivalent to a fully independently verified downstream submitted-record outcome.
  4. Queue creation is visible in this slice; downstream processing behavior is not.

Validation Performed

  • Re-read all required Slice 2 context files before investigation.
  • Performed non-destructive code reading and targeted searches only.
  • Traced the visible ownership model from:
    • session
    • container
    • draft blob state
    • queue handoff
    • CRM mutation boundary
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Representation Draft Creation and Representation Submission / Finalisation

This would complete the parallel maintainability map for the other major storage-backed and queue-backed submission lifecycle without widening into implementation work.


Slice 3 — Draft Representation Creation and Representation Submission / Finalisation

Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • The representation lifecycle reuses much of the same platform shape as the appeal lifecycle:
    • authenticated session bootstrap
    • blob-backed draft persistence in a session.user.id container
    • signed storage routes
    • queue-backed completion handoff
  • The most important structural difference is that representation creation is centred on a case-linked representation draft rather than a standalone appeal draft keyed by a new appeal case reference from the start.
  • Representation draft and submission flows rely more heavily on currentView representation-specific state than the appeal flow, especially for:
    • representationCapacity
    • representationSubmit
    • representationSubmitConfirmation
    • representationMessageSent
    • representation file list state
  • The representation completion flow visibly includes more portal-side side effects than the appeal completion flow:
    • representation involvement creation
    • completion queue message
    • email send
    • watched-case/representation-submitted update

Draft Representation Journey Map

Purpose

Business purpose:

  • allows an authenticated portal user to start a representation against a case, save it as a draft, upload supporting files, leave, and later return to continue editing before final submission.

Maintainer purpose:

  • this journey is the clearest representation-specific example of storage-backed draft ownership using the user container, but with a stronger dependency on case context and representation metadata than the appeal draft flow.

Primary Entry Points

  • pages/myportal/representation.js
  • lib/representation/pageLoaders.js
  • components/representation.js
  • components/case/representation/*
  • adjacent dashboard/list entry points that navigate into it:
    • components/myportal/viewall.js
    • components/myportal/topthree_reps.js

Loaders / Initialisation

Primary page loader
  • pages/myportal/representation.js
    • uses wrapper.getServerSideProps
    • delegates SSR bootstrap to loadRepresentationPage({ store, ctx })
Shared representation bootstrap
  • lib/representation/pageLoaders.js
    • loadRepresentationBootstrap({ ctx })
      • requires getSession(ctx)
      • resolves CRM contact via:
        • getPortalLogin(session.user.email)
      • loads account details via:
        • getPersonalAccount(contactid)
      • loads blob-backed representation drafts via:
        • getRepsFromBlob(session.user.id)
      • derives detail payloads for existing drafts with:
        • getDetails(myRepresentations, "myRepresentations")
        • which calls getCase(...) and getPortalModuleDetails(...)
New representation bootstrap
  • loadNewRepresentation({ store, ctx, bootstrap })
    • requires query.case
    • resolves case context via:
      • getBasicSearch(query.case)
      • getSearchDetails(searchResultsObj)
    • dispatches:
      • setContainerID(session.user.id)
      • setSearchResults(...)
      • setSearchDetails(...)
      • setSearch(query.case)
      • setCurrentView({ viewName: "My Representations", viewKey: "myRepresentations" })
      • setCurrentReference({...})
      • setAccountDetails(accountDetails)
Existing representation draft bootstrap
  • loadExistingRepresentation({ store, ctx, bootstrap })
    • requires:
      • query.case
      • query.created
    • resolves case context via:
      • getBasicSearch(query.case)
      • getSearchDetails(searchResultsObj)
    • finds the existing representation draft by matching repfile_name === query.created
    • loads supporting representation files from storage via:
      • getRepsFilesBlobs(containerID, ticketnumber/caseRef, repfile_name)
    • dispatches:
      • setSearchResults(...)
      • setSearchDetails(...)
      • setContainerID(session.user.id)
      • setMyRepresentations(...)
      • setMyRepresentationsDetails(...)
      • setCurrentReference({... repDetails, filesList ...})
      • setRepresentationCapacity(result.representationCapacity)
      • setAccountDetails(accountDetails)
      • setFilesForRepresentations(repsFileListObj)

State Ownership

Primary slices
  • store/currentView/reducer.js

    • main representation journey owner for:
      • caseReference
      • representationCapacity
      • representationSubmit
      • representationSubmitConfirmation
      • representationMessageSent
      • fileList
      • locale
  • store/myRepresentations/reducer.js

    • owns:
      • myRepresentations
      • myRepresentationsDetails
      • mySubmittedReps
      • mySubmittedRepsDetails
  • store/accountDetails/reducer.js

    • owns:
      • accountDetails
      • loggedinUserId
      • containerID
    • containerID is the strongest visible draft-representation storage owner
  • store/searchOutput/reducer.js

    • provides case context to the representation journey through:
      • searchResultsObj
      • searchDetailsObj
currentView usage
  • representation draft lifecycle uses currentView more directly than the appeal lifecycle for journey state:
    • caseReference.repDetails
    • representationCapacity
    • representationSubmit
    • representationSubmitConfirmation
    • representationMessageSent
    • representation file list
  • breadcrumb/back-link behaviour also depends on these representation flags and questionnaire state
accountDetails usage
  • provides:
    • CRM contact identity
    • email address for completion notifications
    • storage container identity for draft retrieval/deletion/completion

Service Layer

Primary service modules
  • actions/services/documentDirectService.js

    • getRepsFromBlob(...)
    • getRepsFromBlobProxy(...)
    • deleteMyRepresentationsFromBlob(...)
    • generateRepPDF(...)
  • actions/services/portalDirectService.js

    • getMyRepresentations(...)
    • getMyRepresentationsProxy(...)
    • getRepresentations(...)
    • getRepresentationsProxy(...)
    • sendRepCompleteMessage(...)
    • setRepInvolvment(...)
  • actions/services/accountDirectService.js

    • getPortalLogin(...)
    • getPersonalAccount(...)
  • actions/services/caseDirectService.js

    • getCase(...)
    • getPortalModuleDetails(...)
  • actions/services/searchDirectService.js

    • getBasicSearch(...)

API Layer

Principal routes
  • storage-backed draft routes

    • pages/api/file/getrepsblob.js
    • pages/api/file/getrepsblobproxy.js
    • pages/api/file/deleteblobrep.js
    • adjacent representation draft JSON update route:
      • pages/api/file/editRepJson.js
  • CRM-backed representation retrieval routes

    • pages/api/endpoint/getmyrepresentations_api.js
    • pages/api/endpoint/getmyrepresentationsproxy_api.js
    • pages/api/endpoint/getrepresentations_api.js
    • pages/api/endpoint/getrepresentationsproxy_api.js
Route-family classification
  • getrepsblob.js

    • Azure Storage
    • reads all representation draft JSON blobs in the user container via signed hash validation
  • deleteblobrep.js

    • Azure Storage
    • deletes a representation draft subtree using container + casefolder + repfile identifier
  • getmyrepresentations_api.js

    • CRM relay
    • retrieves CRM representation records filtered by contact ownership
  • getrepresentations_api.js

    • CRM relay
    • retrieves published representations for a case by incident ID

Integration Boundaries

  • NextAuth

    • required for session bootstrap and the storage container owner
  • CRM via Azure Relay

    • used for contact resolution, account details, case lookup, portal module detail lookup, and representation retrieval
  • Azure Storage

    • primary persistence layer for representation drafts and representation files before submission
  • Azure Queue

    • not part of the draft creation phase itself
  • GOV.UK Notify

    • not a core part of draft creation itself
  • Local-only processing

    • representation draft selection
    • questionnaire/back-link view state
    • derived case/search context joining

Ownership Model

NextAuth session.user.id
→ accountDetails.containerID
→ Azure Storage container
→ rep draft JSON blobs
→ representation file subtree

Visible distinction from appeals:

  • appeal drafts are keyed around an appeal case reference being created/progressed
  • representation drafts are keyed around an existing case context plus a repfile_name draft identity inside the container

Architectural Flow

New representation draft

User
pages/myportal/representation.js
loadRepresentationPage()
loadRepresentationBootstrap()
loadNewRepresentation()
→ Redux hydration (currentView, searchResultsObj, accountDetails)
→ representation components
→ representation draft persisted to Azure Storage-backed routes

Existing representation draft resume

User
pages/myportal/representation.js?case=...&state=edit&created=...
loadExistingRepresentation()
→ blob draft match by repfile_name
getRepsFilesBlobs(...)
→ Redux hydration with repDetails, representationCapacity, and representation file list

Change Entry Set

First files to inspect
  • pages/myportal/representation.js
  • lib/representation/pageLoaders.js
  • components/representation.js
  • actions/services/documentDirectService.js
  • actions/services/portalDirectService.js
  • store/currentView/reducer.js
  • store/myRepresentations/reducer.js
Adjacent files
  • pages/api/file/getrepsblob.js
  • pages/api/file/getrepsblobproxy.js
  • pages/api/file/deleteblobrep.js
  • pages/api/file/editRepJson.js
  • pages/api/endpoint/getmyrepresentations_api.js
  • pages/api/endpoint/getrepresentations_api.js
  • components/case/representation/*
Highest-risk areas
  • draft identity through repfile_name
  • combined use of case search context and storage-backed representation context
  • currentView representation-specific flags
  • container-scoped file tree handling for representation subfolders

Risk Classification

High

Reasoning:

  • sensitive user draft flow
  • depends on both storage ownership and case-linked context
  • relies on several representation-specific state flags that can drift from generic appeal behaviour

Representation Submission / Finalisation Journey Map

Purpose

Business purpose:

  • converts a representation draft or newly entered representation into a submitted representation outcome for a case.

Maintainer purpose:

  • this journey shows how representation submission differs from appeal submission by centring on involvement creation, representation queue handoff, and watched-case/submission side effects rather than case creation.

Primary Entry Points

  • components/case/representation/representationComplete.js
  • pages/myportal/representation.js
  • components/representation.js

Loaders / Initialisation

  • uses the same loadRepresentationPage() bootstrap as draft creation
  • finalisation-specific client state is carried through currentView rather than a separate SSR loader
  • representation completion depends on hydrated:
    • currentView.caseReference
    • currentView.representationCapacity
    • currentView.representationMessageSent
    • accountDetails.containerID
    • repFormData.repfile_name

State Ownership

Primary slices
  • store/currentView/reducer.js

    • main submission-state owner for:
      • representationSubmit
      • representationSubmitConfirmation
      • representationMessageSent
      • representationCapacity
      • caseReference.repDetails
  • store/accountDetails/reducer.js

    • provides:
      • CRM contact identity
      • email address
      • container identity
  • store/myRepresentations/reducer.js

    • stores list-level representation state before and after submission refreshes

Service Layer

Primary services
  • actions/services/portalDirectService.js

    • sendRepCompleteMessage(...)
    • setRepInvolvment(...)
    • createWatchedCases(...) via portal service export path used in the completion component
  • actions/services/notifyDirectService.js

    • send email helper path used by completion flow
  • actions/services/documentDirectService.js

    • generateRepPDF(...) where applicable in representation flows
  • actions/azurestorage.js

    • createRepCompleteMessage(...)

API Layer

Principal routes
  • finalisation/orchestration

    • pages/api/file/createrepcompletemessage_api.js
    • pages/api/file/createrepinvolvement_api.js
  • adjacent representation mutation/deletion route

    • pages/api/endpoint/deletemyrepresentations_api.js
Route-family classification
  • createrepcompletemessage_api.js

    • queue/finalisation
    • triggers queue-backed representation completion handoff using container, case reference, and rep draft id
  • createrepinvolvement_api.js

    • CRM relay write / orchestration
    • ensures the contact-to-case involvement relationship exists before/around representation completion
  • deletemyrepresentations_api.js

    • CRM relay delete
    • deletes representation records by CRM representation ID

Integration Boundaries

  • NextAuth

    • still the root of container ownership and authenticated portal identity bootstrap
  • CRM via Azure Relay

    • used for representation visibility, involvement creation, and watched/submitted representation side effects
  • Azure Storage

    • source of representation draft JSON/files prior to completion
  • Azure Queue

    • explicit transition boundary through createRepCompleteMessage(...)
  • GOV.UK Notify

    • explicit part of the completion flow via completion email send
  • Local-only processing

    • completion-state guards, questionnaire navigation, and one-time message-sent state handling

Ownership Model

Session identity
→ container identity
→ representation draft ownership
→ completion message route
→ queue handoff
→ CRM involvement / representation side effects

Visible submitted transition:

Representation draft blob
→ create rep complete message
→ Azure Queue message
→ CRM representation/involvement boundary
→ watched/submitted status updates

Architectural Flow

User
→ representation completion component
setRepInvolvment(...)
sendRepCompleteMessage(...)
pages/api/file/createrepcompletemessage_api.js
actions/azurestorage.createRepCompleteMessage(...)
→ Azure Queue
→ email send
→ watched-case/submitted side effect
→ completion UI

Change Entry Set

First files to inspect
  • components/case/representation/representationComplete.js
  • actions/services/portalDirectService.js
  • pages/api/file/createrepcompletemessage_api.js
  • pages/api/file/createrepinvolvement_api.js
  • store/currentView/reducer.js
Adjacent files
  • pages/api/endpoint/deletemyrepresentations_api.js
  • pages/api/endpoint/getmyrepresentations_api.js
  • pages/api/endpoint/getrepresentations_api.js
  • actions/azurestorage.js
  • components/case/representation/*
Highest-risk areas
  • representation involvement sequencing
  • queue handoff for representation completion
  • side effects combined in one completion component
  • representationMessageSent guarding versus repeated side effects
  • watched/submitted representation state mutation after completion

Risk Classification

Very High

Reasoning:

  • multi-integration workflow
  • combines storage, queue, CRM, and notify concerns
  • more client-side side-effect concentration than the appeal completion flow

Appeal vs Representation Lifecycle Comparison

Ownership comparison

  • Shared

    • both begin from NextAuth session.user.id -> container identity
    • both also carry CRM contact identity via account/bootstrap flows
  • Different

    • appeal draft ownership centres on the appeal case reference and draft case blob
    • representation draft ownership centres on an existing case plus a representation draft identity (repfile_name) under the container

Storage comparison

  • Shared

    • both use Azure Storage for pre-submission draft persistence
    • both use signed storage routes and blob helper utilities
  • Different

    • appeal draft flow uses casefolder-based appeal progress blob + case blob + files
    • representation draft flow uses representation draft JSON blobs plus nested representation file subtrees

Queue comparison

  • Shared

    • both use queue-backed completion handoff
    • both have explicit completion-message file routes
  • Different

    • appeals queue handoff packages appeal/case/files paths for submitted application processing
    • representations queue handoff packages representation-specific path and file subtree for submitted representation processing

CRM comparison

  • Shared

    • both eventually transition toward CRM-owned submitted-record outcomes
    • both rely on relay-backed mutation/support routes
  • Different

    • appeal lifecycle is more closely aligned to case creation/update transitions
    • representation lifecycle is more closely aligned to involvement creation and representation submission side effects rather than creating a new appeal case

Maintainability comparison

  • Shared mechanisms

    • session bootstrap
    • storage container ownership
    • signed file routes
    • queue completion message pattern
  • Different maintainability assumptions

    • appeal lifecycle has a clearer draft → submitted application path anchored by case creation/finalisation
    • representation lifecycle has denser client-side state and more completion-side effect coupling in one component
    • representation lifecycle therefore has slightly higher local workflow complexity even though the platform primitives are shared

Ownership Model

Representation draft ownership root

NextAuth session.user.id
→ accountDetails.containerID
→ Azure Storage container
→ representation draft blob set
→ representation file subtree

Representation submitted transition

Representation draft ownership
→ completion message route
→ Azure Queue
→ CRM involvement / representation boundary

Architectural Flows

Draft Representation Creation

User
→ Session
→ Representation loader
→ Redux currentView/accountDetails/myRepresentations
→ Storage draft retrieval / save
→ Azure Storage

Representation Submission / Finalisation

User
→ Representation complete flow
→ involvement creation
→ completion message creation
→ Azure Queue
→ CRM representation boundary
→ Notify / completion UI

Change Entry Sets

Draft Representation Creation

  • start with:
    • pages/myportal/representation.js
    • lib/representation/pageLoaders.js
    • actions/services/documentDirectService.js
    • pages/api/file/{getrepsblob,getrepsblobproxy,deleteblobrep}.js
    • store/currentView/reducer.js
    • store/myRepresentations/reducer.js

Representation Submission / Finalisation

  • start with:
    • components/case/representation/representationComplete.js
    • actions/services/portalDirectService.js
    • pages/api/file/{createrepcompletemessage_api,createrepinvolvement_api}.js
    • pages/api/endpoint/deletemyrepresentations_api.js
    • actions/azurestorage.js

Risk Classification

  • Draft Representation Creation: High
  • Representation Submission / Finalisation: Very High

Investigation Method

Files reviewed for Slice 3

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Journey pages / loaders / components:

  • pages/myportal/representation.js
  • lib/representation/pageLoaders.js
  • components/representation.js
  • components/case/representation/representationComplete.js

State / service / API files:

  • store/currentView/reducer.js
  • store/myRepresentations/reducer.js
  • actions/services/documentDirectService.js
  • actions/services/portalDirectService.js
  • pages/api/file/getrepsblob.js
  • pages/api/file/deleteblobrep.js
  • pages/api/file/createrepcompletemessage_api.js
  • pages/api/file/createrepinvolvement_api.js
  • pages/api/endpoint/getmyrepresentations_api.js
  • pages/api/endpoint/getrepresentations_api.js
  • pages/api/endpoint/deletemyrepresentations_api.js

Searches performed for Slice 3

  • pages: representation|repsblob|repcompletemessage|created=|case=|state=edit
  • lib: loadRepresentationPage|loadExistingRepresentation|loadNewRepresentation|representation|getRepsFromBlob|createRepCompleteMessage|questionnaire|showQuestionnaireSection
  • actions/services: getRepsFromBlob|getRepsFromBlobProxy|sendRepCompleteMessage|setRepInvolvment|getMyRepresentations|getRepresentations|deleteMyRepresentationsFromBlob|generateRepPDF
  • pages/api: getrepsblob|editRepJson|deleteblobrep|createrepcompletemessage_api|createrepinvolvement_api|deletemyrepresentations_api|getmyrepresentations_api|getrepresentations_api
  • store: myRepresentations|representationCapacity|representationSubmit|representationMessageSent|fileList|currentView|accountDetails

Limitations for Slice 3

  • This slice was intentionally limited to representation draft and representation finalisation only.
  • It did not reopen appeal implementation details except where comparison was required.
  • Downstream queue consumers outside this frontend repository were not inspected.
  • No runtime execution or queue-processing verification was performed.
  • No implementation changes, tests, or scripts were run because this remained documentation-only discovery.

Risks / Cautions

  1. Representation draft state is more distributed across currentView flags than the appeal draft lifecycle.
  2. Representation completion currently concentrates several side effects in one completion component, which raises maintenance sensitivity even when behaviour is stable.
  3. Queue creation is visible, but downstream representation processing is not visible in this repository slice.
  4. Comparison conclusions are based on frontend-visible lifecycle behaviour and adjacent API boundaries only.

Validation Performed

  • Re-read all required Slice 3 context files before investigation.
  • Performed non-destructive code reading and targeted searches only.
  • Traced representation lifecycle from:
    • session
    • blob-backed draft ownership
    • completion queue handoff
    • CRM representation/involvement boundary
  • Compared that lifecycle back to the already documented appeal lifecycle.
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Account Registration and Personal Details / Account Management

This would extend the journey map into the identity/bootstrap side of PEDW and complement the already documented draft/submission lifecycles without widening into implementation work.


Slice 4 — Account Registration and Personal Details / Account Management

Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • These two journeys are the clearest maintainer-facing view of the PEDW identity bootstrap model:
NextAuth session
→ session.user.email
→ getPortalLogin(email)
→ CRM Contact
→ portal access / dashboard access
  • Account Registration exists to bridge the gap when a valid NextAuth session exists but getPortalLogin(session.user.email) returns no CRM contact.
  • Personal Details / Account Management depends on that bridge having already succeeded and then operates primarily against a CRM contact identifier stored in Redux.
  • The account journeys therefore strengthen the already documented ownership model by making the session.user.email -> CRM Contact transition explicit, rather than only implicit through dashboard or draft loaders.
  • The reviewed account APIs fit a future journey-based grouping model reasonably well at the architecture level, but several routes still carry historical naming/behaviour patterns that should be treated as future grouping candidates only, not implementation recommendations.

Account Registration Journey Map

Purpose

Business purpose:

  • allows an authenticated user who has signed in successfully but does not yet have a CRM-backed portal account/contact to create that account and become eligible for portal access.

Maintainer purpose:

  • this journey is the clearest place to understand how PEDW turns an authenticated email identity into a CRM Contact record and then into portal eligibility.

Primary Entry Points

  • pages/index.js
  • pages/account/register.js
  • components/account/registerform.js
  • components/account/registerCheck.js
  • components/account/registerComplete.js
  • adjacent auth route:
    • pages/api/auth/[...nextauth].js

Loaders / Initialisation

Signed-in / no-CRM-contact detection
  • pages/index.js
    • uses getServerSideProps
    • requires getSession(ctx) for signed-in portal bootstrap
    • if a session exists:
      • dispatches setContainerID(session.user.id)
      • calls getPortalLogin(session.user.email)
    • client-side branch then decides:
      • if loggedInUserId.value is empty -> redirect to /account/register?id=<encoded email>
      • if CRM contact exists -> set pinsUser cookie and redirect to /myportal
Registration page loader
  • pages/account/register.js
    • requires getSession(ctx)
    • redirects to /auth/signin if missing
    • passes loggedInUserEmail: session.user.email into the page props
Registration form bootstrap
  • components/account/registerform.js
    • uses Redux Form with enableReinitialize
    • seeds initialValues.emailaddress1 from loggedInUserEmail
    • keeps email field disabled, so the current signed-in email remains the registration identity source in the reviewed flow
Post-registration bootstrap
  • registration completion does not itself grant portal access directly
  • visible post-registration bootstrap remains:
User returns through signed-in homepage flow
→ pages/index.js
→ getPortalLogin(session.user.email)
→ CRM contact now exists
→ pinsUser cookie set
→ redirect to /myportal

State Ownership

Primary slices
  • store/accountDetails/reducer.js
    • owns:
      • accountDetails
      • loggedinUserId
      • loggedinUserEmail
      • accCr
      • containerID
Registration-specific state
  • accCr
    • used as the registration completion state marker:
      • false
      • created
      • exists
  • components/account/register.js also uses local component state for form/check/complete progression:
    • registerFormComplete
    • accountCreatedComplete
accountDetails usage
  • registration completion writes account-creation outcome into Redux through setAccCr(...)
  • homepage bootstrap later uses the CRM lookup result, not the registration component state itself, as the source of portal eligibility

Service Layer

Primary service modules
  • actions/services/accountDirectService.js
    • getPortalLogin(emailAddress)
    • createAccount(formValues)
    • getEmailAccountCheck(emailAddress)
Service responsibilities in this journey
  • getPortalLogin(...)
    • confirms whether a signed-in email already maps to a CRM contact
  • getEmailAccountCheck(...)
    • duplicate email/account existence check before create
  • createAccount(...)
    • sends the final CRM contact create request

API Layer

Principal routes
  • pages/api/endpoint/getportallogin_api.js
  • pages/api/endpoint/getemailaccountcheck_api.js
  • pages/api/endpoint/createaccount_api.js
  • adjacent auth route that routes new users toward registration:
    • pages/api/auth/[...nextauth].js
Route-family classification
  • getportallogin_api.js

    • CRM relay lookup
    • signed route
    • strict login/bootstrap lookup by email address
  • getemailaccountcheck_api.js

    • CRM relay lookup
    • duplicate account/email existence check
  • createaccount_api.js

    • CRM relay create
    • creates a CRM contacts record from submitted registration payload
  • pages/api/auth/[...nextauth].js

    • auth/session platform-level
    • responsible for sign-in flow and newUser routing to /account/register

Integration Boundaries

  • NextAuth

    • root of authenticated identity
    • decides whether a user is signed in at all
    • sends new users toward /account/register
  • CRM via Azure Relay

    • contact existence lookup
    • duplicate email check
    • account/contact creation
  • GOV.UK Notify

    • touched indirectly through auth sign-in/verification email route family, not the registration form itself
  • Azure Storage

    • not a primary part of registration itself
    • session container ID may already be set in homepage bootstrap before portal access completes
  • Local-only processing

    • registration step UI state
    • check-details transition
    • account-created state presentation

Ownership Model

NextAuth session
→ session.user.email
→ getPortalLogin(email)
→ no CRM contact found
→ registration flow
→ create CRM contact
→ homepage bootstrap re-check
→ portal access

This journey is therefore the clearest explicit bridge between:

  • authenticated identity
  • CRM contact identity
  • portal eligibility

Architectural Flow

User
→ sign in successfully
pages/index.js bootstrap
getPortalLogin(session.user.email)
→ no CRM contact found
/account/register
→ registration form / check / complete
getEmailAccountCheck(...)
createAccount(...)
→ CRM contacts create
→ later homepage bootstrap re-check
pinsUser cookie + /myportal

Change Entry Set

First files to inspect
  • pages/index.js
  • pages/account/register.js
  • components/account/registerform.js
  • components/account/registerCheck.js
  • components/account/registerComplete.js
  • actions/services/accountDirectService.js
  • pages/api/endpoint/getportallogin_api.js
  • pages/api/endpoint/getemailaccountcheck_api.js
  • pages/api/endpoint/createaccount_api.js
  • pages/api/auth/[...nextauth].js
Likely adjacent files
  • pages/api/auth/resolve-locale.js
  • store/accountDetails/reducer.js
  • store/accountDetails/action.js
Highest-risk areas
  • signed-in/no-CRM-contact detection at homepage bootstrap
  • duplicate email/contact check assumptions
  • registration completion state versus true CRM contact availability
  • newUser routing assumptions in NextAuth

Risk Classification

High

Reasoning:

  • foundational identity bootstrap journey
  • ties auth/session state to portal business identity
  • regressions could block portal entry for legitimate users

Personal Details / Account Management Journey Map

Purpose

Business purpose:

  • allows an authenticated portal user with an existing CRM contact to view and update their personal/account details.

Maintainer purpose:

  • this journey shows how ongoing account management depends on the already-established CRM contact identity and how that contact identity is then reused for account update routes.

Primary Entry Points

  • pages/account/personaldetails.js
  • components/account/personaldetails.js
  • components/account/personaldetailsCheck.js
  • components/account/personaldetailsComplete.js
  • adjacent account UI:
    • components/myportal/youraccount.js
  • legacy/adjacent password flow:
    • pages/account/changepassword.js
    • pages/api/endpoint/updatepassword_api.js

Loaders / Initialisation

Page entry
  • pages/account/personaldetails.js
    • no active SSR account-hydration loader in the reviewed code
    • page is client-side session guarded via useSession():
      • loading -> NoSessionWarning
      • unauthenticated -> redirect to /auth/signin
    • also writes pinsUser cookie from props.accountDetails.loggedinUserId
State hydration assumption
  • this page assumes account identity/details have already been hydrated into Redux by earlier portal bootstrap flows, especially through:
NextAuth session
→ getPortalLogin(session.user.email)
→ CRM contactid
→ getPersonalAccount(contactid)
→ store/accountDetails
Personal details form bootstrap
  • components/account/personaldetails.js
    • uses Redux Form
    • reads initial account values from Redux-backed props rather than loading them fresh inside the page route
    • disabled email field confirms that account management is not treating email as a freely editable identity source in the reviewed UI flow

State Ownership

Primary slices
  • store/accountDetails/reducer.js

    • primary owner for:
      • accountDetails
      • loggedinUserId
      • loggedinUserEmail
      • containerID
  • store/currentView/reducer.js

    • only adjacent here for navigation/breadcrumb context, not the main owner of account state
accountDetails usage
  • loggedinUserId
    • acts as the CRM contact identifier for account update actions
  • accountDetails
    • supplies the current visible account field values
  • loggedinUserEmail
    • supports identity continuity, although the read/update journey itself mostly operates on contact ID plus form payload

Service Layer

Primary service modules
  • actions/services/accountDirectService.js
    • getPersonalAccount(contactid)
    • updateAccount(contactId, updateBody, ssr)
    • getPreferredLanguage(email)
    • updatePassword(contactId, newpassword)

API Layer

Principal routes
  • pages/api/endpoint/getpersonalaccount_api.js
  • pages/api/endpoint/updateaccount_api.js
  • adjacent account-support routes:
    • pages/api/endpoint/getpreferredlanguage_api.js
    • pages/api/endpoint/updatepassword_api.js
Route-family classification
  • getpersonalaccount_api.js

    • CRM relay lookup
    • retrieves account/contact fields by CRM contact ID
  • updateaccount_api.js

    • CRM relay update
    • patches the CRM contact record by contact ID
  • getpreferredlanguage_api.js

    • CRM relay lookup / support route
    • resolves preferred language by email address
  • updatepassword_api.js

    • CRM relay update / historical-adjacent
    • updates a contact record by contact ID
    • relevant mainly as a legacy or alternate account-update path because the live account flow appears to use updateaccount_api.js for password-like updates elsewhere

Integration Boundaries

  • NextAuth

    • required for session/auth gate at page entry
    • not the direct account record store
  • CRM via Azure Relay

    • primary account read/update boundary
  • GOV.UK Notify

    • not a main part of personal-details/account management itself
  • Azure Storage

    • not a main part of account management itself
  • Local-only processing

    • check-details transition
    • completion-state routing
    • preferred-language cookie update (pedw_locale)

Ownership Model

NextAuth session
→ prior portal bootstrap
→ CRM contactid in Redux
→ getPersonalAccount(contactid)
→ account details form
→ updateAccount(contactId, payload)
→ CRM contact update

This journey therefore depends on the identity bridge having already succeeded:

  • registration creates the CRM contact if missing
  • dashboard/bootstrap hydrates it
  • personal details reuses it as the account management key

Architectural Flow

User
/account/personaldetails
useSession() gate
→ Redux accountDetails already present from prior bootstrap
→ personal details form / check state
updateAccount(loggedinUserId, formValues)
pages/api/endpoint/updateaccount_api.js
→ CRM contact patch
→ completion view + locale cookie update

Change Entry Set

First files to inspect
  • pages/account/personaldetails.js
  • components/account/personaldetails.js
  • components/account/personaldetailsCheck.js
  • components/account/personaldetailsComplete.js
  • actions/services/accountDirectService.js
  • pages/api/endpoint/getpersonalaccount_api.js
  • pages/api/endpoint/updateaccount_api.js
  • store/accountDetails/reducer.js
Likely adjacent files
  • pages/api/endpoint/getpreferredlanguage_api.js
  • pages/api/endpoint/updatepassword_api.js
  • components/myportal/youraccount.js
  • pages/api/auth/[...nextauth].js
Highest-risk areas
  • dependence on pre-hydrated Redux account state rather than active SSR loading here
  • contact ID trust between client-held Redux state and final API route
  • preferred-language and locale-cookie side effects
  • legacy/parallel password update path ambiguity

Risk Classification

High

Reasoning:

  • user-critical account data
  • depends on the session-to-CRM-contact bridge remaining coherent
  • mixes present-day account update flow with legacy-adjacent account/password endpoints

Ownership Model

Identity bootstrap model reinforced by account journeys

NextAuth session
→ session.user.email
→ getPortalLogin(email)
→ CRM Contact
→ portal access / dashboard access

How registration fits

Session exists
→ no CRM Contact found
→ registration flow
→ CRM contact creation
→ later bootstrap succeeds

How account management fits

Session exists
→ CRM Contact already known
→ Redux accountDetails hydrated
→ account read/update by contactId

These journeys therefore make the identity model explicit in two phases:

  • registration creates the missing CRM side of the bridge
  • account management depends on and reuses the completed bridge

Future API Grouping Assessment

This section is a future grouping assessment only.

It is not an implementation recommendation.

Current route Journey owner Integration touched Future grouping candidate Migration caution
pages/api/endpoint/getportallogin_api.js account registration / auth bootstrap CRM Relay + signed request integrity candidate for api/account or api/auth used both for registration gating and broader auth/bootstrap; boundary ownership spans account and auth
pages/api/endpoint/getpersonalaccount_api.js personal details / account management CRM Relay candidate for api/account strongly journey-owned by account/profile reads, but also reused by broader portal bootstrap
pages/api/endpoint/createaccount_api.js account registration CRM Relay candidate for api/account clear registration ownership, but current route naming/usage is historical and tied to direct CRM create semantics
pages/api/endpoint/updateaccount_api.js personal details / account management CRM Relay candidate for api/account central account mutation route; caution because other journeys or legacy flows may also reuse it
pages/api/endpoint/getemailaccountcheck_api.js account registration CRM Relay candidate for api/account strong registration fit, but still part of broader bootstrap/identity support checks
pages/api/endpoint/getpreferredlanguage_api.js auth/session and account support CRM Relay candidate for api/shared or api/auth supports locale resolution more broadly than account pages alone
pages/api/endpoint/updatepassword_api.js legacy-adjacent account management CRM Relay unclear / historical relevant to account domain, but reviewed flow suggests legacy or alternate-path status
pages/api/auth/[...nextauth].js auth/session platform NextAuth + Notify + CRM lookup support candidate for api/auth should remain a platform/auth boundary because it owns session and verification flows, not just account registration
pages/api/auth/resolve-locale.js auth/session locale support CRM Relay + session/cookie locale context candidate for api/auth or api/shared mixed support behavior; not purely account-owned despite using account identity lookup

Classification notes

  • candidate for api/account
    • routes whose clearest journey owner is registration or personal-details/account management
  • candidate for api/auth
    • routes whose clearest owner is session/bootstrap/auth flow, even if they consult CRM account identity
  • candidate for api/shared
    • routes that support multiple journey families and are not cleanly owned by one journey alone
  • should remain integration/platform-level
    • routes whose current boundary is more platform/auth than journey-specific
  • unclear / historical
    • routes where the visible live journey ownership is mixed, legacy-shaped, or ambiguous in the reviewed code

Architectural Flows

Account Registration

User
→ NextAuth session exists
→ Homepage bootstrap
→ getPortalLogin(email)
→ no CRM contact
→ Registration page
→ duplicate email check
→ create CRM contact
→ later homepage bootstrap succeeds
→ portal access

Personal Details / Account Management

User
→ Session gate
→ pre-hydrated CRM contact/accountDetails in Redux
→ personal details form
→ updateAccount(contactId, payload)
→ CRM contact update
→ completion state

Change Entry Sets

Account Registration

  • start with:
    • pages/index.js
    • pages/account/register.js
    • components/account/registerform.js
    • components/account/registerCheck.js
    • components/account/registerComplete.js
    • actions/services/accountDirectService.js
    • pages/api/endpoint/{getportallogin_api,getemailaccountcheck_api,createaccount_api}.js
    • pages/api/auth/[...nextauth].js

Personal Details / Account Management

  • start with:
    • pages/account/personaldetails.js
    • components/account/personaldetails.js
    • components/account/personaldetailsCheck.js
    • components/account/personaldetailsComplete.js
    • actions/services/accountDirectService.js
    • pages/api/endpoint/{getpersonalaccount_api,updateaccount_api,getpreferredlanguage_api,updatepassword_api}.js
    • store/accountDetails/reducer.js

Risk Classification

  • Account Registration: High
  • Personal Details / Account Management: High

Investigation Method

Files reviewed for Slice 4

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/portal-api-platform-assessment.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Journey pages / components:

  • pages/index.js
  • pages/account/register.js
  • pages/account/personaldetails.js
  • components/account/registerform.js
  • components/account/registerCheck.js
  • components/account/registerComplete.js
  • components/account/personaldetails.js
  • components/account/personaldetailsCheck.js
  • components/account/personaldetailsComplete.js

Services / state / API files:

  • actions/services/accountDirectService.js
  • store/accountDetails/reducer.js
  • pages/api/endpoint/getportallogin_api.js
  • pages/api/endpoint/getpersonalaccount_api.js
  • pages/api/endpoint/createaccount_api.js
  • pages/api/endpoint/updateaccount_api.js
  • pages/api/endpoint/getemailaccountcheck_api.js
  • pages/api/endpoint/getpreferredlanguage_api.js
  • pages/api/endpoint/updatepassword_api.js
  • pages/api/auth/[...nextauth].js
  • pages/api/auth/resolve-locale.js

Searches performed for Slice 4

  • pages: register|personaldetails|changepassword|youraccount|getServerSideProps|getSession\(|getPortalLogin
  • components/account: register|personaldetails|changepassword|emailaddress1|updateAccount|createAccount|getPersonalAccount
  • actions/services: createAccount|getPortalLogin|getPersonalAccount|updateAccount|getPreferredLanguage|updatePassword|getEmailAccountCheck
  • pages/api: getportallogin_api|getpersonalaccount_api|createaccount_api|updateaccount_api|getemailaccountcheck_api|getpreferredlanguage_api|updatepassword_api|resolve-locale|nextauth
  • store: accountDetails|loggedinUserId|loggedinUserEmail|setAccountDetails|setLoggedInUserId|setLoggedInUserEmail|currentView

Limitations for Slice 4

  • This slice was intentionally limited to registration and account-management journeys only.
  • It did not reopen dashboard, appeal, or representation journeys except where identity/bootstrap continuity required it.
  • The future grouping section is a classification exercise only.
  • No runtime execution or auth-flow verification was performed.
  • No implementation changes, tests, or scripts were run because this remained documentation-only discovery.

Risks / Cautions

  1. Registration completion state and actual portal eligibility are not identical; the visible portal-access transition still depends on a later homepage bootstrap re-check.
  2. Personal details page entry relies on client session gating and pre-hydrated Redux account state more than on active SSR account loading in the reviewed route.
  3. Several account-support APIs participate in both account and auth/session concerns, so future grouping ownership is architectural classification only, not a change proposal.
  4. updatepassword_api.js appears relevant as a legacy or alternate account-update path and should be treated cautiously in grouping assessments.

Validation Performed

  • Re-read all required Slice 4 context files before investigation.
  • Performed non-destructive code reading and targeted searches only.
  • Traced the account identity model from:
    • NextAuth session
    • email-based portal login lookup
    • CRM contact creation/read/update
    • post-registration portal bootstrap
  • Classified future grouping candidates using journey ownership, integration touched, and migration caution only.
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Authentication / Sign-In and Notifications / Email

This would extend the journey map into the cross-cutting auth/communication layer that supports registration, portal entry, and user-facing lifecycle communications without widening into implementation work.


Slice 5 — Authentication / Sign-In and Notifications / Email

Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • Authentication / Sign-In is a cross-cutting support journey centred on pages/api/auth/[...nextauth].js, but its practical architecture also includes:
    • locale pre-resolution in pages/auth/signin.js
    • CRM preferred-language / contact lookup support
    • homepage bootstrap in pages/index.js
    • registration redirect when session exists but CRM contact does not
    • sign-out cleanup via lib/auth/sessionClient.js
  • Notifications / Email is not one single journey shape. It visibly contains two maintainability forms:
    • a thin direct Notify send path via pages/api/email/notify.js
    • a broader aggregation/orchestration path via pages/api/email/getall.js and supporting email data routes
  • GOV.UK Notify is used in two distinct ways:
    • as an auth-support integration for passwordless sign-in emails
    • as a business-notification integration for completion emails and watchlist/batch updates
  • The strongest visible identity bridge across both journeys remains:
    • NextAuth session.user.email -> getPortalLogin(email) -> CRM contact / preferred language
  • This slice does not reopen the completed Authorization Architecture Assessment. The focus here is how auth and email fit into journey architecture, ownership, and maintainer change-entry.

Authentication / Sign-In Journey Map

Purpose

Business purpose:

  • allows a user to start a passwordless sign-in flow, receive a verification email, complete callback handling, establish a session, and then continue into portal bootstrap or registration.

Maintainer purpose:

  • this journey is the clearest cross-cutting entry into authenticated portal behaviour because it joins:
    • sign-in UI
    • locale handling
    • verification-email delivery
    • session establishment
    • CRM contact/bootstrap lookup
    • registration redirect for first-time users

Primary Entry Points

  • pages/auth/signin.js
  • pages/auth/verify-request.js
  • pages/auth/error.js
  • pages/api/auth/[...nextauth].js
  • pages/api/auth/resolve-locale.js
  • homepage bootstrap after callback:
    • pages/index.js
  • sign-out / reset touchpoints directly relevant to auth continuity:
    • components/header.js
    • components/myportal/servicebanner.js
    • lib/auth/sessionClient.js
    • pages/logout.js

Loaders / Initialisation

Sign-in page entry
  • pages/auth/signin.js
    • gated by SHOWLOGIN
    • obtains csrfToken through getCsrfToken(context)
    • builds callback URL from host/protocol and incoming callbackUrl
    • appends locale to the callback URL before rendering
Locale resolution before email sign-in submit
  • pages/auth/signin.js

    • intercepts form submit in handleSubmit(...)
    • POSTs to pages/api/auth/resolve-locale.js with:
      • entered email
      • current UI locale
    • writes pedw_locale cookie
    • rewrites the hidden callback URL to include resolved locale before posting to /api/auth/signin/email
  • pages/api/auth/resolve-locale.js

    • resolves fallback locale from request/body/cookie
    • if email is present, calls getPortalLogin(email)
    • derives locale from CRM pinswg_preferredlanguage when available
    • otherwise falls back to request locale
Verify-request page
  • pages/auth/verify-request.js
    • lightweight page with getCsrfToken(context) only
    • presents the check-email state after verification email request
    • keeps locale continuity through normal Next.js locale routing rather than extra bootstrap
NextAuth session and callback bootstrap
  • pages/api/auth/[...nextauth].js
    • defines the NextAuth boundary via NextAuth(req, res, authOptions(req, res))
    • derives request locale using:
      • direct query/body locale
      • pedw_locale cookie
      • callback URL locale parsing
      • callback cookie fallback
    • resolves effective locale by attempting CRM preferred-language lookup first and request locale second
    • customises:
      • signIn
      • verifyRequest
      • error
      • newUser page paths by locale
    • uses Prisma adapter and database session strategy
    • rewrites external redirects through the locale-aware redirect callback
Verification email generation
  • pages/api/auth/[...nextauth].js
    • EmailProvider.sendVerificationRequest(...)
    • builds localized verification URL
    • selects EN/CY Notify template
    • sends sign-in email through GOV.UK Notify
Post-auth portal bootstrap relationship
  • pages/index.js
    • calls getSession(ctx) during SSR
    • if session exists:
      • stores session.user.id into containerID
      • calls getPortalLogin(session.user.email)
    • if CRM contact exists:
      • writes pinsUser cookie from CRM contactid
      • redirects to /myportal
    • if CRM contact does not exist:
      • redirects to /account/register?id=<hashed email>
Logout / reset behaviour directly relevant to auth continuity
  • lib/auth/sessionClient.js

    • clearSessionArtifacts() clears:
      • localStorage
      • next-auth.csrf-token
      • callback URL cookies
      • pedw_locale
      • pinsUser
    • performPortalSignOut(...) triggers NextAuth signOut(...) with locale-aware callback URL
  • components/header.js

  • components/myportal/servicebanner.js

    • both call performPortalSignOut(locale)
  • pages/logout.js

    • renders logged-out confirmation page
    • resets Redux account state via setLogout() on mount

State Ownership

Primary ownership layers
  • NextAuth session / Prisma-backed persistence

    • owns whether the user is authenticated
    • owns session.user.id
    • owns session.user.email
  • Cookie-level supporting state

    • pedw_locale
      • preserves locale continuity across sign-in and callback
    • pinsUser
      • stores CRM contactid after homepage bootstrap succeeds
    • NextAuth callback cookies
      • preserve callback routing context during sign-in flow
  • store/accountDetails/reducer.js

    • not the owner of authentication itself
    • becomes the owner of post-auth portal identity context after bootstrap:
      • loggedinUserId
      • loggedinUserEmail
      • containerID
      • accountDetails
  • store/currentView/reducer.js

    • participates through locale-related UI context rather than owning session state directly
Ownership note
  • auth ownership is therefore split between:
    • platform auth/session state in NextAuth/Prisma
    • journey continuation state in cookies and homepage/bootstrap redirects
    • portal business identity state after CRM bootstrap succeeds

Service Layer

Primary service modules
  • actions/services/accountDirectService.js
    • getPortalLogin(emailAddress)
    • getPreferredLanguage(email)
Supporting auth helpers
  • lib/auth/sessionClient.js
    • buildSignedOutCallbackUrl(locale)
    • clearSessionArtifacts()
    • performPortalSignOut(...)
Journey role of services
  • getPortalLogin(...)
    • is the main bridge from session email to CRM contact/bootstrap state
  • getPreferredLanguage(...)
    • supports language-sensitive mail or locale decisions in adjacent flows
  • sessionClient
    • centralises sign-out/reset behaviour across public and portal surfaces

API Layer

Principal routes
  • pages/api/auth/[...nextauth].js
  • pages/api/auth/resolve-locale.js
  • adjacent supporting routes directly relevant to auth bootstrap:
    • pages/api/endpoint/getportallogin_api.js
    • pages/api/endpoint/getpreferredlanguage_api.js
Route-family classification
  • pages/api/auth/[...nextauth].js

    • auth/session platform route
    • owns callback, verification-email, redirect, and session behaviour
  • pages/api/auth/resolve-locale.js

    • auth/session support route
    • mixed local utility + CRM lookup for locale selection before sign-in submit
  • pages/api/endpoint/getportallogin_api.js

    • CRM relay lookup / auth bootstrap support
    • resolves portal contact existence and preferred-language-bearing contact summary by email
  • pages/api/endpoint/getpreferredlanguage_api.js

    • CRM relay lookup / shared support
    • resolves preferred language by email

Integration Boundaries

  • NextAuth + Prisma

    • primary authentication/session boundary
    • owns verification token and database session handling
  • GOV.UK Notify

    • sends passwordless sign-in email
  • CRM via Azure Relay

    • used for preferred-language lookup and portal-contact existence lookup
    • touched because sign-in completion alone is not enough for portal bootstrap; CRM identity still determines portal continuity
  • Azure Storage

    • not part of sign-in itself
    • becomes relevant immediately after successful auth because homepage bootstrap sets container ownership from session.user.id
  • Local-only processing

    • callback URL rewriting
    • locale cookie management
    • sign-out artifact clearing
    • post-auth redirect branching

Ownership / Identity Model

User email
→ sign-in request
→ NextAuth verification flow
→ session.user.email + session.user.id
→ getPortalLogin(email)
→ CRM contact found or not found
→ pinsUser cookie / registration redirect
→ portal bootstrap continuation

This journey makes visible three distinct but linked identity layers:

  • authentication identity
    • session.user.email
    • session.user.id
  • business identity
    • CRM contactid from getPortalLogin(email)
  • portal continuity state
    • pinsUser
    • pedw_locale
    • callback URL state

Architectural Flow

User
pages/auth/signin.js
pages/api/auth/resolve-locale.js
/api/auth/signin/email
pages/api/auth/[...nextauth].js
→ GOV.UK Notify verification email
/api/auth/callback/email
→ NextAuth session created
pages/index.js SSR bootstrap
getPortalLogin(session.user.email)
→ CRM contact exists?
→ yes: pinsUser + /myportal
→ no: /account/register

Change Entry Set

First files to inspect
  • pages/auth/signin.js
  • pages/auth/verify-request.js
  • pages/api/auth/[...nextauth].js
  • pages/api/auth/resolve-locale.js
  • pages/index.js
  • lib/auth/sessionClient.js
  • actions/services/accountDirectService.js
  • pages/api/endpoint/getportallogin_api.js
  • pages/api/endpoint/getpreferredlanguage_api.js
Likely adjacent files
  • pages/auth/error.js
  • components/header.js
  • components/myportal/servicebanner.js
  • pages/logout.js
  • pages/account/register.js
  • store/accountDetails/reducer.js
Highest-risk areas
  • locale resolution before and during callback handling
  • verification-email URL rewriting and EN/CY template selection
  • homepage bootstrap distinction between:
    • authenticated session exists
    • CRM contact exists
    • registration required
  • callback URL / redirect continuity
  • sign-out artifact cleanup across session, locale, and cached CRM-contact continuity

Risk Classification

Very High

Reasoning:

  • foundational cross-cutting entry to authenticated portal behaviour
  • combines NextAuth, Notify, CRM lookup, locale continuity, and registration branching
  • regressions can block sign-in, misroute locale, or break portal bootstrap for all authenticated journeys

Notifications / Email Journey Map

Purpose

Business purpose:

  • sends user-facing transactional and update emails including:
    • auth sign-in emails
    • appeal/representation completion emails
    • watchlist and batch update emails

Maintainer purpose:

  • this journey shows how PEDW email behaviour ranges from simple template sends to orchestration routes that collect CRM, document, and event data before building outgoing Notify payloads.

Primary Entry Points

  • direct send entry:
    • pages/api/email/notify.js
  • auth-support email send embedded in:
    • pages/api/auth/[...nextauth].js
  • business completion callers:
    • components/newappeal/complete.js
    • components/case/representation/representationComplete.js
  • watchlist/email-notification signup touchpoints:
    • components/search/searchresults.js
    • components/case/summary.js
  • aggregation/batch routes:
    • pages/api/email/getall.js
    • pages/api/email/getdocuments.js
    • pages/api/email/getevents.js
    • pages/api/email/getmailinglist.js
    • pages/api/email/getcaseref.js

Loaders / Initialisation

Thin direct Notify send
  • actions/services/notifyDirectService.js

    • packages:
      • templateId
      • emailAddress
      • reference
      • personalisation
    • POSTs to /api/email/notify
  • pages/api/email/notify.js

    • validates emailAddress
    • for reference === "PEDW-NEW-CASEREF", optionally resolves CRM preferred language before final template selection
    • sends email via GOV.UK Notify
Completion email callers
  • components/newappeal/complete.js

    • derives EN/CY completion template ID from locale
    • sends completion email from client-side completion effect path
    • uses logged-in user email and case reference personalisation
  • components/case/representation/representationComplete.js

    • derives EN/CY and SIPS/non-SIPS template IDs
    • sends representation completion email
    • does so alongside representation completion side effects:
      • involvement
      • completion message
      • watched-case creation/update
Watchlist / email-notification sign-up relationship
  • components/search/searchresults.js
  • components/case/summary.js
    • selectEmailNotifications(...) creates/updates a watched-case record with pinswg_emailnotifications: true
    • sign-in is prompted when a user attempts the action without the required authenticated/contact context
    • email notification state is therefore primarily represented first in watchlist CRM state, not in Notify state directly
Aggregation / batch notification bootstrap
  • pages/api/email/getall.js

    • fetches watchlist entries with expanded contact data
    • gathers recent documents, SIP events, and representation consultation-period data per watched case
    • groups by contact email
    • builds EN/CY Notify payloads
    • sends outbound case-update emails in batch
  • pages/api/email/getdocuments.js

    • loads recent published documents for an incident
    • enriches results with secure document download links
  • pages/api/email/getevents.js

    • loads SIP record and related SIP events for an incident
  • pages/api/email/getmailinglist.js

    • returns flattened watchlist/contact email data for notification audiences
  • pages/api/email/getcaseref.js

    • returns watchlist entries including watched-case reference and appeal-type context

State Ownership

Primary ownership layers
  • Notify payload state is mostly ephemeral

    • built at send time in route or caller logic
    • not owned by a long-lived Redux slice
  • CRM watchlist state

    • is the strongest durable owner for business-notification intent
    • specifically owns whether pinswg_emailnotifications is enabled for a watched case
  • store/watchedCases/reducer.js

    • owns client-visible watchlist and email-notification status after read/refresh
    • supports search/case/myportal UI refresh after watched-case changes
  • store/accountDetails/reducer.js

    • provides email address and CRM contact identity used by completion email callers and watchlist email-notification mutations
Ownership note
  • email sending itself is not the source of truth. The durable ownership model differs by sub-journey:
    • auth sign-in email -> NextAuth-driven
    • completion email -> completion/orchestration caller-driven
    • watchlist updates -> CRM watchlist state-driven

Service Layer

Primary service modules
  • actions/services/notifyDirectService.js

    • sendEmail(...)
  • actions/services/notifyService.js

    • re-exports sendEmail(...)
  • adjacent service callers:

    • actions/services/accountDirectService.js
      • getPreferredLanguage(...)
    • actions/services/portalDirectService.js
      • completion-message related orchestration routes adjacent to email lifecycle
Journey role of services
  • sendEmail(...)
    • is the main thin abstraction for direct Notify sends from UI-side completion flows
  • account service helpers
    • supply preferred-language or contact context used to shape mail behaviour

API Layer

Principal routes
  • pages/api/email/notify.js
  • pages/api/email/getall.js
  • pages/api/email/getdocuments.js
  • pages/api/email/getevents.js
  • pages/api/email/getmailinglist.js
  • pages/api/email/getcaseref.js
  • auth-support email path also embedded in:
    • pages/api/auth/[...nextauth].js
Route-family classification
  • notify.js

    • direct Notify send route
    • thin send-focused route with small preferred-language exception for new-case-reference mail
  • getall.js

    • notification orchestration / batch route
    • aggregates CRM watchlist, document, event, and consultation-period data before sending
  • getdocuments.js

    • notification-support data route
    • document lookup and link-building for mail payload assembly
  • getevents.js

    • notification-support data route
    • SIP-event lookup for mail payload assembly
  • getmailinglist.js

    • notification-support audience route
    • mailing list flattening over watchlist/contact data
  • getcaseref.js

    • notification-support audience/context route
    • watched-case reference and appeal-type lookup for notification context

Integration Boundaries

  • GOV.UK Notify

    • direct outbound email send boundary for all reviewed notification types
  • CRM via Azure Relay

    • used for:
      • preferred-language lookup
      • watchlist audience retrieval
      • watched-case reference lookup
      • recent documents lookup
      • SIP events lookup
      • representation consultation-period lookup
  • NextAuth

    • not the main owner of business notifications
    • does own the auth sign-in mail use case
  • Azure Storage

    • not directly part of reviewed email routes
    • adjacent completion journeys may touch storage/finalisation before or around completion email send, but email routes themselves remain Notify/CRM-oriented here
  • Local-only processing

    • template selection
    • payload formatting
    • bilingual section-building
    • secure link concatenation for document mail content

Ownership / Identity Model

Account/contact identity
→ email address + preferred language
→ business event or watchlist state
→ Notify payload build
→ GOV.UK Notify send

For watchlist-driven notifications specifically:

CRM watched case
→ pinswg_emailnotifications == true
→ contact email + preferred language
→ case-linked document/event/reps aggregation
→ batch Notify send

Architectural Flow

Direct completion-style send

User completes journey
→ completion component (newappeal or representation)
actions/services/notifyService.sendEmail(...)
pages/api/email/notify.js
→ GOV.UK Notify

Watchlist-driven batch updates

Scheduler / triggered route call
pages/api/email/getall.js
→ watchlist/contact fetch
→ per-case documents/events/reps aggregation
→ bilingual payload shaping
→ GOV.UK Notify batch send

Auth-support sign-in email

User enters email
pages/api/auth/[...nextauth].js
→ localized verification URL build
→ GOV.UK Notify sign-in email

Change Entry Set

First files to inspect
  • pages/api/email/notify.js
  • pages/api/email/getall.js
  • pages/api/email/getdocuments.js
  • pages/api/email/getevents.js
  • pages/api/email/getmailinglist.js
  • pages/api/email/getcaseref.js
  • actions/services/notifyDirectService.js
  • actions/services/notifyService.js
  • components/newappeal/complete.js
  • components/case/representation/representationComplete.js
Likely adjacent files
  • pages/api/auth/[...nextauth].js
  • actions/services/accountDirectService.js
  • components/search/searchresults.js
  • components/case/summary.js
  • store/watchedCases/reducer.js
  • pages/api/documents/download/[id].js
Highest-risk areas
  • template selection and EN/CY parity
  • business-event timing versus email send timing
  • watchlist/contact grouping assumptions in batch notification route
  • document-link and case-link generation inside email payloads
  • mixed responsibility in getall.js across audience retrieval, content aggregation, and send behaviour

Risk Classification

High

Reasoning:

  • user-facing communications with visible side effects
  • includes both simple sends and orchestration-heavy aggregation
  • depends on CRM audience/content correctness and bilingual template continuity

Ownership / Identity Model

Authentication / Sign-In

NextAuth session
→ session.user.email
→ getPortalLogin(email)
→ CRM contact or registration redirect
→ pinsUser cookie
→ portal entry continuity

Notifications / Email

CRM contact / account email
→ preferred language + journey event or watchlist state
→ Notify payload
→ GOV.UK Notify

Combined interpretation

  • Authentication owns the transition from:
    • anonymous or pre-session identity
    • into session identity
    • and then into CRM-backed portal continuity
  • Notifications own the transition from:
    • CRM/account/contact context or journey completion context
    • into outbound user communication
  • The overlap is strongest where sign-in email and preferred-language resolution use the same CRM contact/email model that later business notifications also reuse.

Future API Grouping Assessment

This section is a future grouping assessment only.

It is not an implementation recommendation.

Current route Journey owner Integration touched Future grouping candidate Migration caution
pages/api/auth/[...nextauth].js authentication / sign-in platform NextAuth + Notify + CRM lookup support should remain platform-level core auth/session boundary with callback, redirect, verify-request, and sign-in email behaviour; journey ownership is auth but boundary is platform-critical
pages/api/auth/resolve-locale.js authentication / sign-in support CRM Relay + cookie/request locale candidate for api/auth or api/shared locale helper is auth-adjacent but also behaves like a small shared support route; migration caution because it joins locale and CRM lookup concerns
pages/api/email/notify.js notifications / email direct send GOV.UK Notify + optional CRM language candidate for api/notifications mostly send-focused, but contains preferred-language exception for PEDW-NEW-CASEREF; migration caution because it is not purely transport-only
pages/api/email/getall.js notifications / email batch orchestration GOV.UK Notify + CRM Relay candidate for api/notifications broad orchestration route with audience lookup, aggregation, payload building, and send side effects; migration caution due to mixed responsibilities
pages/api/email/getdocuments.js notifications / email support CRM Relay + secure document-link logic candidate for api/notifications or api/shared supports email aggregation but overlaps with wider document-retrieval concepts; migration caution because route has support-role rather than standalone user journey
pages/api/email/getevents.js notifications / email support CRM Relay candidate for api/notifications or api/shared support route for email payload assembly; migration caution because event data may also be meaningful outside notification use
pages/api/email/getmailinglist.js notifications / email audience support CRM Relay candidate for api/notifications strong notification-audience fit, but still reflects watchlist CRM ownership rather than a standalone notification-owned record source
pages/api/email/getcaseref.js notifications / email audience/context support CRM Relay candidate for api/notifications or api/shared context-support route for watched-case notification assembly; migration caution because ownership overlaps with watched-case domain context
pages/api/endpoint/getpreferredlanguage_api.js auth/email shared support CRM Relay candidate for api/shared supports both sign-in locale resolution and mail-template decisions; journey ownership is shared, so migration caution is primarily boundary ambiguity
pages/api/endpoint/getportallogin_api.js authentication / portal bootstrap CRM Relay + signed request integrity candidate for api/auth or api/account bootstrap identity lookup is used by sign-in continuity and registration branching; migration caution because journey ownership spans auth and account edges

Classification notes

  • candidate for api/auth
    • routes whose clearest journey ownership is sign-in, callback, locale resolution, or portal-auth bootstrap
  • candidate for api/notifications
    • routes whose clearest journey ownership is outbound mail send, audience assembly, or mail payload orchestration
  • candidate for api/account
    • routes whose visible ownership is closer to account/bootstrap identity than to session mechanics alone
  • candidate for api/shared
    • support routes reused across auth and notification concerns
  • should remain platform-level
    • routes whose boundary is fundamentally platform/auth infrastructure rather than a narrow journey slice
  • unclear / historical
    • not the dominant classification for the sampled auth/email routes, but still relevant when route ownership is mixed or legacy-shaped

Auth vs Notification Comparison

Where they are independent

  • Authentication / Sign-In owns:
    • sign-in form entry
    • verify-request page
    • callback handling
    • session creation
    • redirect logic
    • logout/reset continuity
  • Notifications / Email owns:
    • direct Notify sends
    • completion emails
    • watchlist update emails
    • aggregation of documents/events/reps into outbound email content

Where they overlap

  • both use email address as a key continuity field
  • both use CRM preferred-language/contact lookup support
  • both rely on GOV.UK Notify for actual outbound mail delivery in relevant sub-flows
  • both have EN/CY template or locale-sensitive behaviour

Where Notify is used as an auth-support integration

  • passwordless verification email in pages/api/auth/[...nextauth].js
  • localized sign-in-link delivery based on CRM preferred language or request locale fallback

Where Notify is used as a business-notification integration

  • appeal completion emails from components/newappeal/complete.js
  • representation completion emails from components/case/representation/representationComplete.js
  • watchlist / case-update batch sends in pages/api/email/getall.js

Practical maintainer distinction

  • auth email is identity-entry support
  • notification email is business-event communication

Architectural Flows

Authentication / Sign-In

User
→ /auth/signin
→ resolve-locale(email, locale)
→ /api/auth/signin/email
→ NextAuth verification flow
→ GOV.UK Notify sign-in email
→ callback/email verification
→ session created
→ homepage bootstrap
→ getPortalLogin(email)
→ /myportal or /account/register

Notifications / Email

Business event or watchlist state
→ direct send route or aggregation route
→ optional CRM/document/event enrichment
→ EN/CY template selection
→ GOV.UK Notify send
→ user receives portal communication

Change Entry Sets

Authentication / Sign-In

  • start with:
    • pages/auth/signin.js
    • pages/auth/verify-request.js
    • pages/api/auth/[...nextauth].js
    • pages/api/auth/resolve-locale.js
    • pages/index.js
    • lib/auth/sessionClient.js
    • pages/api/endpoint/{getportallogin_api,getpreferredlanguage_api}.js
    • actions/services/accountDirectService.js

Notifications / Email

  • start with:
    • pages/api/email/{notify,getall,getdocuments,getevents,getmailinglist,getcaseref}.js
    • actions/services/{notifyDirectService,notifyService}.js
    • components/newappeal/complete.js
    • components/case/representation/representationComplete.js
    • components/search/searchresults.js
    • components/case/summary.js
    • pages/api/auth/[...nextauth].js for auth-support mail continuity

Risk Classification

  • Authentication / Sign-In: Very High
  • Notifications / Email: High

Investigation Method

Files reviewed for Slice 5

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/portal-api-platform-assessment.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Guardrails/context discipline:

  • .clinerules/refactor-branch-rules.md
  • GUARDRAILS.md

Journey pages / components / helpers:

  • pages/auth/signin.js
  • pages/auth/verify-request.js
  • pages/index.js
  • pages/logout.js
  • pages/account/register.js
  • components/header.js
  • components/myportal/servicebanner.js
  • components/newappeal/complete.js
  • components/case/representation/representationComplete.js
  • components/search/searchresults.js
  • components/case/summary.js
  • lib/auth/sessionClient.js
  • actions/services/accountDirectService.js
  • actions/services/notifyDirectService.js
  • actions/services/notifyService.js

API files:

  • pages/api/auth/[...nextauth].js
  • pages/api/auth/resolve-locale.js
  • pages/api/email/notify.js
  • pages/api/email/getall.js
  • pages/api/email/getdocuments.js
  • pages/api/email/getevents.js
  • pages/api/email/getmailinglist.js
  • pages/api/email/getcaseref.js
  • pages/api/endpoint/getpreferredlanguage_api.js
  • pages/api/endpoint/getportallogin_api.js

Searches performed for Slice 5

  • pages: getServerSideProps|getSession\(|signIn\(|signOut\(|getCsrfToken\(|verify-request|nextauth|resolve-locale|logout
  • actions/services: notify|getPortalLogin|getPreferredLanguage|send.*email|create.*message
  • pages/api: NotifyClient|sendEmail|NextAuth|EmailProvider|verification|callback|session|getall|getdocuments|getevents|getmailinglist|getcaseref
  • lib: auth|session|locale|preferredLanguage|getPortalLogin
  • components: logout|sign in|verify|email|notify|preferred language

Limitations for Slice 5

  • This slice was intentionally limited to auth/sign-in and notifications/email only.
  • It did not reopen the completed Authorization Architecture Assessment.
  • It did not reassess exploitability, security posture, or ownership risk.
  • It traced submission, representation, registration, watchlist, and portal pages only where needed to explain auth/email touchpoints.
  • The future grouping section is a classification exercise only and not an implementation recommendation.
  • No runtime execution, mail send, or sign-in flow testing was performed.

Risks / Cautions

  1. Authentication / Sign-In and Notifications / Email are both cross-cutting, so their practical ownership spans page, API, integration, and bootstrap boundaries rather than one narrow folder.
  2. pages/api/auth/[...nextauth].js includes both platform-auth behaviour and Notify-backed email behaviour; its future grouping candidate should therefore be read as architectural classification only.
  3. pages/api/email/getall.js is materially more orchestration-heavy than pages/api/email/notify.js, so “notifications/email” is not one uniform route shape.
  4. getpreferredlanguage_api.js and getportallogin_api.js support both auth and email journeys; their future grouping candidates are shared/auth/account classifications only, not an implementation recommendation.
  5. Watchlist email behaviour is partly owned by CRM watchlist state (pinswg_emailnotifications) rather than by the Notify send layer alone.

Validation Performed

  • Confirmed the required Slice 5 context files were read.
  • Performed non-destructive code reading and targeted searches only.
  • Verified the next journey-map section followed the same maintainability format as prior slices.
  • Traced auth flow from sign-in page through locale resolution, NextAuth callback/session handling, homepage bootstrap, and registration redirect continuity.
  • Traced email flow across direct Notify sends, auth sign-in email, completion emails, and watchlist/batch aggregation routes.
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Watchlist / Subscriptions and Unsubscribe Flows

This would extend the journey map into the cross-cutting subscription lifecycle that connects case pages, search results, my portal state, CRM watchlist records, email-notification intent, and unsubscribe routes without widening into implementation work.


Slice 6 — Watchlist / Subscriptions and Unsubscribe / Watchlist Removal

Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • The watchlist/subscription architecture is a cross-cutting portal support journey built around a visible CRM relationship model:
CRM Contact
↔ Watched Case
  • The same watched-case relationship supports several visible behaviours at once:
    • case watching
    • dashboard visibility
    • search-results visibility
    • case-summary visibility
    • email-notification participation through pinswg_emailnotifications
  • Watchlist creation and removal are not isolated to one page. They are triggered from:
    • search results
    • case summary
    • my portal top-three cards
    • my portal view-all lists
    • dedicated unsubscribe pages for email-only removal
  • Dashboard watchlist viewing is not an independent data model. It is a dashboard projection over watched-case CRM retrieval plus additional detail enrichment.
  • Notification participation is visibly a property of the watched-case relationship rather than a separate subscription entity in the reviewed frontend code.
  • This slice does not reopen the authorization assessment. It documents only visible watchlist/subscription architecture and ownership behaviour.

Watchlist Creation Journey Map

Purpose

Business purpose:

  • allows a signed-in portal user to mark a case as watched so that it appears in their portal context and can later participate in email-notification flows.

Maintainer purpose:

  • this journey is the clearest entry into the visible watched-case relationship architecture because it shows how PEDW creates or updates a CRM relationship between:
    • portal contact
    • watched case
    • optional email-notification participation

Primary Entry Points

  • components/search/searchresults.js
  • components/search/addresssearchresults.js
  • components/search/dnssearchresults.js
  • components/case/summary.js
  • adjacent authenticated search routes that preload watched-case state for the above components:
    • pages/myportal/searchresults.js
    • pages/myportal/addresssearchresults.js
    • pages/myportal/advancedsearchresults.js

Loaders / Initialisation

Signed-in watched-case availability in portal search flows
  • pages/myportal/searchresults.js

    • resolves session and CRM contact identity
    • loads watched cases via getWatchedCases(loggedInUser)
    • derives watchedCasesDetails via getDetails(...)
    • dispatches:
      • setWatchedCases(...)
      • setWatchedCasesDetails(...)
      • setLoggedInUserId(...)
      • setAccountDetails(...)
  • pages/myportal/addresssearchresults.js

  • pages/myportal/advancedsearchresults.js

    • perform equivalent portal bootstrap for watched-case state before rendering search-style results pages in myportal context
Watch action branch in results and case summary
  • components/search/searchresults.js
  • components/case/summary.js
    • expose selectWatchedCase(loggedInUser, incidentID, appealType)
    • construct watched-case relationship payload using:
      • pinswg_WatchedCase@odata.bind
      • pinswg_Contact@odata.bind
      • pinswg_appealcasetype
    • call createWatchedCases(updateBody)
    • refresh watched-case state after mutation using:
      • getWatchedCasesProxy(...)
      • getDetailsProxy(..., "myWatchedCases")
Notification-enabled creation branch
  • components/search/searchresults.js
  • components/case/summary.js
    • expose selectEmailNotifications(...)
    • create the same watched-case relationship with one additional visible field:
      • pinswg_emailnotifications: true
    • this means initial subscription signup is visibly implemented as watched-case upsert, not a separate notification-only create route

State Ownership

Primary slices
  • store/watchedCases/reducer.js

    • owns:
      • watchedCases
      • watchedCasesDetails
  • store/accountDetails/reducer.js

    • provides:
      • loggedinUserId
      • accountDetails.contactid
      • accountDetails.emailaddress1
    • these values are used to create the watched-case relationship and optional email-notification participation
  • store/currentView/reducer.js

    • participates in preserving origin/view context when navigating into watched cases or back into myportal list views
Ownership note
  • creation-state ownership is therefore split between:
    • contact identity in accountDetails
    • watched-case list/read model in watchedCases
    • view context in currentView

Service Layer

Primary service modules
  • actions/services/portalDirectService.js
    • createWatchedCases(formValues)
    • getWatchedCases(loggedInUserId)
    • getWatchedCasesProxy(loggedInUserId)
Journey role of services
  • createWatchedCases(...)
    • is the main visible watched-case upsert entry
  • getWatchedCases(...) and getWatchedCasesProxy(...)
    • are used immediately after mutation to refresh portal-visible state

API Layer

Principal routes
  • pages/api/endpoint/createwatchedcases_api.js
  • adjacent read routes used immediately after create:
    • pages/api/endpoint/getwatchedcases_api.js
    • pages/api/endpoint/getwatchedcasesproxy_api.js
Route-family classification
  • createwatchedcases_api.js

    • CRM relationship upsert / orchestration route
    • derives watched case id and contact id from odata bind payload
    • checks for an existing relationship first
    • patches an existing watchlist record or creates a new one
  • getwatchedcases_api.js

    • CRM relationship read
    • retrieves watched cases by contact ownership
  • getwatchedcasesproxy_api.js

    • CRM relationship read / proxy variant
    • returns a closely related watched-case read model for refresh and portal display support

Integration Boundaries

  • CRM via Azure Relay

    • primary watched-case relationship store
    • handles relationship create/read/update behavior
  • NextAuth

    • not the route-local owner of watch creation itself
    • but is the upstream identity root used to establish the CRM contact before watched-case actions become available
  • GOV.UK Notify

    • not directly touched during watch creation itself
    • but pinswg_emailnotifications visibly links the created relationship into later notification participation
  • Local-only processing

    • JSONPath watched/unwatched state checks in components
    • post-mutation refresh of Redux state
    • conditional watch/watch-email button rendering

Ownership Model

CRM Contact
→ watched-case payload bindings
→ createWatchedCases
→ CRM watchlist record exists or is created
→ watchedCases Redux refresh

Visible fields used to represent the relationship include:

  • pinswg_WatchedCase@odata.bind
  • pinswg_Contact@odata.bind
  • pinswg_appealcasetype
  • optionally pinswg_emailnotifications

Architectural Flow

User
→ search results or case summary watch action
selectWatchedCase(...) or selectEmailNotifications(...)
actions/services/portalDirectService.createWatchedCases(...)
pages/api/endpoint/createwatchedcases_api.js
→ CRM pinswg_watchlists create/patch
getWatchedCasesProxy(...) refresh
→ Redux watchedCases + watchedCasesDetails

Change Entry Set

First files to inspect
  • components/search/searchresults.js
  • components/search/addresssearchresults.js
  • components/search/dnssearchresults.js
  • components/case/summary.js
  • actions/services/portalDirectService.js
  • pages/api/endpoint/createwatchedcases_api.js
  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/endpoint/getwatchedcasesproxy_api.js
  • store/watchedCases/reducer.js
Likely adjacent files
  • pages/myportal/searchresults.js
  • pages/myportal/addresssearchresults.js
  • pages/myportal/advancedsearchresults.js
  • store/accountDetails/reducer.js
  • store/currentView/reducer.js
Highest-risk areas
  • relationship upsert behaviour in createwatchedcases_api.js
  • immediate post-create refresh assumptions
  • component-level JSONPath watched/not-watched checks
  • dual use of the same create route for both watch and email-notification signup

Risk Classification

High

Reasoning:

  • cross-cutting relationship creation used from multiple entry points
  • state refresh must stay aligned across search, case, and portal contexts
  • the same relationship underpins later dashboard and notification behaviour

Watchlist Viewing Journey Map

Purpose

Business purpose:

  • allows a portal user to see watched cases in their dashboard and related portal list views.

Maintainer purpose:

  • this journey shows how watched-case CRM records are read, filtered, classified, enriched, and then displayed across myportal and adjacent signed-in search/case contexts.

Primary Entry Points

  • pages/myportal/index.js
  • components/myportal/watchedcases.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js
  • adjacent portal search pages that preload watched cases for watch/unwatch controls:
    • pages/myportal/searchresults.js
    • pages/myportal/addresssearchresults.js
    • pages/myportal/advancedsearchresults.js

Loaders / Initialisation

Dashboard bootstrap
  • pages/myportal/index.js
    • resolves session and CRM contact identity
    • loads watched cases via getWatchedCases(loggedInUser)
    • classifies results with splitWatchedCasesBySubmissionState(watchedCases.value) into:
      • watchedCases
      • submittedRepresentations
    • enriches watched cases with getDetails(..., "myWatchedCases")
    • dispatches:
      • setWatchedCases(filteredWatchedCases)
      • setWatchedCasesDetails(watchedCasesDetails)
Watched-cases dashboard card
  • components/myportal/watchedcases.js
    • renders the watched-cases card
    • delegates list/card rendering to TopThree
    • sends users to /myportal/viewall?key=watchedCases with setCurrentView({ viewName: "Watched Cases", viewKey: "watchedCases" })
Portal view-all bootstrap
  • components/myportal/viewall.js
    • treats currentViewKey === "watchedCases" as one of the main list modes
    • uses:
      • props.watchedCases.watchedCases
      • props.watchedCases.watchedCasesDetails
    • sets watched cases into search/detail state when navigating deeper into a case from this list
Portal search viewing support
  • pages/myportal/searchresults.js
  • pages/myportal/addresssearchresults.js
  • pages/myportal/advancedsearchresults.js
    • preload watched-case state to support portal-context watch/unwatch controls inside results pages

State Ownership

Primary slices
  • store/watchedCases/reducer.js

    • durable view-state owner for:
      • watched-case read model
      • watched-case details enrichment model
  • store/currentView/reducer.js

    • records whether the active dashboard/list context is:
      • watchedCases
    • preserves navigation back into view-all and case contexts
  • store/searchOutput/reducer.js

    • is temporarily reused by viewall.js when a watched-case item is opened via case-detail navigation
Ownership note
  • watchlist viewing is not a separate standalone state store. It is a combination of:
    • watched-case list state
    • watched-case detail enrichment
    • dashboard/view context

Service Layer

Primary service modules
  • actions/services/portalDirectService.js

    • getWatchedCases(...)
    • getWatchedCasesProxy(...)
  • actions/services/caseDirectService.js

    • getPortalModuleDetails(...)
    • used indirectly for watched-case detail enrichment
Supporting domain helper
  • lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js
    • separates plain watched cases from submitted representation-related records

API Layer

Principal routes
  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/endpoint/getwatchedcasesproxy_api.js
Route-family classification
  • getwatchedcases_api.js

    • CRM relationship read
    • filters pinswg_watchlists by _pinswg_contact_value eq loggedInUserId
    • selects visible watchlist fields including:
      • pinswg_emailnotifications
      • pinswg_watchlistid
      • _pinswg_watchedcase_value
      • submission/representation-related fields
  • getwatchedcasesproxy_api.js

    • CRM relationship read / proxy variant
    • similar relationship retrieval with a slightly different selected field set

Integration Boundaries

  • CRM via Azure Relay

    • primary watched-case retrieval boundary
  • NextAuth

    • upstream identity root used to determine which CRM contacts watched cases are loaded
  • Local-only processing

    • classification of watched cases vs submitted representations
    • sorting, detail enrichment, and view-all routing

Ownership Model

CRM Contact
→ getWatchedCases(contactId)
→ CRM watchlist rows
→ splitWatchedCasesBySubmissionState
→ detail enrichment
→ dashboard / view-all projection

Architectural Flow

User
pages/myportal/index.js
getWatchedCases(loggedInUser)
pages/api/endpoint/getwatchedcases_api.js
→ CRM watchlist rows
splitWatchedCasesBySubmissionState(...)
getPortalModuleDetails(...) enrichment
→ Redux watchedCases + watchedCasesDetails
→ dashboard card / top-three / view-all

Change Entry Set

First files to inspect
  • pages/myportal/index.js
  • components/myportal/watchedcases.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js
  • actions/services/portalDirectService.js
  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/endpoint/getwatchedcasesproxy_api.js
  • store/watchedCases/reducer.js
Likely adjacent files
  • lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js
  • pages/myportal/searchresults.js
  • pages/myportal/addresssearchresults.js
  • pages/myportal/advancedsearchresults.js
  • store/currentView/reducer.js
Highest-risk areas
  • watched-case classification versus submitted-representation classification
  • enrichment fan-out through portal module details
  • reuse of watched-case state in view-all and case-detail navigation contexts

Risk Classification

High

Reasoning:

  • dashboard-critical signed-in journey
  • watched-case state is reused in several components and contexts
  • visible relationship between dashboard and watchlist ownership is strong and cross-cutting

Watchlist Removal Journey Map

Purpose

Business purpose:

  • allows a user to stop watching a case, removing it from portal watchlist views and related watch-state controls.

Maintainer purpose:

  • this journey shows how removal uses the same watched-case CRM relationship record as creation/viewing, and how portal state is refreshed after deletion.

Primary Entry Points

  • components/search/searchresults.js
  • components/search/addresssearchresults.js
  • components/search/dnssearchresults.js
  • components/case/summary.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js

Loaders / Initialisation

Removal in search and case contexts
  • components/search/searchresults.js
  • components/case/summary.js
    • use deleteItem(caseID, "watchedCases")
    • call deleteWatchedCases(caseID)
    • refresh watched cases via getWatchedCasesProxy(...)
    • rehydrate watchedCases and watchedCasesDetails
Removal in dashboard card/view-all contexts
  • components/myportal/topthree.js
  • components/myportal/viewall.js
    • also use deleteWatchedCases(...)
    • refresh and reclassify watched cases through:
      • getWatchedCasesProxy(...)
      • splitWatchedCasesBySubmissionState(...)
      • getDetailsProxy(..., "myWatchedCases")

State Ownership

Primary slices
  • store/watchedCases/reducer.js

    • is rewritten after every successful remove flow
  • store/currentView/reducer.js

    • retains watched-cases list context during myportal list refreshes

Service Layer

Primary service modules
  • actions/services/portalDirectService.js
    • deleteWatchedCases(watchedCaseID)
    • getWatchedCasesProxy(loggedInUserId)

API Layer

Principal routes
  • pages/api/endpoint/deletewatchedcases_api.js
  • pages/api/endpoint/deletewatchedcasesproxy_api.js
Route-family classification
  • deletewatchedcases_api.js

    • CRM relationship delete
    • deletes a pinswg_watchlists(<watchedCaseID>) record
  • deletewatchedcasesproxy_api.js

    • proxy delete wrapper
    • forwards watched-case deletion through local endpoint routing

Integration Boundaries

  • CRM via Azure Relay

    • primary delete boundary for watched-case relationship removal
  • NextAuth / cached CRM identity context

    • upstream source of the watched-case ids exposed to portal UI flows
  • Local-only processing

    • list refresh
    • classification refresh
    • removal confirmation prompts

Ownership Model

Watched-case record id
→ deleteWatchedCases(watchedCaseID)
→ CRM watchlist record delete
→ watchedCases Redux refresh

Architectural Flow

User
→ unwatch action in search/case/dashboard/view-all
actions/services/portalDirectService.deleteWatchedCases(...)
pages/api/endpoint/deletewatchedcases_api.js
→ CRM watchlist record delete
getWatchedCasesProxy(...)
→ refreshed Redux watched-case state

Change Entry Set

First files to inspect
  • components/search/searchresults.js
  • components/case/summary.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js
  • actions/services/portalDirectService.js
  • pages/api/endpoint/deletewatchedcases_api.js
  • pages/api/endpoint/deletewatchedcasesproxy_api.js
  • store/watchedCases/reducer.js
Likely adjacent files
  • pages/api/endpoint/getwatchedcasesproxy_api.js
  • lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js
  • store/currentView/reducer.js
Highest-risk areas
  • refresh behaviour after delete across multiple UI surfaces
  • reuse of watched-case ids between UI and delete route
  • view-all state continuity after record removal

Risk Classification

High

Reasoning:

  • removal is available from multiple user-facing surfaces
  • stale state or refresh drift can break dashboard/search/case consistency
  • same relationship powers watch visibility and notification participation

CRM Relationship Ownership Model

Visible relationship model

CRM Contact
↔ Watched Case

Relationship entities used

  • visible relationship entity:
    • pinswg_watchlists
  • visible linked fields include:
    • pinswg_watchlistid
    • _pinswg_contact_value
    • _pinswg_watchedcase_value
    • pinswg_emailnotifications
    • pinswg_appealcasetype
    • pinswg_representationsubmitted
    • pinswg_representationtype

Retrieval pattern

  • read by CRM contact ownership:
pinswg_watchlists
→ filter _pinswg_contact_value eq loggedInUserId
→ expand pinswg_WatchedCase
→ flatten watched-case details for portal use

Creation pattern

  • create/upsert path in createwatchedcases_api.js:
payload contains pinswg_WatchedCase@odata.bind + pinswg_Contact@odata.bind
→ extract incidentId/contactId
→ lookup existing relationship in pinswg_watchlists
→ patch existing record or post new record

Deletion pattern

  • delete by relationship record id:
watchedCaseID
→ pinswg_watchlists(watchedCaseID)
→ CRM delete

Ownership interpretation

  • the visible durable owner is not a separate portal subscription table in frontend state.
  • instead the CRM watchlist relationship record is the main persistent ownership unit joining:
    • contact
    • case
    • notification participation flag

Future API Grouping Assessment

This section is a future grouping assessment only.

It is not an implementation recommendation.

Current route Journey owner Integration touched Future grouping candidate Migration caution
pages/api/endpoint/getwatchedcases_api.js watchlist viewing CRM Relay candidate for api/watchlist core read model for dashboard and portal watch visibility; migration caution because current payload is reused across several UI contexts
pages/api/endpoint/getwatchedcasesproxy_api.js watchlist viewing / refresh support CRM Relay candidate for api/watchlist or api/shared proxy variant is tightly coupled to refresh behaviour and historical route usage; migration caution because caller expectations may differ
pages/api/endpoint/createwatchedcases_api.js watchlist creation / subscription upsert CRM Relay candidate for api/watchlist handles both create and patch behaviour plus notification-participation flag updates; migration caution because it is not create-only
pages/api/endpoint/deletewatchedcases_api.js watchlist removal CRM Relay candidate for api/watchlist central relationship delete route used from multiple surfaces; migration caution because many flows assume current watchedCaseID semantics
pages/api/endpoint/deletewatchedcasesproxy_api.js watchlist removal proxy support Local proxy + CRM Relay candidate for api/watchlist or api/shared wrapper route reflects historical forwarding boundary; migration caution because path and caller behaviour are support-shaped rather than journey-pure

Classification notes

  • candidate for api/watchlist
    • routes whose clearest visible owner is watched-case relationship creation, retrieval, or deletion
  • candidate for api/shared
    • proxy/support variants whose behaviour is coupled to refresh or forwarding patterns rather than one pure journey step

Watchlist vs Dashboard Comparison

Shared dependencies

  • both depend on:
    • getPortalLogin(session.user.email) bootstrap upstream
    • watched-case CRM retrieval
    • getPortalModuleDetails(...) detail enrichment
    • splitWatchedCasesBySubmissionState(...) when dashboard classification is involved

Shared state

  • both use:
    • store/watchedCases.reducer.js
    • store/currentView.reducer.js
    • store/accountDetails.reducer.js

Shared APIs

  • both directly or indirectly depend on:
    • getwatchedcases_api.js
    • getwatchedcasesproxy_api.js
    • createwatchedcases_api.js
    • deletewatchedcases_api.js

Ownership relationship

  • dashboard is a projection/consumer of watched-case ownership, not a separate watchlist owner
  • watchlist journey owns the CRM relationship lifecycle
  • dashboard journey owns the signed-in card/list presentation of that relationship

Architectural Flows

Watchlist Creation

User
→ search results / case summary watch action
→ createWatchedCases(payload)
→ createwatchedcases_api
→ CRM watchlist create/patch
→ getWatchedCasesProxy
→ watchedCases Redux refresh

Watchlist Viewing

User
→ myportal bootstrap
→ getWatchedCases(contactId)
→ CRM watchlist retrieval
→ splitWatchedCasesBySubmissionState
→ detail enrichment
→ dashboard card / top-three / view-all

Watchlist Removal

User
→ unwatch action or unsubscribe path
→ deleteWatchedCases(watchedCaseID)
→ CRM watchlist delete
→ watchedCases refresh or unsubscribe confirmation page

Change Entry Sets

Watchlist Creation

  • start with:
    • components/search/searchresults.js
    • components/search/addresssearchresults.js
    • components/search/dnssearchresults.js
    • components/case/summary.js
    • actions/services/portalDirectService.js
    • pages/api/endpoint/{createwatchedcases_api,getwatchedcases_api,getwatchedcasesproxy_api}.js
    • store/watchedCases/reducer.js

Watchlist Viewing

  • start with:
    • pages/myportal/index.js
    • components/myportal/watchedcases.js
    • components/myportal/topthree.js
    • components/myportal/viewall.js
    • pages/api/endpoint/{getwatchedcases_api,getwatchedcasesproxy_api}.js
    • lib/domain/dashboard-policy/splitWatchedCasesBySubmissionState.js
    • store/watchedCases/reducer.js

Watchlist Removal

  • start with:
    • components/search/searchresults.js
    • components/case/summary.js
    • components/myportal/topthree.js
    • components/myportal/viewall.js
    • actions/services/portalDirectService.js
    • pages/api/endpoint/{deletewatchedcases_api,deletewatchedcasesproxy_api}.js
    • pages/unsubscribe/[watchlistid].js
    • pages/unsubscribeall/[watchlistid].js

Risk Classification

  • Watchlist Creation: High
  • Watchlist Viewing: High
  • Watchlist Removal: High

Investigation Method

Files reviewed for Slice 6

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/portal-api-security-boundary-assessment.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Journey pages / components / services / state:

  • pages/myportal/index.js
  • pages/myportal/searchresults.js
  • pages/myportal/addresssearchresults.js
  • pages/myportal/advancedsearchresults.js
  • pages/unsubscribe/[watchlistid].js
  • pages/unsubscribeall/[watchlistid].js
  • components/search/searchresults.js
  • components/search/addresssearchresults.js
  • components/search/dnssearchresults.js
  • components/case/summary.js
  • components/myportal/watchedcases.js
  • components/myportal/topthree.js
  • components/myportal/viewall.js
  • actions/services/portalDirectService.js
  • store/watchedCases/reducer.js
  • store/watchedCases/action.js

API files:

  • pages/api/endpoint/getwatchedcases_api.js
  • pages/api/endpoint/getwatchedcasesproxy_api.js
  • pages/api/endpoint/createwatchedcases_api.js
  • pages/api/endpoint/deletewatchedcases_api.js
  • pages/api/endpoint/deletewatchedcasesproxy_api.js
  • pages/api/email/getall.js
  • pages/api/email/getmailinglist.js
  • pages/api/email/getcaseref.js

Searches performed for Slice 6

  • pages: unsubscribe|watchlist|watchedcases|getWatchedCases|createWatchedCases|deleteWatchedCases
  • components: selectWatchedCase|selectEmailNotifications|deleteItem|watchedCases|unsubscribe|send-email-notifications|stop-sending-email-notifications
  • actions/services: getWatchedCases|createWatchedCases|deleteWatchedCases|getWatchedCasesProxy|watchlist
  • pages/api: getwatchedcases|createwatchedcases|deletewatchedcases|unsubscribe|watchlist|pinswg_emailnotifications
  • store: watchedCases|watchedCasesDetails|setWatchedCases|setWatchedCasesDetails|setCurrentView

Limitations for Slice 6

  • This slice was intentionally limited to watched-case/subscription and unsubscribe/removal architecture only.
  • It did not reopen the security assessment beyond reusing already-established ownership context.
  • It did not speculate beyond visible fields, flows, and routes in the codebase.
  • It did not propose route redesign, state redesign, or ownership redesign.
  • No runtime execution or unsubscribe-flow testing was performed.

Risks / Cautions

  1. The watched-case relationship is used for both watch visibility and notification participation, so changes in one part of the journey can affect multiple user-visible surfaces.
  2. createwatchedcases_api.js behaves as an upsert route rather than a simple create route, which is important for maintainers tracing watch vs email-subscription behaviour.
  3. Dashboard watchlist displays are projections over watched-case CRM data and portal detail enrichment rather than a separate owned dashboard record set.
  4. Dedicated unsubscribe pages implement removal through direct CRM watchlist queries and deletes rather than the same portal service helpers used in signed-in UI flows.
  5. Proxy and non-proxy watched-case routes coexist, so journey ownership is clearer than folder ownership.

Validation Performed

  • Confirmed the required Slice 6 context files were read.
  • Performed non-destructive code reading and targeted searches only.
  • Reused the established watched-cases route family context from the API route map and ownership assessment without reopening exploitability analysis.
  • Traced creation, viewing, removal, unsubscribe, and notification-participation touchpoints across pages, components, services, store, and API handlers.
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Documents / Published Document Retrieval and Download

This would extend the journey map into the public-and-portal document access lifecycle that connects case/search document visibility, document metadata retrieval, download routing, and hash-link usage without widening into implementation work.


Slice 7 — Published Document Discovery and Published Document Retrieval / Download

Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • The visible published-document architecture is split into two linked but distinct maintainability shapes:
    • Published Document Discovery is primarily a case-detail presentation journey backed by document metadata reads from the search/document endpoint family.
    • Published Document Retrieval / Download is a dedicated download-proxy journey backed by a separate document-delivery route under pages/api/documents/download/[id].js.
  • The strongest visible entry path is:
    • public search result
    • case detail navigation
    • case documents panel
    • document metadata retrieval
    • per-document hash-link download
  • The clearest visible metadata source is CRM document data queried through the relay-backed pinswg_documents family.
  • The clearest visible delivery mechanism is:
    • metadata route generates pinswg_hashlink
    • browser fetches /api/documents/download/[id]?hash=...
    • download proxy streams the relay-backed binary response to the browser
  • Document history routes are present and classified in the API layer, but they were not surfaced by the reviewed case-detail UI path in this slice.

Published Document Discovery Journey Map

Purpose

Business purpose:

  • allows a user to discover published case documents, review document names, document type labels, and publish dates, and navigate from a case view into downloadable published records.

Maintainer purpose:

  • this journey shows how PEDW presents published-document metadata on case detail pages, including filter, sort, pagination, and document-type grouping behaviour without directly exposing CRM or relay details in the UI layer.

Primary Entry Points

  • components/search/searchresults.js
  • pages/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • components/case/documents.js

Loaders / Initialisation

Search-to-case transition
  • components/search/searchresults.js
    • sets currentReference before navigating to the case route
    • establishes the visible search-to-document navigation handoff through the case journey rather than a dedicated document page
Case page loader
  • pages/case/[ticketnumber].js
    • bootstraps the case route via getBasicSearch(developmentQuery)
    • expands case details via getSearchDetails(searchResultsObj)
    • passes document-related runtime flags to the case page:
      • docsOffline
      • showFilteredDocs
    • does not SSR-hydrate document metadata itself
Document metadata bootstrap in case UI
  • components/case/documents.js
    • is the main visible document-discovery loader for the reviewed journey
    • on mount / dependency changes, calls:
      • getSearchDocumentTypes(incidentid)
      • getSearchDocumentDetails(incidentid)
      • getSearchDocumentDetailsPaged(...)
    • populates Redux document state through setDocumentDetails(...)
    • derives document-availability UI from docsOffline via getDocLink(docsOffline)

State Ownership

Primary slices
  • store/searchOutput/reducer.js

    • owns documentDetailsObj
    • this is the primary visible read model for case-document presentation
  • store/currentView/reducer.js

    • owns currentPage
    • participates in document pagination state continuity
Document-discovery state in component layer
  • components/case/documents.js
    • owns local UI state for:
      • selectedOption
      • documentTypes
      • selectedDocumentType
      • checkedItems
      • selectAll
      • orderByState
      • fieldSortState
      • loading and download-status overlays
Search-to-document continuity
  • currentView.caseReference
    • is set before case navigation in search results
    • provides continuity from search discovery into case document discovery context

Service Layer

Primary service modules
  • actions/services/searchDirectService.js

    • getSearchDocumentDetails(incidentID)
    • getSearchDocumentTypes(incidentID)
    • getSearchDocumentDetailsPaged(...)
  • actions/services/searchService.js

    • thin re-export layer used by case document UI
Supporting UI helpers
  • components/utils/downloads.js

    • consumes generated document hash links for user-triggered downloads
  • components/utils/downloadmanager.js

    • provides queued download orchestration in the browser

API Layer

Principal routes
  • pages/api/endpoint/getsearchdocumentdetails_api.js
  • pages/api/endpoint/getsearchdocumentdetailspaged_api.js
  • pages/api/endpoint/getsearchdocumentTypes_api.js
  • adjacent but not visibly surfaced in the reviewed UI path:
    • pages/api/endpoint/getsearchdocumenthistory_api.js
    • pages/api/endpoint/getsearchdocumenthistorypaged_api.js
Route-family characteristics
  • getsearchdocumentdetails_api.js

    • CRM relay read with metadata shaping
    • filters for published-to-web documents tied to the case incident id
    • normalises pinswg_documentpublisheddate
    • adds pinswg_hashlink for downstream download use
  • getsearchdocumentdetailspaged_api.js

    • CRM relay read with pagination, filter, sort, and hash-link shaping
    • supports:
      • page number
      • sort field
      • sort direction
      • record-count preference
      • document-type filtering
  • getsearchdocumentTypes_api.js

    • CRM relay read with grouping transform
    • returns grouped document-type buckets and counts for the case documents filter UI
  • getsearchdocumenthistory*_api.js

    • CRM relay read for document history metadata
    • present in the route family, but not visibly consumed in the reviewed case-detail path

Integration Boundaries

  • CRM via Azure Relay

    • primary source of published-document metadata
    • touched through the search/document endpoint family
  • Local-only processing

    • document-type grouping presentation
    • filter state
    • pagination state
    • download queue state in the browser
    • locale-based label translation in the UI
  • NextAuth

    • not required for the public case document discovery path reviewed here
  • Azure Storage

    • not part of this published-document discovery flow

Ownership Model

Case incident id
→ getSearchDocumentDetails / getSearchDocumentDetailsPaged
→ CRM published document metadata
→ documentDetailsObj Redux state
→ case documents presentation

Architectural Flow

User
components/search/searchresults.js case selection
pages/case/[ticketnumber].js
components/case.js / components/case/summary.js
components/case/documents.js
searchService.getSearchDocumentTypes(...) + getSearchDocumentDetails(...) / getSearchDocumentDetailsPaged(...)
pages/api/endpoint/getsearchdocumentTypes_api.js / getsearchdocumentdetails*_api.js
relayGet(...)
→ Azure Relay
→ Dynamics 365 CRM

Change Entry Set

First files to inspect
  • pages/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • components/case/documents.js
  • actions/services/searchDirectService.js
  • store/searchOutput/action.js
  • store/searchOutput/reducer.js
Likely adjacent files
  • components/search/searchresults.js
  • pages/api/endpoint/getsearchdocumentdetails_api.js
  • pages/api/endpoint/getsearchdocumentdetailspaged_api.js
  • pages/api/endpoint/getsearchdocumentTypes_api.js
  • pages/api/endpoint/getsearchdocumenthistory_api.js
  • pages/api/endpoint/getsearchdocumenthistorypaged_api.js
  • components/utils/downloads.js
  • components/utils/downloadmanager.js
Highest-risk areas
  • document metadata shape expected by components/case/documents.js
  • generated pinswg_hashlink continuity between metadata and download
  • filter and pagination assumptions tied to @odata.count and @odata.nextLink
  • docsOffline flag behaviour because it changes whether download links are surfaced

Risk Classification

High

Reasoning:

  • public-facing document discovery behaviour
  • metadata retrieval, UI filtering, and download-link generation are tightly coupled
  • document presentation depends on multiple route variants rather than a single narrow loader

Published Document Retrieval / Download Journey Map

Purpose

Business purpose:

  • allows a user to retrieve a published document file once a visible document link is selected.

Maintainer purpose:

  • this journey shows the dedicated binary-delivery path, where the frontend does not download directly from CRM metadata routes but instead uses a separate download proxy route fed by the metadata-generated hash link.

Primary Entry Points

  • components/case/documents.js
  • components/utils/downloads.js
  • pages/api/documents/download/[id].js

Loaders / Initialisation

  • components/case/documents.js
    • uses ShowDocLinks = getDocLink(docsOffline) to determine whether link/button download behaviour is available
    • passes document records into DocumentLink
Browser-side download start
  • components/utils/downloads.js
    • receives detailsObj.pinswg_hashlink
    • on click, fetches the hash-link URL
    • reads stream data in the browser
    • derives filename from content-disposition when available
    • creates a blob URL and triggers an <a> download
    • emits a DownloadedFile analytics event
Download queuing
  • components/utils/downloadmanager.js
    • manages queued download tasks
    • limits concurrent downloads
    • tracks per-document statuses:
      • idle
      • queued
      • downloading
      • done
      • failed

State Ownership

Primary ownership
  • there is no dedicated Redux download slice in the reviewed path
  • download state is owned locally in the component/helper layer:
    • useDownloadQueue(...) status map
    • local progress state in DocumentLink
Metadata dependency
  • download initiation depends on metadata-generated pinswg_hashlink stored in document rows within documentDetailsObj

Service Layer

Visible service/helper modules
  • components/utils/downloads.js

    • acts as the main browser-side download helper in the reviewed published-document path
  • components/utils/downloadmanager.js

    • acts as the visible queue/orchestration helper
Important boundary note
  • this journey does not use a separate frontend actions/services/* download helper for published documents in the reviewed case-document path
  • instead, the browser fetches the generated proxy URL directly

API Layer

Principal route
  • pages/api/documents/download/[id].js
Route-family characteristics
  • documents/download/[id].js
    • document download proxy
    • requires id path param and hash query param
    • obtains access token via getToken()
    • forwards request to relay-backed documents/download/{id}?hash=...
    • streams response to the browser with download headers
    • redirects to /filenotavailable on invalid input or downstream failure
    • includes retry behaviour before giving up

Integration Boundaries

  • CRM document delivery via Azure Relay

    • visible downstream source of the streamed document response
  • Local proxy processing

    • request validation for id and hash
    • retry handling
    • response header setting
    • browser-stream handoff
  • Analytics

    • browser-side DownloadedFile event emitted after successful client download flow

Ownership Model

Document metadata row
→ pinswg_hashlink
→ /api/documents/download/[id]
→ relay-backed document stream
→ browser file download

Architectural Flow

User
→ click document link/button in components/case/documents.js
components/utils/downloads.js
→ fetch detailsObj.pinswg_hashlink
pages/api/documents/download/[id].js
getToken()
→ relay-backed documents/download/{id}?hash=...
→ streamed response returned to browser
→ blob URL download trigger

Change Entry Set

First files to inspect
  • components/case/documents.js
  • components/utils/downloads.js
  • components/utils/downloadmanager.js
  • pages/api/documents/download/[id].js
Likely adjacent files
  • pages/api/endpoint/getsearchdocumentdetails_api.js
  • pages/api/endpoint/getsearchdocumentdetailspaged_api.js
  • actions/core/token.js
  • actions/core/logger.js
Highest-risk areas
  • continuity between generated hash links and proxy-route expectations
  • filename extraction from response headers
  • retry and failure redirect behaviour
  • divergence between discovery metadata and actual downloadable document reference

Risk Classification

High

Reasoning:

  • direct user-visible download behaviour
  • download success depends on cross-boundary continuity between metadata shaping and proxy delivery
  • failure path redirects to a dedicated not-available route rather than returning document metadata errors in-place

Document Delivery Architecture

Visible architecture

User
→ Page
→ Service
→ API
→ CRM Metadata
→ Download Proxy
→ Document Delivery

Visible delivery flow

User
→ case documents UI
→ search document metadata route
→ CRM published document metadata
→ metadata row includes pinswg_hashlink
→ browser fetches /api/documents/download/[id]?hash=...
→ download proxy forwards to relay-backed documents/download/{id}
→ streamed file delivered to browser

Document metadata source

  • visible source: pinswg_documents metadata queried via relay-backed endpoint routes
  • key visible metadata fields include:
    • pinswg_isharedocumentreference
    • pinswg_name
    • pinswg_latestpublisheddate
    • pinswg_documentpublisheddate
    • pinswg_isharedocumentlocations

Download mechanism

  • metadata routes generate pinswg_hashlink
  • browser fetches the hash link
  • proxy streams the binary response
  • browser creates a blob-backed local download

Proxy behaviour

  • validates presence of id and hash
  • retrieves access token
  • retries the downstream fetch up to the visible configured attempt count
  • sets Content-Disposition
  • streams binary data to the browser
  • redirects to /filenotavailable when download cannot be served

Future API Grouping Assessment

This section is a future grouping assessment only.

It is not an implementation recommendation.

Current route Journey owner Integration touched Future grouping candidate Migration caution
pages/api/documents/download/[id].js published document retrieval / download CRM Relay candidate for api/documents/download dedicated streaming proxy with redirect-on-failure behaviour; migration caution because callers depend on binary delivery rather than JSON shape
pages/api/endpoint/getsearchdocumentdetails_api.js published document discovery CRM Relay candidate for api/documents/discovery generates pinswg_hashlink consumed by download flow; migration caution because metadata and download continuity are tightly coupled
pages/api/endpoint/getsearchdocumentdetailspaged_api.js published document discovery CRM Relay candidate for api/documents/discovery carries paging, sorting, and document-type filtering semantics; migration caution because UI list behaviour depends on current contract
pages/api/endpoint/getsearchdocumentTypes_api.js published document discovery CRM Relay candidate for api/documents/discovery grouped bucket/count output is UI-shaped rather than raw CRM output; migration caution because filter UI depends on this grouped format
pages/api/endpoint/getsearchdocumenthistory_api.js published document discovery support CRM Relay candidate for api/documents/history route is visible in the family but not surfaced in the reviewed UI path; migration caution because unseen callers may still depend on contract
pages/api/endpoint/getsearchdocumenthistorypaged_api.js published document discovery support CRM Relay candidate for api/documents/history paged/history variant appears parallel to non-paged history route; migration caution because contract usage was not fully surfaced in this slice

Classification notes

  • candidate for api/documents/discovery
    • routes whose clearest visible responsibility is published-document metadata retrieval, shaping, grouping, paging, or filtering
  • candidate for api/documents/history
    • routes whose clearest visible responsibility is history metadata rather than current document-list presentation
  • candidate for api/documents/download
    • routes whose clearest visible responsibility is binary document delivery

Discovery vs Download Comparison

Shared APIs

  • both journeys depend on:
    • metadata-generated pinswg_hashlink
    • document reference continuity across search/document route family and download proxy route

Shared integrations

  • both touch:
    • CRM via Azure Relay

Shared ownership assumptions

  • both assume the document journey is keyed by:
    • case incident id for discovery
    • document shared reference/id for download
  • both assume published-web filtering occurs before a document becomes user-downloadable in the visible UI path

Where the journeys diverge

  • discovery is metadata/list oriented:
    • grouping
    • filtering
    • sorting
    • pagination
    • bilingual label presentation
  • download is binary-delivery oriented:
    • proxy forwarding
    • stream handling
    • filename extraction
    • failure redirect

Architectural Flows

Published Document Discovery

User
→ search results
→ case detail route
→ case documents component
→ getSearchDocumentTypes / getSearchDocumentDetails / getSearchDocumentDetailsPaged
→ CRM published-document metadata
→ documentDetailsObj
→ visible document list

Published Document Retrieval / Download

User
→ click published document link
→ metadata row pinswg_hashlink
→ /api/documents/download/[id]
→ relay-backed document stream
→ browser blob download

Change Entry Sets

Published Document Discovery

  • start with:
    • pages/case/[ticketnumber].js
    • components/case.js
    • components/case/summary.js
    • components/case/documents.js
    • actions/services/searchDirectService.js
    • pages/api/endpoint/{getsearchdocumentdetails_api,getsearchdocumentdetailspaged_api,getsearchdocumentTypes_api}.js
    • store/searchOutput/{action,reducer}.js

Published Document Retrieval / Download

  • start with:
    • components/case/documents.js
    • components/utils/downloads.js
    • components/utils/downloadmanager.js
    • pages/api/documents/download/[id].js
    • adjacent metadata generators in pages/api/endpoint/getsearchdocumentdetails*_api.js

Risk Classification

  • Published Document Discovery: High
  • Published Document Retrieval / Download: High

Investigation Method

Files reviewed for Slice 7

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/portal-api-platform-assessment.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Journey pages / components / services / state:

  • components/search/searchresults.js
  • pages/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • components/case/documents.js
  • components/utils/downloads.js
  • components/utils/downloadmanager.js
  • actions/services/searchService.js
  • actions/services/searchDirectService.js
  • store/searchOutput/action.js
  • store/searchOutput/reducer.js

API files:

  • pages/api/endpoint/getsearchdocumentdetails_api.js
  • pages/api/endpoint/getsearchdocumentdetailspaged_api.js
  • pages/api/endpoint/getsearchdocumentTypes_api.js
  • pages/api/endpoint/getsearchdocumenthistory_api.js
  • pages/api/endpoint/getsearchdocumenthistorypaged_api.js
  • pages/api/documents/download/[id].js

Searches performed for Slice 7

  • pages: documents/download|getsearchdocumentdetails|getsearchdocumenthistory|getsearchdocumentTypes|filenotavailable
  • components: document|download|filenotavailable|DocumentDetails|docsOffline|showFilteredDocs
  • actions/services: getSearchDocumentDetails|getSearchDocumentTypes|getSearchDocumentDetailsPaged|download
  • store: documentDetailsObj|setDocumentDetails|setDocumentHistory
  • pages/api: getsearchdocumentdetails|getsearchdocumenthistory|getsearchdocumentTypes|download/[id]|hashAPIPath|relayGet(

Limitations for Slice 7

  • This slice was intentionally limited to visible published-document discovery and download architecture.
  • It did not speculate about security posture, authorization posture, or relay-side implementation beyond the visible frontend code.
  • It did not widen into draft/blob document upload flows, appeal-PDF download flows, or admin/latest-document reporting flows.
  • It did not execute runtime downloads.
  • It did not infer active use of document history beyond visible route presence, because the reviewed UI path did not surface it.

Risks / Cautions

  1. Discovery and download are separate route families but are tightly coupled by generated pinswg_hashlink values.
  2. docsOffline visibly suppresses live document-link behaviour, so maintainers should treat document availability messaging as part of the journey architecture.
  3. The reviewed user-facing discovery path is case-detail-centric rather than a standalone document page, so changes can affect search-to-case continuity.
  4. Document history routes exist in the API family, but their visible user-facing ownership is weaker than current document discovery in the reviewed slice.
  5. Download behaviour is split between server-side streaming in /api/documents/download/[id] and client-side blob handling in components/utils/downloads.js.

Validation Performed

  • Confirmed the required Slice 7 context files were read.
  • Performed non-destructive code reading and targeted searches only.
  • Traced the visible discovery path from search-to-case navigation into the case documents component.
  • Traced the visible delivery path from metadata-generated hash link into the download proxy and browser download helper.
  • Confirmed that document history routes are present but not visibly surfaced by the reviewed case-document UI path.
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Case Messages / Notices and Related Published Case Communications

This would extend the journey map into another public case-detail-adjacent read flow, staying within discovery-only architecture work and preserving continuity with the already-mapped case, document, notification, and watchlist slices.


Files Modified

  • context/journey-architecture-map.md
  • memory-bank/change-log.md

Findings

  • The visible case-communication architecture is centered on case-detail presentation, not outbound delivery.
  • The strongest visible message path is:
    • case route bootstrap
    • getCaseMessage(incidentid)
    • messagesObj
    • CaseNoticeBanner
    • user-visible notice content on the case details tab
  • The reviewed case-message route retrieves CRM tasks records filtered to subjects containing Banner, which is the clearest visible source of public case-level notices in this slice.
  • Published case communications are therefore distinct from:
    • Published Documents, which are metadata + download driven
    • Notifications / Email, which are outbound communication driven
  • SIPS events and SIPS media are adjacent published case communications in the case journey, but they are visibly surfaced as separate tabs and a live-event banner rather than being part of the same messagesObj notice payload.

Case Messages / Notices Journey Map

Purpose

Business purpose:

  • allows a user to see time-bounded public case notices or banner-style communications associated with a case.

Maintainer purpose:

  • this journey shows how PEDW loads and renders public case notices directly in the case-detail experience, using a dedicated message route and a case-page presentation component rather than a document or notification delivery mechanism.

Primary Entry Points

  • pages/case/[ticketnumber].js
  • pages/dns/[developmentName].js
  • pages/myportal/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • components/case/caseNoticeBanner.js

Loaders / Initialisation

Public case loader
  • pages/case/[ticketnumber].js
    • bootstraps case detail through search-family reads
    • separately retrieves messages with:
      • getCaseMessage(searchResultsObj.value[0].incidentid)
    • passes messagesObj into the case page props
DNS case loader
  • pages/dns/[developmentName].js
    • bootstraps DNS case detail through DNS search-family reads
    • separately retrieves messages with:
      • getCaseMessage(searchResultsObj.value[0].incidentid)
    • passes messagesObj into the case page props
My Portal case loader
  • pages/myportal/case/[ticketnumber].js
    • bootstraps portal case detail with authenticated portal context plus case search/detail data
    • separately retrieves messages with:
      • getCaseMessage(searchResultsObj.value[0].incidentid)
    • passes messagesObj into the case page props
Case page presentation bootstrap
  • components/case.js

    • passes messagesObj into components/case/summary.js
  • components/case/summary.js

    • renders CaseNoticeBanner inside the case-details tab when:
      • props.messagesObj["@odata.count"] > 0

State Ownership

Primary ownership
  • messagesObj
    • is page-prop owned in the reviewed path
    • is not stored in a dedicated Redux slice in the reviewed case-message flow
Adjacent state
  • store/searchOutput/reducer.js

    • owns adjacent case-detail communication state for:
      • eventDetailsObj
      • mediaDetailsObj
    • does not own messagesObj
  • store/currentView/reducer.js

    • participates only indirectly through case route/context continuity
UI ownership
  • components/case/caseNoticeBanner.js
    • owns the visible interpretation and rendering of message rows
    • applies date-window checks and bilingual content splitting in the UI layer

Service Layer

Primary service modules
  • actions/services/caseDirectService.js

    • getCaseMessage(searchString)
    • adjacent communication-related helpers:
      • getSIPSEvents(caseid)
      • getSIPSMedia(caseid)
  • actions/services/caseService.js

    • thin re-export layer for the above helpers

API Layer

Principal routes
  • pages/api/endpoint/getcasemessage_api.js
  • adjacent directly relevant communication routes:
    • pages/api/endpoint/getsipsevents_api.js
    • pages/api/endpoint/getsipsmedia_api.js
Route-family characteristics
  • getcasemessage_api.js

    • CRM relay read for case banner messages
    • requires id
    • queries CRM tasks
    • filters by:
      • _regardingobjectid_value eq caseId
      • contains(subject, 'Banner')
      • statuscode ne 5
    • orders by createdon desc
  • getsipsevents_api.js

    • CRM relay read for published event records
    • separate case-communication route family for event-tab content
  • getsipsmedia_api.js

    • CRM relay read for published media/event recordings
    • separate case-communication route family for media-tab content

Integration Boundaries

  • CRM via Azure Relay

    • primary source of visible case notices/messages
    • also the source of adjacent SIPS communication content
  • Local-only processing

    • date-window filtering in CaseNoticeBanner
    • bilingual subject/description splitting in the UI layer
    • case-tab placement and notice rendering
  • NextAuth

    • not required for the public case-message path
    • used only in the authenticated myportal case variant upstream of the same message retrieval call

Ownership Model

Case incident id
→ getCaseMessage(incidentid)
→ CRM tasks filtered to Banner subjects
→ messagesObj page prop
→ CaseNoticeBanner
→ visible case notice

Architectural Flow

User
→ case route (pages/case/[ticketnumber].js or DNS/portal variant)
→ case bootstrap via search family
caseService.getCaseMessage(incidentid)
pages/api/endpoint/getcasemessage_api.js
relayGet(...)
→ Azure Relay
→ Dynamics 365 CRM tasks
messagesObj
components/case/summary.js
CaseNoticeBanner

Change Entry Set

First files to inspect
  • pages/case/[ticketnumber].js
  • pages/dns/[developmentName].js
  • pages/myportal/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • components/case/caseNoticeBanner.js
  • actions/services/caseDirectService.js
  • pages/api/endpoint/getcasemessage_api.js
Likely adjacent files
  • pages/api/endpoint/getsipsevents_api.js
  • pages/api/endpoint/getsipsmedia_api.js
  • store/searchOutput/action.js
  • store/searchOutput/reducer.js
Highest-risk areas
  • message filtering assumptions based on subject containing Banner
  • bilingual content splitting conventions in subject and description
  • date-window visibility logic in CaseNoticeBanner
  • page-prop ownership of messagesObj, because it is not normalized into Redux in the reviewed path

Risk Classification

High

Reasoning:

  • public case-page communication is user-visible and contract-sensitive
  • message meaning is shaped partly in the UI layer rather than only in the API layer
  • the same case journey mixes messages, documents, events, media, and status presentation

Purpose

Business purpose:

  • allows a user to see related published case communications around the case beyond banner notices, where those communications are directly surfaced in the case-detail journey.

Maintainer purpose:

  • this journey shows how PEDW presents adjacent published communication surfaces, especially SIPS live-event, events, and media content, and how those differ from notice banners while still participating in the same case-page communication experience.

Primary Entry Points

  • pages/case/[ticketnumber].js
  • pages/dns/[developmentName].js
  • pages/myportal/case/[ticketnumber].js
  • components/case/summary.js
  • adjacent case communication components:
    • components/case/events.js
    • components/case/media.js

Loaders / Initialisation

SIPS communication bootstrap
  • pages/case/[ticketnumber].js
  • pages/dns/[developmentName].js
  • pages/myportal/case/[ticketnumber].js
    • conditionally load SIPS event records when appeal case type is 846040002
    • load:
      • getSIPSEvents(searchDetailsObj[0].value[0].pinswg_sipsid)
      • getSIPSMedia(searchResultsObj.value[0].incidentid)
    • dispatch results into Redux:
      • setEventDetails(eventsObj)
      • setMediaDetails(mediaObj)
Case-summary communication presentation
  • components/case/summary.js
    • derives:
      • hasEventsTabData
      • hasMediaTabData
      • livePublishedEvent
    • renders a GOV.UK notification banner for a live published event when available
    • renders separate Events and Media tabs when corresponding data exists

State Ownership

Primary slices
  • store/searchOutput/reducer.js
    • owns:
      • eventDetailsObj
      • mediaDetailsObj
    • this is the primary visible state owner for adjacent published case communications in the reviewed path
UI ownership
  • components/case/summary.js
    • owns the live-event banner selection logic through derived view state

Service Layer

Primary service modules
  • actions/services/caseDirectService.js
    • getSIPSEvents(caseid)
    • getSIPSMedia(caseid)

API Layer

Principal routes
  • pages/api/endpoint/getsipsevents_api.js
  • pages/api/endpoint/getsipsmedia_api.js
Route-family characteristics
  • getsipsevents_api.js

    • CRM relay read for event records
    • requires caseid
    • reads pinswg_sipsevents
  • getsipsmedia_api.js

    • CRM relay read for published event recordings/media
    • requires caseid
    • filters to pinswg_publishtoweb eq true
    • returns published recording metadata and URLs

Integration Boundaries

  • CRM via Azure Relay

    • primary source of event/media communication records
  • Local-only processing

    • live-event derivation and banner placement
    • case-tab presentation

Ownership Model

Case / SIPS context
→ getSIPSEvents / getSIPSMedia
→ Redux eventDetailsObj / mediaDetailsObj
→ case summary tabs and live-event banner

Architectural Flow

User
→ case route
→ SIPS-specific conditional bootstrap
getSIPSEvents(...) / getSIPSMedia(...)
getsipsevents_api.js / getsipsmedia_api.js
→ CRM via relay
→ Redux event/media state
→ case summary live-event banner and tabs

Change Entry Set

First files to inspect
  • pages/case/[ticketnumber].js
  • pages/dns/[developmentName].js
  • pages/myportal/case/[ticketnumber].js
  • components/case/summary.js
  • actions/services/caseDirectService.js
  • pages/api/endpoint/getsipsevents_api.js
  • pages/api/endpoint/getsipsmedia_api.js
  • store/searchOutput/action.js
  • store/searchOutput/reducer.js
Likely adjacent files
  • components/case/events.js
  • components/case/media.js
  • lib/domain/case-lifecycle/*
Highest-risk areas
  • direct coupling between case type checks and SIPS communication loading
  • live-event banner derivation logic in the case-summary layer
  • adjacency between event/media communications and message/document/status tabs in one page shell

Risk Classification

Medium-High

Reasoning:

  • user-visible communication content on public case pages
  • conditional SIPS-specific branching adds hidden coupling
  • adjacent but distinct from the main case-message banner route

Case Communication Architecture

Visible architecture

Case
→ message/notice source
→ case-detail presentation
→ user-visible communication

Visible communication flow

Case incident id
→ getcasemessage_api / getsipsevents_api / getsipsmedia_api
→ case-detail props or Redux state
→ case summary / notice banner / event-media tabs
→ user-visible communication on case page

Data source

  • banner notices/messages:
    • CRM tasks records filtered by contains(subject, 'Banner')
  • related SIPS communications:
    • CRM pinswg_sipsevents
    • CRM pinswg_eventrecordings

Route family

  • primary notice route:
    • pages/api/endpoint/getcasemessage_api.js
  • adjacent communication routes:
    • pages/api/endpoint/getsipsevents_api.js
    • pages/api/endpoint/getsipsmedia_api.js

State ownership

  • messagesObj
    • page-prop owned
  • eventDetailsObj / mediaDetailsObj
    • Redux owned via store/searchOutput

UI ownership

  • components/case/summary.js
    • main case-page owner of communication placement
  • components/case/caseNoticeBanner.js
    • owner of banner-message rendering

Where this differs from documents and notifications

  • Messages / Notices
    • case-page presentation of case-linked communications
  • Published Documents
    • document metadata retrieval + binary delivery path
  • Notifications / Email
    • outbound communication to a recipient rather than on-page case presentation

Future API Grouping Assessment

This section is a future grouping assessment only.

It is not an implementation recommendation.

Current route Journey owner Integration touched Future grouping candidate Migration caution
pages/api/endpoint/getcasemessage_api.js case messages / notices CRM Relay candidate for api/cases/messages route is the clearest visible public notice/banner source; migration caution because case pages currently depend on its specific tasks/banner shape
pages/api/endpoint/getsipsevents_api.js related published case communications CRM Relay candidate for api/cases/events SIPS-specific route is conditionally loaded from case pages; migration caution because ownership is case-type dependent rather than universally case-owned
pages/api/endpoint/getsipsmedia_api.js related published case communications CRM Relay candidate for api/cases/events or api/shared media is adjacent communication content but also its own tab family; migration caution because it overlaps event/media presentation concerns
pages/api/notices/index.js shared site notice support Local-only candidate for api/shared or unclear / historical static notice route is not part of case-message retrieval; migration caution because it appears to be site-level notice support rather than case-owned

Classification notes

  • candidate for api/cases/messages
    • routes whose clearest visible owner is case-page notice/message presentation
  • candidate for api/cases/events
    • routes whose clearest visible owner is case-page event/media communication content
  • candidate for api/shared
    • routes with broader site-level or cross-journey notice/support behaviour
  • unclear / historical
    • routes whose visible ownership is weaker or not clearly part of the active case-message journey

Messages vs Documents vs Notifications Comparison

Where they overlap

  • all three are user-visible communication surfaces in the wider platform
  • all can be associated with case context
  • messages and documents are both directly surfaced on case pages
  • notifications and messages can both convey case-related information, but in different delivery models

Where they differ

  • Case Messages / Notices

    • case-page presentation
    • time-bounded or banner-style communication
    • no download proxy
    • no outbound send path in the reviewed journey
  • Published Documents

    • metadata list + binary delivery
    • document-type grouping, sorting, filtering, and download behaviour
  • Notifications / Email

    • outbound communication
    • Notify/template driven
    • recipient-address oriented rather than case-tab oriented

Which one is case-page presentation

  • Case Messages / Notices

Which one is document delivery

  • Published Documents

Which one is outbound communication

  • Notifications / Email

Architectural Flows

Case Messages / Notices

User
→ case route bootstrap
→ getCaseMessage(incidentid)
→ getcasemessage_api
→ CRM Banner task records
→ messagesObj
→ CaseNoticeBanner
User
→ case route bootstrap
→ conditional SIPS event/media fetches
→ getsipsevents_api / getsipsmedia_api
→ Redux event/media state
→ live-event banner / event-media tabs

Change Entry Sets

Case Messages / Notices

  • start with:
    • pages/case/[ticketnumber].js
    • pages/dns/[developmentName].js
    • pages/myportal/case/[ticketnumber].js
    • components/case.js
    • components/case/summary.js
    • components/case/caseNoticeBanner.js
    • actions/services/caseDirectService.js
    • pages/api/endpoint/getcasemessage_api.js
  • start with:
    • pages/case/[ticketnumber].js
    • pages/dns/[developmentName].js
    • pages/myportal/case/[ticketnumber].js
    • components/case/summary.js
    • actions/services/caseDirectService.js
    • pages/api/endpoint/{getsipsevents_api,getsipsmedia_api}.js
    • store/searchOutput/{action,reducer}.js

Risk Classification

  • Case Messages / Notices: High
  • Related Published Case Communications: Medium-High

Investigation Method

Files reviewed for Slice 8

Required context re-read:

  • context/journey-architecture-map.md
  • context/api-route-map.md
  • context/portal-api-platform-assessment.md
  • context/architecture.md
  • context/integration-map.md
  • memory-bank/change-log.md

Journey pages / components / services / state:

  • pages/case/[ticketnumber].js
  • pages/dns/[developmentName].js
  • pages/myportal/case/[ticketnumber].js
  • components/case.js
  • components/case/summary.js
  • components/case/caseNoticeBanner.js
  • actions/services/caseService.js
  • actions/services/caseDirectService.js
  • store/searchOutput/action.js
  • store/searchOutput/reducer.js

API files:

  • pages/api/endpoint/getcasemessage_api.js
  • pages/api/endpoint/getsipsevents_api.js
  • pages/api/endpoint/getsipsmedia_api.js
  • pages/api/notices/index.js

Searches performed for Slice 8

  • components/case: messagesObj|getCaseMessage|notice|banner|message
  • pages: getCaseMessage|messagesObj|getcasemessage_api|notice|message
  • actions/services: getCaseMessage|getSIPSEvents|getSIPSMedia|message|notice
  • pages/api/endpoint: getcasemessage_api|getsipsevents_api|getsipsmedia_api|message|notice
  • store: messagesObj|message|notice|eventDetailsObj|mediaDetailsObj

Limitations for Slice 8

  • This slice was intentionally limited to visible case-message / notice / communication flows.
  • It did not widen into outbound notification delivery or document download behaviour beyond comparison.
  • It did not assess communication policy correctness or content governance.
  • It did not infer a dedicated Redux ownership model for messagesObj where none was visibly present.
  • It did not execute runtime case-page flows.

Risks / Cautions

  1. The main notice source is identified through a CRM tasks query filtered by contains(subject, 'Banner'), so behaviour depends on content conventions as well as route logic.
  2. CaseNoticeBanner performs visible bilingual splitting and date-window checks in the UI layer, which makes presentation logic part of the architectural behaviour.
  3. messagesObj is page-prop owned while adjacent event/media communications are Redux owned, so communication state is split across ownership models.
  4. SIPS live-event/media communications overlap with case communications, but they are a separate visible route/state family from banner notices.
  5. The static pages/api/notices/index.js route exists as a notice-like support surface but is not part of the reviewed case-message loading path.

Validation Performed

  • Confirmed the required Slice 8 context files were read.
  • Performed non-destructive code reading and targeted searches only.
  • Traced the visible case-message flow from case loader to CaseNoticeBanner presentation.
  • Traced directly relevant SIPS event/media communication overlap only where it is visibly part of the case page.
  • Compared message/notice presentation against the already documented document and notification slices without reopening those journeys.
  • No runtime code changed.
  • No lint/tests run because this was documentation-only work.

Recommendation

Next journey slice only:

  • Case Status / Lifecycle Presentation and Related Published Timeline Signals

This would extend the journey map into another case-detail-adjacent presentation slice that naturally follows messages, notices, events, media, and documents while remaining discovery-only.