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

6066 lines
208 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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
```text
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
```text
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:
```text
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
```text
NextAuth session.user.id
→ setContainerID(session.user.id)
→ storage container identity
→ casefolderID / ticketnumber prefix
→ draft appeal JSON
→ case blob
→ uploaded files
```
#### Submitted ownership transition
```text
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
```text
User
→ New Appeal page
→ SSR loader
→ Redux hydration
→ BuildSection
→ document services
→ file APIs
→ Azure Storage
```
#### Draft Resume
```text
User
→ My Portal resume page
→ SSR loader
→ getProgressFromBlob + getFilesFromBlob
→ Redux hydration
→ NewAppealFlow
```
#### Appeal Submission / Finalisation
```text
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
```text
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
```text
Session identity
→ container identity
→ representation draft ownership
→ completion message route
→ queue handoff
→ CRM involvement / representation side effects
```
Visible submitted transition:
```text
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
```text
NextAuth session.user.id
→ accountDetails.containerID
→ Azure Storage container
→ representation draft blob set
→ representation file subtree
```
#### Representation submitted transition
```text
Representation draft ownership
→ completion message route
→ Azure Queue
→ CRM involvement / representation boundary
```
### Architectural Flows
#### Draft Representation Creation
```text
User
→ Session
→ Representation loader
→ Redux currentView/accountDetails/myRepresentations
→ Storage draft retrieval / save
→ Azure Storage
```
#### Representation Submission / Finalisation
```text
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:
```text
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:
```text
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
```text
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:
```text
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
```text
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
```text
NextAuth session
→ session.user.email
→ getPortalLogin(email)
→ CRM Contact
→ portal access / dashboard access
```
#### How registration fits
```text
Session exists
→ no CRM Contact found
→ registration flow
→ CRM contact creation
→ later bootstrap succeeds
```
#### How account management fits
```text
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
```text
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
```text
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
```text
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
```text
Account/contact identity
→ email address + preferred language
→ business event or watchlist state
→ Notify payload build
→ GOV.UK Notify send
```
For watchlist-driven notifications specifically:
```text
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
```text
NextAuth session
→ session.user.email
→ getPortalLogin(email)
→ CRM contact or registration redirect
→ pinsUser cookie
→ portal entry continuity
```
#### Notifications / Email
```text
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
```text
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
```text
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:
```text
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
```text
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
```text
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
```text
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
```text
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:
```text
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`:
```text
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:
```text
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
```text
User
→ search results / case summary watch action
→ createWatchedCases(payload)
→ createwatchedcases_api
→ CRM watchlist create/patch
→ getWatchedCasesProxy
→ watchedCases Redux refresh
```
#### Watchlist Viewing
```text
User
→ myportal bootstrap
→ getWatchedCases(contactId)
→ CRM watchlist retrieval
→ splitWatchedCasesBySubmissionState
→ detail enrichment
→ dashboard card / top-three / view-all
```
#### Watchlist Removal
```text
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
```text
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
##### Download link enablement
- `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
```text
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
```text
User
→ Page
→ Service
→ API
→ CRM Metadata
→ Download Proxy
→ Document Delivery
```
#### Visible delivery flow
```text
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
```text
User
→ search results
→ case detail route
→ case documents component
→ getSearchDocumentTypes / getSearchDocumentDetails / getSearchDocumentDetailsPaged
→ CRM published-document metadata
→ documentDetailsObj
→ visible document list
```
#### Published Document Retrieval / Download
```text
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.
---
## Slice 8 — Case Messages / Notices and Related Published Case Communications
### 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
```text
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
### Related Published Case Communications Journey Map
#### 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
```text
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
```text
Case
→ message/notice source
→ case-detail presentation
→ user-visible communication
```
#### Visible communication flow
```text
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
```text
User
→ case route bootstrap
→ getCaseMessage(incidentid)
→ getcasemessage_api
→ CRM Banner task records
→ messagesObj
→ CaseNoticeBanner
```
#### Related Published Case Communications
```text
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`
#### Related Published Case Communications
- 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.