Skip to content

Feature/296 subscription list types rebased - #340

Closed
alexbottenberg wants to merge 136 commits into
feature/use-list-types-from-databasefrom
feature/296-subscription-list-types-rebased
Closed

alexbottenberg wants to merge 136 commits into
feature/use-list-types-from-databasefrom
feature/296-subscription-list-types-rebased

Conversation

@alexbottenberg

@alexbottenberg alexbottenberg commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

https://tools.hmcts.net/jira/browse/VIBE-307

Change description

Adding functionality for Verified Media Users to select specific list types when subscribing to hearing lists in CaTH SO THAT they can receive email notifications for the selected list types

Testing done

  • Unit Tests: 18/18 passing (queries + service layer)
  • Controller Tests: 34/34 passing (all 5 pages)
  • E2E Tests: 3/3 passing (complete journey + validation + duplicate prevention)
  • Accessibility: WCAG 2.2 AA compliant with zero violations

Security Vulnerability Assessment

CVE Suppression: Are there any CVEs present in the codebase (either newly introduced or pre-existing) that are being intentionally suppressed or ignored by this commit?

  • Yes
  • No

Checklist

  • commit messages are meaningful and follow good commit message guidelines
  • README and other documentation has been updated / added (if needed)
  • tests have been updated / new tests has been added (if needed)
  • Does this PR introduce a breaking change

Summary by CodeRabbit

Release Notes

  • New Features

    • Added list-type subscriptions enabling verified users to subscribe to specific courts, tribunals, and case types independently of location
    • Introduced bilingual support (English and Welsh) across subscription workflows with language selection (English, Welsh, or Both)
    • Enhanced subscription management dashboard displaying both location-based and list-type subscriptions with edit and removal options
    • Implemented multi-step guided subscription journey with location filtering and duplicate-prevention
  • Improvements

    • Updated filter panel visual indicators for better accessibility
    • Enhanced error handling and validation across subscription pages

KianKwa and others added 15 commits January 26, 2026 09:28
Allows verified media users to subscribe to specific list types (e.g., Civil Daily Cause List, Family Daily Cause List) and choose their preferred language (English, Welsh, or both). Users can manage their list type subscriptions alongside their location subscriptions.

Key changes:
- Add SubscriptionListType model with unique constraint on (userId, listTypeId, language)
- Create subscription-list-types module with service layer and queries
- Add user journey pages: subscription-add-method, subscription-list-types, subscription-list-language, subscription-confirm, subscription-locations-review, delete-list-type-subscription
- Update subscription-management page to display both location and list type subscriptions
- Add comprehensive test coverage (18 unit tests, 34 controller tests, 3 E2E tests)
- Support maximum of 50 list type subscriptions per user

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove inline styles from subscription-list-types template and move to external SCSS with BEM naming conventions
- Remove style block from subscription-confirm template and replace with CSS classes
- Remove location requirement validation in subscription-confirm POST handler to allow list type subscriptions without locations
- Add proper CSS classes following GOV.UK Design System best practices

This resolves the 3 high-priority issues identified in the code review while maintaining all functionality and test coverage.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 187 files, which is 37 over the limit of 150.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1513fa7f-5ca1-43be-ba3b-2dda64bd35ff

📥 Commits

Reviewing files that changed from the base of the PR and between 67ef15e and 5a4e07e.

📒 Files selected for processing (187)
  • .github/workflows/e2e.yml
  • .gitignore
  • apps/api/src/server.ts
  • apps/postgres/prisma/migrations/20260122150011_add_case_name_and_case_number_to_subscription/migration.sql
  • apps/postgres/prisma/migrations/20260123151728_add_cascade_delete_to_notification_audit_log/migration.sql
  • apps/postgres/prisma/migrations/20260205161949_add_subscription_list_type/migration.sql
  • apps/postgres/prisma/migrations/20260211000000_change_language_to_array/migration.sql
  • apps/postgres/prisma/migrations/20260216162345_make_subscription_id_optional_in_notification_audit_log/migration.sql
  • apps/postgres/prisma/schema.prisma
  • apps/postgres/src/schema-discovery.test.ts
  • apps/web/.env.example
  • apps/web/helm/values.dev.yaml
  • apps/web/src/assets/css/index.scss
  • apps/web/src/server.ts
  • docs/GITHUB_SECRETS_SETUP.md
  • docs/tickets/296/IMPLEMENTATION_SUMMARY.md
  • docs/tickets/296/notification-integration-implementation.md
  • docs/tickets/296/notification-integration-plan.md
  • docs/tickets/296/plan.md
  • docs/tickets/296/review.md
  • docs/tickets/296/tasks.md
  • docs/tickets/296/ticket.md
  • docs/tickets/VIBE-300/e2e-tests-summary.md
  • docs/tickets/VIBE-300/plan.md
  • docs/tickets/VIBE-300/specification.md
  • docs/tickets/VIBE-300/tasks.md
  • docs/tickets/VIBE-300/ticket.md
  • e2e-tests/README-NOTIFICATIONS.md
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • e2e-tests/tests/bulk-unsubscribe.spec.ts
  • e2e-tests/tests/case-subscriptions.spec.ts
  • e2e-tests/tests/email-subscriptions.spec.ts
  • e2e-tests/tests/manage-list-types.spec.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/tests/subscription-list-types.spec.ts
  • e2e-tests/tests/subscription-notifications.spec.ts
  • e2e-tests/utils/notification-helpers.ts
  • e2e-tests/utils/seed-reference-data.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/api/src/blob-ingestion/repository/service.ts
  • libs/cloud-native-platform/src/properties-volume/azure-vault.test.ts
  • libs/cloud-native-platform/src/properties-volume/azure-vault.ts
  • libs/location/src/location-data.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notifications/package.json
  • libs/notifications/prisma/schema.prisma
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/notifications/src/govnotify/template-config.test.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/src/notification/subscription-queries.ts
  • libs/publication/src/index.ts
  • libs/publication/src/repository/artefact-search-queries.test.ts
  • libs/publication/src/repository/artefact-search-queries.ts
  • libs/publication/src/repository/queries.ts
  • libs/subscription-list-types/package.json
  • libs/subscription-list-types/prisma/schema.prisma
  • libs/subscription-list-types/src/config.ts
  • libs/subscription-list-types/src/index.ts
  • libs/subscription-list-types/src/locales/cy.ts
  • libs/subscription-list-types/src/locales/en.ts
  • libs/subscription-list-types/src/subscription-list-type/repository/queries.test.ts
  • libs/subscription-list-types/src/subscription-list-type/repository/queries.ts
  • libs/subscription-list-types/src/subscription-list-type/repository/service.test.ts
  • libs/subscription-list-types/src/subscription-list-type/repository/service.ts
  • libs/subscription-list-types/tsconfig.json
  • libs/subscription/package.json
  • libs/subscription/prisma/schema.prisma
  • libs/subscription/src/case-search-service.test.ts
  • libs/subscription/src/case-search-service.ts
  • libs/subscription/src/config.ts
  • libs/subscription/src/index.ts
  • libs/subscription/src/locales/cy.ts
  • libs/subscription/src/locales/en.ts
  • libs/subscription/src/repository/queries.test.ts
  • libs/subscription/src/repository/queries.ts
  • libs/subscription/src/repository/service.test.ts
  • libs/subscription/src/repository/service.ts
  • libs/subscription/src/validation/validation.test.ts
  • libs/subscription/src/validation/validation.ts
  • libs/subscription/tsconfig.json
  • libs/subscriptions/src/index.ts
  • libs/subscriptions/src/repository/queries.test.ts
  • libs/subscriptions/src/repository/queries.ts
  • libs/verified-pages/package.json
  • libs/verified-pages/src/assets/css/verified-pages.scss
  • libs/verified-pages/src/pages/bulk-unsubscribe/cy.ts
  • libs/verified-pages/src/pages/bulk-unsubscribe/en.ts
  • libs/verified-pages/src/pages/bulk-unsubscribe/index.njk
  • libs/verified-pages/src/pages/bulk-unsubscribe/index.test.ts
  • libs/verified-pages/src/pages/bulk-unsubscribe/index.ts
  • libs/verified-pages/src/pages/case-name-search-results/cy.ts
  • libs/verified-pages/src/pages/case-name-search-results/en.ts
  • libs/verified-pages/src/pages/case-name-search-results/index.njk
  • libs/verified-pages/src/pages/case-name-search-results/index.test.ts
  • libs/verified-pages/src/pages/case-name-search-results/index.ts
  • libs/verified-pages/src/pages/case-name-search/cy.ts
  • libs/verified-pages/src/pages/case-name-search/en.ts
  • libs/verified-pages/src/pages/case-name-search/index.njk
  • libs/verified-pages/src/pages/case-name-search/index.test.ts
  • libs/verified-pages/src/pages/case-name-search/index.ts
  • libs/verified-pages/src/pages/case-number-search-results/cy.ts
  • libs/verified-pages/src/pages/case-number-search-results/en.ts
  • libs/verified-pages/src/pages/case-number-search-results/index.njk
  • libs/verified-pages/src/pages/case-number-search-results/index.test.ts
  • libs/verified-pages/src/pages/case-number-search-results/index.ts
  • libs/verified-pages/src/pages/case-number-search/cy.ts
  • libs/verified-pages/src/pages/case-number-search/en.ts
  • libs/verified-pages/src/pages/case-number-search/index.njk
  • libs/verified-pages/src/pages/case-number-search/index.test.ts
  • libs/verified-pages/src/pages/case-number-search/index.ts
  • libs/verified-pages/src/pages/confirm-bulk-unsubscribe/cy.ts
  • libs/verified-pages/src/pages/confirm-bulk-unsubscribe/en.ts
  • libs/verified-pages/src/pages/confirm-bulk-unsubscribe/index.njk
  • libs/verified-pages/src/pages/confirm-bulk-unsubscribe/index.test.ts
  • libs/verified-pages/src/pages/confirm-bulk-unsubscribe/index.ts
  • libs/verified-pages/src/pages/delete-list-type-subscription/index.ts
  • libs/verified-pages/src/pages/delete-subscription/index.test.ts
  • libs/verified-pages/src/pages/delete-subscription/index.ts
  • libs/verified-pages/src/pages/location-name-search/cy.ts
  • libs/verified-pages/src/pages/location-name-search/en.ts
  • libs/verified-pages/src/pages/location-name-search/index.njk
  • libs/verified-pages/src/pages/location-name-search/index.ts
  • libs/verified-pages/src/pages/pending-subscriptions/cy.ts
  • libs/verified-pages/src/pages/pending-subscriptions/en.ts
  • libs/verified-pages/src/pages/pending-subscriptions/index.njk
  • libs/verified-pages/src/pages/pending-subscriptions/index.test.ts
  • libs/verified-pages/src/pages/pending-subscriptions/index.ts
  • libs/verified-pages/src/pages/subscription-add-method/cy.ts
  • libs/verified-pages/src/pages/subscription-add-method/en.ts
  • libs/verified-pages/src/pages/subscription-add-method/index.njk
  • libs/verified-pages/src/pages/subscription-add-method/index.test.ts
  • libs/verified-pages/src/pages/subscription-add-method/index.ts
  • libs/verified-pages/src/pages/subscription-add/cy.ts
  • libs/verified-pages/src/pages/subscription-add/en.ts
  • libs/verified-pages/src/pages/subscription-add/index.njk
  • libs/verified-pages/src/pages/subscription-add/index.test.ts
  • libs/verified-pages/src/pages/subscription-add/index.ts
  • libs/verified-pages/src/pages/subscription-by-location/cy.ts
  • libs/verified-pages/src/pages/subscription-by-location/en.ts
  • libs/verified-pages/src/pages/subscription-by-location/index.njk
  • libs/verified-pages/src/pages/subscription-by-location/index.test.ts
  • libs/verified-pages/src/pages/subscription-by-location/index.ts
  • libs/verified-pages/src/pages/subscription-confirm/cy.ts
  • libs/verified-pages/src/pages/subscription-confirm/en.ts
  • libs/verified-pages/src/pages/subscription-confirm/index.njk
  • libs/verified-pages/src/pages/subscription-confirm/index.test.ts
  • libs/verified-pages/src/pages/subscription-confirm/index.ts
  • libs/verified-pages/src/pages/subscription-confirmed/index.njk
  • libs/verified-pages/src/pages/subscription-confirmed/index.test.ts
  • libs/verified-pages/src/pages/subscription-confirmed/index.ts
  • libs/verified-pages/src/pages/subscription-list-language/cy.ts
  • libs/verified-pages/src/pages/subscription-list-language/en.ts
  • libs/verified-pages/src/pages/subscription-list-language/index.njk
  • libs/verified-pages/src/pages/subscription-list-language/index.test.ts
  • libs/verified-pages/src/pages/subscription-list-language/index.ts
  • libs/verified-pages/src/pages/subscription-list-types/cy.ts
  • libs/verified-pages/src/pages/subscription-list-types/en.ts
  • libs/verified-pages/src/pages/subscription-list-types/index.njk
  • libs/verified-pages/src/pages/subscription-list-types/index.test.ts
  • libs/verified-pages/src/pages/subscription-list-types/index.ts
  • libs/verified-pages/src/pages/subscription-locations-review/cy.ts
  • libs/verified-pages/src/pages/subscription-locations-review/en.ts
  • libs/verified-pages/src/pages/subscription-locations-review/index.njk
  • libs/verified-pages/src/pages/subscription-locations-review/index.test.ts
  • libs/verified-pages/src/pages/subscription-locations-review/index.ts
  • libs/verified-pages/src/pages/subscription-management/cy.ts
  • libs/verified-pages/src/pages/subscription-management/en.ts
  • libs/verified-pages/src/pages/subscription-management/index.njk
  • libs/verified-pages/src/pages/subscription-management/index.test.ts
  • libs/verified-pages/src/pages/subscription-management/index.ts
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.njk
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.test.ts
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.ts
  • libs/verified-pages/src/types/session.ts
  • libs/verified-pages/src/utils/csrf.test.ts
  • libs/verified-pages/src/utils/csrf.ts
  • libs/web-core/src/assets/css/filter-panel.scss
  • libs/web-core/src/assets/js/filter-panel.test.ts
  • libs/web-core/src/assets/js/filter-panel.ts
  • tsconfig.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/296-subscription-list-types-rebased

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Remove non-existent @hmcts/subscriptions import
- Use @hmcts/subscription (singular) instead
- Update schema discovery to include correct packages
- Fix test imports

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@alexbottenberg
alexbottenberg changed the base branch from master to feature/VIBE-300 February 9, 2026 14:49
alexbottenberg and others added 2 commits February 9, 2026 14:55
- Change from 'else if' to 'if' for hasNoListTypes error check
- This allows both errorNoLocations and errorNoListTypes to show when applicable
- When locations are selected but all list types removed, display errorNoListTypes and selectListTypesLink
- Add test case for this scenario

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove hasNoSubscriptions wrapper that hid all tables
- Now locations table and language table always display
- List types table shows with 'Select list types' link when empty
- Ensures users can see and manage locations and language even when all list types removed

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/verified-pages/src/pages/subscription-management/index.test.ts (1)

1-19: ⚠️ Potential issue | 🟠 Major

Fix incorrect mock package name.

The mock declares @hmcts/subscriptions (plural) but should be @hmcts/subscription (singular) to match the import on line 1 and the actual package name. The current mock will not intercept the module, causing tests to use real code or fail when .mockResolvedValue() is called.

Proposed fix
-vi.mock("@hmcts/subscriptions", () => ({
+vi.mock("@hmcts/subscription", () => ({
   getAllSubscriptionsByUserId: vi.fn()
 }));
🟡 Minor comments (20)
apps/web/src/assets/js/index.ts-3-3 (1)

3-3: ⚠️ Potential issue | 🟡 Minor

Remove the non-essential import comment.

The note is descriptive rather than explanatory, so it adds noise.

Suggested change
-import { initFilterPanel } from "@hmcts/web-core/src/assets/js/filter-panel.js"; // Updated icons
+import { initFilterPanel } from "@hmcts/web-core/src/assets/js/filter-panel.js";

Based on learnings: Do not add comments unless meaningful - explain why, not what.

libs/subscription-list-types/src/config.ts-7-8 (1)

7-8: ⚠️ Potential issue | 🟡 Minor

Use SCREAMING_SNAKE_CASE for exported constants.

Please rename these exports and adjust downstream imports (e.g., schema discovery) to align with the constants naming rule.

Suggested change
-export const prismaSchemas = path.join(__dirname, "../prisma");
-export const moduleRoot = __dirname;
+export const PRISMA_SCHEMAS = path.join(__dirname, "../prisma");
+export const MODULE_ROOT = __dirname;

As per coding guidelines: Constants should use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).

libs/location/src/location-data.ts-67-70 (1)

67-70: ⚠️ Potential issue | 🟡 Minor

Align Welsh wording for Single Justice Procedure.

The Welsh name currently uses “Gweithdrefn Un Ynad”; the preferred term elsewhere in the repo is “Gweithdrefn Ynad Sengl”. Consider updating for consistency.

✏️ Suggested change
-      welshName: "Gweithdrefn Un Ynad",
+      welshName: "Gweithdrefn Ynad Sengl",
Based on learnings "For Single Justice Procedure (SJP) list types in libs/list-types/common/src/mock-list-types.ts, the Welsh translation "Gweithdrefn Ynad Sengl" is the preferred terminology for "Single Justice Procedure" rather than "Gweithdrefn Cyfiawnder Sengl"."
libs/subscription-list-types/tsconfig.json-9-10 (1)

9-10: ⚠️ Potential issue | 🟡 Minor

Exclude assets from library tsconfig.

The exclude list should include src/assets/ per repository guidelines for library modules. Other libraries in the codebase (system-admin-pages, verified-pages, public-pages, admin-pages) follow this pattern.

✏️ Suggested change
-  "exclude": ["**/*.test.ts", "**/*.spec.ts", "dist", "node_modules"]
+  "exclude": ["**/*.test.ts", "**/*.spec.ts", "dist", "node_modules", "src/assets/"]
docs/tickets/296/tasks.md-148-152 (1)

148-152: ⚠️ Potential issue | 🟡 Minor

Minor grammar tweak for clarity.
This line reads more cleanly with “it”.

Proposed fix
-- [x] Add module to Vite build config if has assets (no assets needed)
+- [x] Add module to Vite build config if it has assets (no assets needed)
libs/verified-pages/src/pages/subscription-list-language/cy.ts-8-10 (1)

8-10: ⚠️ Potential issue | 🟡 Minor

Remove duplicated phrase in Welsh error message.
The current string repeats “Dewiswch”, which reads like a paste error.

Proposed fix
-  errorRequired: "Dewiswch fersiwn o'r math o restr Dewiswch opsiwn",
+  errorRequired: "Dewiswch fersiwn o'r math o restr i barhau",
libs/verified-pages/src/pages/subscription-list-language/en.ts-8-10 (1)

8-10: ⚠️ Potential issue | 🟡 Minor

Minor copy fix in validation message.

Proposed fix
-  errorRequired: "Please select version of the list type to continue",
+  errorRequired: "Please select a version of the list type to continue",
libs/verified-pages/src/pages/subscription-by-location/index.test.ts-26-124 (1)

26-124: ⚠️ Potential issue | 🟡 Minor

Replace as any session mocks with a local type.

Lines 31, 65, 87, 106, and 124 all use as any on session objects, which violates strict mode. Define a MockSession type to keep tests properly typed.

Proposed fix
+type MockSession = {
+  listTypeSubscription?: { selectedLocationIds: number[] };
+  save?: (callback: (err?: Error | null) => void) => void;
+};
+
 describe("subscription-by-location", () => {
   let mockRequest: Partial<Request>;
   let mockResponse: Partial<Response>;
 
   beforeEach(() => {
     vi.clearAllMocks();
     mockRequest = {
       query: {},
       body: {},
-      session: {} as any
+      session: {} as MockSession
     };
libs/verified-pages/src/pages/subscription-add-method/index.test.ts-5-8 (1)

5-8: ⚠️ Potential issue | 🟡 Minor

Import NextFunction and type middleware parameters.

Middleware mocks use any for request/response/next parameters, which violates strict mode requirements. Import NextFunction from express and add proper type annotations to requireAuth and blockUserAccess handlers.

Proposed fix
-import type { Request, Response } from "express";
+import type { NextFunction, Request, Response } from "express";
@@
-  requireAuth: vi.fn(() => (_req: any, _res: any, next: any) => next()),
-  blockUserAccess: vi.fn(() => (_req: any, _res: any, next: any) => next())
+  requireAuth: vi.fn(() => (_req: Request, _res: Response, next: NextFunction) => next()),
+  blockUserAccess: vi.fn(() => (_req: Request, _res: Response, next: NextFunction) => next())
docs/tickets/296/review.md-19-19 (1)

19-19: ⚠️ Potential issue | 🟡 Minor

Hyphenate “high-priority” when used as a compound adjective.
Improves readability and consistency.

✍️ Suggested edits
-## ⚠️ HIGH PRIORITY Issues
+## ⚠️ High-priority issues

-**Deploy after addressing HIGH PRIORITY issues `#1`, `#2`, and `#3`.**
+**Deploy after addressing high-priority issues `#1`, `#2`, and `#3`.**

-**Next Review**: After HIGH PRIORITY fixes
+**Next Review**: After high-priority fixes

Also applies to: 447-447, 459-459

docs/tickets/296/review.md-141-141 (1)

141-141: ⚠️ Potential issue | 🟡 Minor

Add the missing article for clarity.
“This back link points to the wrong page” reads more naturally.

✍️ Suggested edit
-**Issue**: Since Pages 3-4 are skipped, this back link points to wrong page.
+**Issue**: Since Pages 3-4 are skipped, this back link points to the wrong page.
libs/verified-pages/src/pages/subscription-locations-review/index.test.ts-26-31 (1)

26-31: ⚠️ Potential issue | 🟡 Minor

Replace as any with a typed session helper.
This keeps tests aligned with strict typing and avoids masking shape errors.

♻️ Suggested direction
+type ListTypeSubscriptionSession = {
+  listTypeSubscription?: {
+    selectedLocationIds: number[];
+  };
+};
+
 describe("subscription-locations-review", () => {
-  let mockRequest: Partial<Request>;
+  let mockRequest: Partial<Request & { session: ListTypeSubscriptionSession }>;
   let mockResponse: Partial<Response>;

   beforeEach(() => {
     vi.clearAllMocks();
     mockRequest = {
       query: {},
-      session: {} as any
+      session: {}
     };

Apply the same pattern to the later session assignments.

As per coding guidelines: "Use TypeScript strict mode. Avoid any without justification."

Also applies to: 42-47, 69-74, 91-96, 114-119, 136-141

libs/verified-pages/src/pages/subscription-add-method/index.ts-26-56 (1)

26-56: ⚠️ Potential issue | 🟡 Minor

Validate unexpected subscriptionMethod values instead of silently redirecting.
At present any non-empty value skips the error path and redirects; treating unknown values as invalid keeps behaviour consistent.

🔧 Proposed fix
-  if (!subscriptionMethod) {
+  if (!subscriptionMethod || !["court", "case", "reference"].includes(subscriptionMethod)) {
     if (!res.locals.navigation) {
       res.locals.navigation = {};
     }
     res.locals.navigation.verifiedItems = buildVerifiedUserNavigation(req.path, locale);
docs/tickets/296/ticket.md-39-41 (1)

39-41: ⚠️ Potential issue | 🟡 Minor

Fix verb agreement in the notification sentence.
“who are subscribe” should be “who are subscribed” (or “who are subscribing”).

✏️ Proposed fix
- * To trigger subscription notification email for list type, when publication is received, all the subscribers who are subscribe to that list type and language will get notification email.
+ * To trigger subscription notification email for list type, when publication is received, all the subscribers who are subscribed to that list type and language will get notification email.
docs/tickets/296/IMPLEMENTATION_SUMMARY.md-231-246 (1)

231-246: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks.
markdownlint flags the output blocks without a language; please add text (or another suitable language) to the fence.

✏️ Proposed fix
-```
+```text
 `@hmcts/subscription-list-types`:
   ✓ queries.test.ts (9 tests)
   ✓ service.test.ts (9 tests)
   Total: 18 tests passing
@@
 `@hmcts/verified-pages`:
   ✓ subscription-add-method/index.test.ts (7 tests)
   ✓ subscription-list-types/index.test.ts (8 tests)
   ✓ subscription-list-language/index.test.ts (9 tests)
   ✓ subscription-confirm/index.test.ts (10 tests)
   ✓ subscription-management/index.test.ts (3 tests)
   Total: 148 tests passing (37 new)
</details>

</blockquote></details>
<details>
<summary>docs/tickets/296/ticket.md-245-251 (1)</summary><blockquote>

`245-251`: _⚠️ Potential issue_ | _🟡 Minor_

**Replace Welsh placeholders with real translations.**  
There are still “Welsh placeholder” entries (e.g., here and later in the document). This makes the bilingual specification incomplete; please supply the proper Welsh strings or mark as TODO with an owner/date.

</blockquote></details>
<details>
<summary>libs/verified-pages/src/pages/subscription-locations-review/index.njk-1-23 (1)</summary><blockquote>

`1-23`: _⚠️ Potential issue_ | _🟡 Minor_

**Add conditional error summary handling for this page.**  
The template currently lacks a `govukErrorSummary` block, so any validation/session errors would not be announced. Please add the macro import and a conditional summary near the heading.  
  
<details>
<summary>🛠️ Proposed fix</summary>

```diff
 {% extends "layouts/base-template.njk" %}
 {% from "govuk/components/button/macro.njk" import govukButton %}
 {% from "govuk/components/back-link/macro.njk" import govukBackLink %}
+{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
@@
   <div class="govuk-grid-column-two-thirds">
     <h1 class="govuk-heading-l">{{ heading }}</h1>
+
+    {% if errors %}
+      {{ govukErrorSummary({
+        titleText: errorSummaryTitle,
+        errorList: errors
+      }) }}
+    {% endif %}
As per coding guidelines: “libs/*/src/pages/**/*.njk: Nunjucks templates must extend `layouts/base-template.njk`, use govuk macros from govuk/components, and include error handling with govukErrorSummary.”
docs/tickets/296/plan.md-57-81 (1)

57-81: ⚠️ Potential issue | 🟡 Minor

Specify fenced code block languages (MD040).

Several fenced blocks omit the language identifier. Please add language tags (e.g., text, prisma, typescript) to all fenced blocks.

Example fix
-```
+```text
 libs/subscription-list-types/
 ├── prisma/
 │   └── schema.prisma              # List type subscription schema
 └── src/
libs/verified-pages/src/pages/subscription-by-location/index.njk-17-23 (1)

17-23: ⚠️ Potential issue | 🟡 Minor

Add a govukErrorSummary block when errors are present.

This template has no error summary hook, which breaks the error‑handling requirement for pages with forms.
As per coding guidelines: “Nunjucks templates must extend layouts/base-template.njk, use govuk macros from govuk/components, and include error handling with govukErrorSummary.”

libs/verified-pages/src/pages/subscription-by-location/index.ts-227-234 (1)

227-234: ⚠️ Potential issue | 🟡 Minor

Unchecking locations doesn’t remove them from the session.

The POST handler unions with existing IDs, so unchecked locations persist and the user must remove them elsewhere. If you want form semantics, replace the stored list with the current selection.

Proposed fix
-  const existingLocationIds = req.session.listTypeSubscription.selectedLocationIds || [];
-
-  // Merge new selections with existing ones and remove duplicates
-  const mergedLocationIds = [...new Set([...existingLocationIds, ...selectedLocationIds])];
-
-  // Store merged location IDs (empty array is valid - no validation required)
-  req.session.listTypeSubscription.selectedLocationIds = mergedLocationIds;
+  // Store current selection (empty array is valid - no validation required)
+  req.session.listTypeSubscription.selectedLocationIds = selectedLocationIds;
🧹 Nitpick comments (11)
libs/verified-pages/src/pages/subscription-management/index.njk (1)

194-194: Minor: Missing text-align: right style for consistency.

Other action cells in this template (lines 60, 89, 122, 155) include style="text-align: right;" on the <td> element. Consider adding it here for visual consistency.

🔧 Suggested fix
-            <td class="govuk-table__cell govuk-table__cell--numeric">
+            <td class="govuk-table__cell govuk-table__cell--numeric" style="text-align: right;">
libs/verified-pages/src/pages/subscription-management/index.ts (1)

36-49: Consider hoisting languageDisplay outside the map.

The languageDisplay object is recreated for each subscription. Moving it before the .map() call would be marginally more efficient and cleaner.

♻️ Suggested refactor
+    const languageDisplay = {
+      ENGLISH: locale === "cy" ? "Saesneg" : "English",
+      WELSH: locale === "cy" ? "Cymraeg" : "Welsh",
+      BOTH: locale === "cy" ? "Cymraeg a Saesneg" : "English and Welsh"
+    };
+
     const listTypeSubscriptionsWithDetails = listTypeSubscriptions.map((sub) => {
       const listType = mockListTypes.find((lt) => lt.id === sub.listTypeId);
-      const languageDisplay = {
-        ENGLISH: locale === "cy" ? "Saesneg" : "English",
-        WELSH: locale === "cy" ? "Cymraeg" : "Welsh",
-        BOTH: locale === "cy" ? "Cymraeg a Saesneg" : "English and Welsh"
-      };

       return {
         ...sub,
         listTypeName: listType ? (locale === "cy" ? listType.welshFriendlyName : listType.englishFriendlyName) : "Unknown",
         languageDisplay: languageDisplay[sub.language as keyof typeof languageDisplay] || sub.language
       };
     });
libs/verified-pages/src/pages/subscription-by-location/index.ts (2)

17-33: Move interfaces and module augmentation to the bottom of the file.

Current ordering places types before the handlers, which breaks the module ordering convention and makes the file harder to scan.
As per coding guidelines: “Module ordering: constants outside function scope at top, exported functions next, other functions in usage order, interfaces and types at bottom.”


191-210: Replace as any cast with Express Request type augmentation.

This avoids bypassing strict typing. Add a module declaration for csrfToken on the Express Request interface at the top of the file, then use req.csrfToken?.() ?? "" instead.

Proposed fix
+declare module "express-serve-static-core" {
+  interface Request {
+    csrfToken?: () => string;
+  }
+}
+
 declare module "express-session" {
   interface SessionData {
     listTypeSubscription?: ListTypeSubscriptionSession;
   }
 }
 ...
-    csrfToken: (req as any).csrfToken?.() || ""
+    csrfToken: req.csrfToken?.() ?? ""
libs/verified-pages/src/pages/subscription-list-language/index.ts (1)

6-15: Move interfaces/module augmentation to the bottom of the file.

Keeps handlers and core flow first, as per ordering convention.
As per coding guidelines: “Module ordering: constants outside function scope at top, exported functions next, other functions in usage order, interfaces and types at bottom.”

libs/verified-pages/src/pages/subscription-locations-review/index.ts (1)

7-23: Move interfaces/module augmentation to the bottom of the file.

Keeps primary handlers near the top and reduces cognitive load.
As per coding guidelines: “Module ordering: constants outside function scope at top, exported functions next, other functions in usage order, interfaces and types at bottom.”

libs/verified-pages/src/pages/subscription-list-types/index.ts (2)

7-27: Move interfaces/module augmentation to the bottom of the file.

Helps align file structure with the agreed ordering rules.
As per coding guidelines: “Module ordering: constants outside function scope at top, exported functions next, other functions in usage order, interfaces and types at bottom.”


29-58: Pass explicit locale to localeCompare() for correct Welsh collation.

Without an explicit locale parameter, localeCompare() uses the runtime's default locale, which may not apply Welsh collation rules for digraphs (ch, dd, ff, ng, ll, ph, rh, th). Update the sorting logic to pass the locale explicitly:

Proposed fix
-          return aName.localeCompare(bName);
+          const sortLocale = locale === "cy" ? "cy" : "en";
+          return aName.localeCompare(bName, sortLocale);
libs/verified-pages/src/pages/subscription-confirm/index.ts (2)

9-20: Move ListTypeSubscriptionSession to the module footer.
Keeps module ordering consistent with repo rules.

As per coding guidelines: Module ordering: constants outside function scope at top, exported functions next, other functions in usage order, interfaces and types at bottom.


205-257: Consider extracting a shared view‑model builder.
GET and the error branch rebuild the same location/list‑type/language rows; a shared helper would prevent divergence and reduce maintenance.

e2e-tests/tests/subscription-list-types.spec.ts (1)

4-217: Consolidate into a single journey test.
The flow is split across three @nightly tests, but the guideline asks for one complete user journey with validations, Welsh checks, and a11y inline to minimise test count. Consider folding the duplicate/validation scenarios into the main journey (or parameterising within a single test).

As per coding guidelines: E2E tests in Playwright should minimize test count with one test per complete user journey, including validations, Welsh translations, and accessibility checks inline rather than in separate tests.

Comment on lines +12 to +28
export async function createListTypeSubscriptions(userId: string, listTypeIds: number[], language: string) {
const currentCount = await countListTypeSubscriptionsByUserId(userId);

if (currentCount + listTypeIds.length > MAX_LIST_TYPE_SUBSCRIPTIONS) {
throw new Error(`Maximum ${MAX_LIST_TYPE_SUBSCRIPTIONS} list type subscriptions allowed`);
}

for (const listTypeId of listTypeIds) {
const duplicate = await findDuplicateListTypeSubscription(userId, listTypeId, language);
if (duplicate) {
throw new Error(`Already subscribed to list type ${listTypeId} with language ${language}`);
}
}

const subscriptions = await Promise.all(listTypeIds.map((listTypeId) => createListTypeSubscriptionRecord(userId, listTypeId, language)));

return subscriptions;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find libs/subscription-list-types -type f -name "*.ts" | head -20

Repository: hmcts/cath-service

Length of output: 518


🏁 Script executed:

wc -l libs/subscription-list-types/src/subscription-list-type/service.ts

Repository: hmcts/cath-service

Length of output: 131


🏁 Script executed:

cat -n libs/subscription-list-types/src/subscription-list-type/service.ts

Repository: hmcts/cath-service

Length of output: 2317


🏁 Script executed:

cat -n libs/subscription-list-types/src/subscription-list-type/queries.ts

Repository: hmcts/cath-service

Length of output: 1765


🏁 Script executed:

cat -n libs/subscription-list-types/src/subscription-list-type/service.test.ts

Repository: hmcts/cath-service

Length of output: 7093


🏁 Script executed:

find . -name "*.prisma" -o -name "schema.prisma" | head -10

Repository: hmcts/cath-service

Length of output: 317


🏁 Script executed:

cat -n libs/subscription-list-types/prisma/schema.prisma

Repository: hmcts/cath-service

Length of output: 1137


🏁 Script executed:

rg '\$transaction' --type ts --max-count 5

Repository: hmcts/cath-service

Length of output: 1561


🏁 Script executed:

cat -n libs/subscription/src/repository/queries.ts | head -50

Repository: hmcts/cath-service

Length of output: 1599


🏁 Script executed:

rg '\$transaction' -A 15 libs/subscription/src/repository/queries.ts | head -40

Repository: hmcts/cath-service

Length of output: 421


🏁 Script executed:

rg 'createListTypeSubscriptions' -A 5 -B 2 --type ts | grep -A 5 -B 2 'duplicate\|listTypeIds'

Repository: hmcts/cath-service

Length of output: 2088


Handle input duplicates and enforce the 50-subscription limit atomically.

listTypeIds can contain duplicates and will trigger a unique-constraint failure after partial inserts. Additionally, the count and duplicate checks are not transactionally isolated; concurrent requests can bypass the limit. De-duplicate the input and execute all validations and inserts within a single prisma.$transaction() call.

Proposed fix (de-duplicate input and use transaction)
-export async function createListTypeSubscriptions(userId: string, listTypeIds: number[], language: string) {
-  const currentCount = await countListTypeSubscriptionsByUserId(userId);
+export async function createListTypeSubscriptions(userId: string, listTypeIds: number[], language: string) {
+  const uniqueListTypeIds = [...new Set(listTypeIds)];
+  
+  return prisma.$transaction(async (tx) => {
+    const currentCount = await tx.subscriptionListType.count({ where: { userId } });
 
-  if (currentCount + listTypeIds.length > MAX_LIST_TYPE_SUBSCRIPTIONS) {
+    if (currentCount + uniqueListTypeIds.length > MAX_LIST_TYPE_SUBSCRIPTIONS) {
       throw new Error(`Maximum ${MAX_LIST_TYPE_SUBSCRIPTIONS} list type subscriptions allowed`);
-  }
+    }
 
-  for (const listTypeId of listTypeIds) {
-    const duplicate = await findDuplicateListTypeSubscription(userId, listTypeId, language);
+    for (const listTypeId of uniqueListTypeIds) {
+      const duplicate = await tx.subscriptionListType.findFirst({
+        where: { userId, listTypeId, language }
+      });
       if (duplicate) {
         throw new Error(`Already subscribed to list type ${listTypeId} with language ${language}`);
       }
-  }
+    }
 
-  const subscriptions = await Promise.all(listTypeIds.map((listTypeId) => createListTypeSubscriptionRecord(userId, listTypeId, language)));
+    const subscriptions = await Promise.all(uniqueListTypeIds.map((listTypeId) =>
+      tx.subscriptionListType.create({
+        data: { userId, listTypeId, language }
+      })
+    ));
 
-  return subscriptions;
+    return subscriptions;
+  });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function createListTypeSubscriptions(userId: string, listTypeIds: number[], language: string) {
const currentCount = await countListTypeSubscriptionsByUserId(userId);
if (currentCount + listTypeIds.length > MAX_LIST_TYPE_SUBSCRIPTIONS) {
throw new Error(`Maximum ${MAX_LIST_TYPE_SUBSCRIPTIONS} list type subscriptions allowed`);
}
for (const listTypeId of listTypeIds) {
const duplicate = await findDuplicateListTypeSubscription(userId, listTypeId, language);
if (duplicate) {
throw new Error(`Already subscribed to list type ${listTypeId} with language ${language}`);
}
}
const subscriptions = await Promise.all(listTypeIds.map((listTypeId) => createListTypeSubscriptionRecord(userId, listTypeId, language)));
return subscriptions;
export async function createListTypeSubscriptions(userId: string, listTypeIds: number[], language: string) {
const uniqueListTypeIds = [...new Set(listTypeIds)];
return prisma.$transaction(async (tx) => {
const currentCount = await tx.subscriptionListType.count({ where: { userId } });
if (currentCount + uniqueListTypeIds.length > MAX_LIST_TYPE_SUBSCRIPTIONS) {
throw new Error(`Maximum ${MAX_LIST_TYPE_SUBSCRIPTIONS} list type subscriptions allowed`);
}
for (const listTypeId of uniqueListTypeIds) {
const duplicate = await tx.subscriptionListType.findFirst({
where: { userId, listTypeId, language }
});
if (duplicate) {
throw new Error(`Already subscribed to list type ${listTypeId} with language ${language}`);
}
}
const subscriptions = await Promise.all(uniqueListTypeIds.map((listTypeId) =>
tx.subscriptionListType.create({
data: { userId, listTypeId, language }
})
));
return subscriptions;
});
}

Comment thread libs/verified-pages/src/pages/subscription-confirm/index.ts
Comment thread libs/verified-pages/src/pages/subscription-list-language/index.njk
Comment thread libs/verified-pages/src/pages/subscription-list-language/index.ts
Comment on lines +7 to +11
{% block backLink %}
{{ govukBackLink({
text: backLinkText,
href: "/location-name-search"
}) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

sed -n '1,220p' libs/verified-pages/src/pages/subscription-list-types/index.ts && echo "---" && rg -n "backLink" libs/verified-pages/src/pages/subscription-list-types/index.ts

Repository: hmcts/cath-service

Length of output: 4925


🏁 Script executed:

# Search for files that reference subscription-list-types or the journey flow
rg -l "subscription-list-types" --type ts --type tsx --type js --type jsx && echo "---" && rg -n "location-name-search" --type ts --type tsx --type js --type jsx | head -20

Repository: hmcts/cath-service

Length of output: 89


🏁 Script executed:

# Search for references and routing
rg -l "subscription-list-types" -t ts -t js -t jsx && echo "---" && rg -n "location-name-search" -t ts -t js -t jsx | head -30

Repository: hmcts/cath-service

Length of output: 89


🏁 Script executed:

# Search without file type restrictions
rg "subscription-list-types" | head -20 && echo "---SEPARATOR---" && rg "location-name-search" | head -20

Repository: hmcts/cath-service

Length of output: 3913


🏁 Script executed:

# Read the journey plan to understand page sequence
cat docs/tickets/296/plan.md | head -100

Repository: hmcts/cath-service

Length of output: 3851


🏁 Script executed:

# Find the subscription-locations-review page to confirm its path
find . -name "*subscription-locations-review*" -type f | head -20

Repository: hmcts/cath-service

Length of output: 44


🏁 Script executed:

# Read more of the plan file to see the full navigation flow
sed -n '1,300p' docs/tickets/296/plan.md | tail -150

Repository: hmcts/cath-service

Length of output: 5304


🏁 Script executed:

# Check if subscription-locations-review exists or what page comes before subscription-list-types
find . -name "*subscription-locations*" -o -name "*subscription-by-location*" 2>/dev/null | grep -v node_modules

Repository: hmcts/cath-service

Length of output: 180


🏁 Script executed:

# Check the subscription-locations-review controller to find its path
head -50 libs/verified-pages/src/pages/subscription-locations-review/index.ts

Repository: hmcts/cath-service

Length of output: 1756


🏁 Script executed:

# Check the test file or route definitions for subscription-locations-review
grep -r "subscription-locations-review" --include="*.ts" --include="*.js" | grep -E "(path|route|href)" | head -20

Repository: hmcts/cath-service

Length of output: 44


🏁 Script executed:

# Check test file to see what path is used
ls -la libs/verified-pages/src/pages/subscription-locations-review/ && cat libs/verified-pages/src/pages/subscription-locations-review/index.test.ts 2>/dev/null | head -50

Repository: hmcts/cath-service

Length of output: 1943


🏁 Script executed:

# Look for route definitions or main app configuration
find . -name "routes.ts" -o -name "router.ts" | head -10 && echo "---" && rg "subscription-locations-review|subscription-list-types" | grep -v "node_modules" | grep -E "router|route|app\." | head -20

Repository: hmcts/cath-service

Length of output: 204


🏁 Script executed:

# Check app.ts for route registration
grep -A 5 -B 5 "verified-pages\|subscription" apps/web/src/app.ts 2>/dev/null | head -80

Repository: hmcts/cath-service

Length of output: 480


🏁 Script executed:

# Find and check verified-pages config
find . -path "*verified-pages*/config*" -type f | head -10

Repository: hmcts/cath-service

Length of output: 138


🏁 Script executed:

# Check verified-pages config to see route definitions
cat libs/verified-pages/src/config.ts

Repository: hmcts/cath-service

Length of output: 389


Back link should return to the previous step in the workflow, not to location-name-search.

The back link is currently hard-coded to /location-name-search, but according to the subscription workflow, page 5 (list types) should return to page 4 (subscription-locations-review). Change the href to /subscription-locations-review.

Comment on lines +30 to +45
<form method="post" novalidate>
<table class="govuk-table">
<tbody class="govuk-table__body">
{% for group in groupedListTypes %}
{% for item in group.items %}
<tr class="govuk-table__row">
<td class="govuk-table__cell app-list-types-table__letter-cell">
{% if loop.first %}{{ group.letter }}{% endif %}
</td>
<td class="govuk-table__cell app-list-types-table__checkbox-cell">
<div class="govuk-checkboxes govuk-checkboxes--small app-list-types-checkbox">
<div class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input" id="listTypes-{{ item.value }}" name="listTypes" type="checkbox" value="{{ item.value }}" {% if item.checked %}checked{% endif %}>
<label class="govuk-label govuk-checkboxes__label" for="listTypes-{{ item.value }}">
<span class="govuk-visually-hidden">{{ item.text }}</span>
</label>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error summary links to #list-types, but no such anchor exists.
This breaks keyboard focus from the error summary. Add an element with id="list-types" (or update the error href to match an existing input id).

🛠️ Proposed fix
-    <form method="post" novalidate>
-      <table class="govuk-table">
+    <form method="post" novalidate>
+      <table class="govuk-table" id="list-types">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form method="post" novalidate>
<table class="govuk-table">
<tbody class="govuk-table__body">
{% for group in groupedListTypes %}
{% for item in group.items %}
<tr class="govuk-table__row">
<td class="govuk-table__cell app-list-types-table__letter-cell">
{% if loop.first %}{{ group.letter }}{% endif %}
</td>
<td class="govuk-table__cell app-list-types-table__checkbox-cell">
<div class="govuk-checkboxes govuk-checkboxes--small app-list-types-checkbox">
<div class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input" id="listTypes-{{ item.value }}" name="listTypes" type="checkbox" value="{{ item.value }}" {% if item.checked %}checked{% endif %}>
<label class="govuk-label govuk-checkboxes__label" for="listTypes-{{ item.value }}">
<span class="govuk-visually-hidden">{{ item.text }}</span>
</label>
<form method="post" novalidate>
<table class="govuk-table" id="list-types">
<tbody class="govuk-table__body">
{% for group in groupedListTypes %}
{% for item in group.items %}
<tr class="govuk-table__row">
<td class="govuk-table__cell app-list-types-table__letter-cell">
{% if loop.first %}{{ group.letter }}{% endif %}
</td>
<td class="govuk-table__cell app-list-types-table__checkbox-cell">
<div class="govuk-checkboxes govuk-checkboxes--small app-list-types-checkbox">
<div class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input" id="listTypes-{{ item.value }}" name="listTypes" type="checkbox" value="{{ item.value }}" {% if item.checked %}checked{% endif %}>
<label class="govuk-label govuk-checkboxes__label" for="listTypes-{{ item.value }}">
<span class="govuk-visually-hidden">{{ item.text }}</span>
</label>

Comment on lines +33 to +35
{% for group in groupedListTypes %}
{% for item in group.items %}
<tr class="govuk-table__row">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align grouped list type property name with controller/test data.
The template iterates group.items, but the tests assert groupedListTypes[*].listTypes. If the controller provides listTypes, this will render an empty table. Please align the property name.

🛠️ Proposed fix
-          {% for item in group.items %}
+          {% for item in group.listTypes %}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{% for group in groupedListTypes %}
{% for item in group.items %}
<tr class="govuk-table__row">
{% for group in groupedListTypes %}
{% for item in group.listTypes %}
<tr class="govuk-table__row">

alexbottenberg and others added 9 commits February 9, 2026 15:17
- Change hasNoSubscriptions logic from OR to AND (both must be empty)
- Remove errorNoLocations since locations are optional for list type subscriptions
- Users can now subscribe to list types without selecting any locations
- Add test case for list-type-only subscription flow

This aligns with the feature design where locations (pages 3-4) are optional.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added CSRF tokens to forms and controllers:
- subscription-list-language: form + 3 render calls
- subscription-add-method: form + 2 render calls
- subscription-list-types: form + 4 render calls
- subscription-confirm: form + 2 render calls

All forms now include hidden _csrf input field and controllers pass csrfToken to templates, matching the pattern used in other pages like subscription-management.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add ALLOWED_LANGUAGES constant with ['ENGLISH', 'WELSH', 'BOTH']
- Reject invalid/arbitrary language values with same error as missing input
- Prevents storing unexpected values that break downstream logic
- Add test case for invalid language value rejection

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…w step

- Change back link from /location-name-search to /subscription-locations-review
- Correctly returns to page 4 (locations review) as per workflow design
- Page 5 (list types) should go back to page 4, not skip to page 1

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…-types

- Add id="list-types" to the table element
- Fixes broken keyboard focus from error summary to form field
- Error summary href="#list-types" now correctly links to the form element

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Change test from expecting 'listTypes' property to 'items'
- Update test assertions to expect {text, value} format instead of raw list type objects
- Aligns with actual controller output from groupListTypesByLetter function

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add src/assets/ to exclude list per repository guidelines
- Aligns with pattern used in other library modules (verified-pages, admin-pages, etc.)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Import govukErrorSummary macro in template
- Add error summary block after heading
- Add errorSummaryTitle to English and Welsh content files
- Provides infrastructure for future validation error handling

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add complete mock exports for @hmcts/list-types-common
- Fix module name from @hmcts/subscriptions to @hmcts/subscription
- Add session and query objects to request mocks
- Resolves test failures from incomplete mock setups

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
alexbottenberg and others added 12 commits February 25, 2026 10:58
…ariables

- Import dotenv and load .env file from parent directory
- Add debug logging to check if template IDs are loaded
- This ensures GOVUK_NOTIFY_TEMPLATE_ID_* variables are available when Playwright config is evaluated

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Prevents spawned server processes from overriding environment variables
that were passed via command line (e.g., from Playwright webServer).

- Check if GOVUK_NOTIFY_TEST_API_KEY exists before calling dotenv.config()
- This allows Playwright to pass env vars directly in the webServer command
- Fixes issue where dotenv.config() with wrong path was clearing env vars

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Pass environment variables via webServer.env property
- Inherit all process.env variables and override specific ones
- Wait for notifications to reach 'Sent' status before asserting
- Add debug logging to verify env vars are loaded

Still failing: notifications show 'Failed' status with 'GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION environment variable is not set' even though Playwright config confirms the variable is loaded.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Environment variables were captured as constants at module load time, causing them to be empty if the module loaded before env vars were set. Changed to read process.env at runtime to ensure variables are always current.

Also fixed missing location names in enhanced notification emails by setting hasLocationSubscription: true.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add test.skip() checks to notification E2E tests that require a valid GOV.UK Notify API key. This prevents test failures in CI environments where the GitHub secrets may not be configured.

Tests affected:
- Manual upload notification test
- Subscription notification with case summary
- Multiple subscribers notification

These tests will now be skipped in CI if the API key is not available, while still running in local development where the .env file provides valid credentials.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Log full error messages and notification details when tests fail to help diagnose GOV.UK Notify API issues in CI environment.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added subscription-list-types schema to getPrismaSchemas() to work with new audit-log module.
Using package imports (@hmcts/*/config) created circular dependencies since those packages depend on @hmcts/postgres. Reverted to using direct path.join() to reference schema directories.

Also added audit-log schema to tsconfig paths for consistency.

Fixes:
- Circular dependency error in turbo
- Cannot find package errors in CI tests
- All 12 postgres tests now pass

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated notifications-node-client from 8.2.1 to 8.3.0
Cleaned up console.error debug statements that were added for CI troubleshooting. Tests are now passing consistently, so the debug logging is no longer needed.

Removed:
- Debug logs from subscription-notifications.spec.ts (24 lines)
- Debug logs from manual-upload.spec.ts (15 lines)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removed console.log statements that were added to verify environment variable loading during troubleshooting.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@ChrisS1512
ChrisS1512 changed the base branch from master to feature/VIBE-300 February 27, 2026 09:24
@ChrisS1512
ChrisS1512 changed the base branch from feature/VIBE-300 to feature/VIBE-300-subscribe-by-case-details February 27, 2026 09:24
@ChrisS1512
ChrisS1512 changed the base branch from feature/VIBE-300-subscribe-by-case-details to master February 27, 2026 09:25
@junaidiqbalmoj

Copy link
Copy Markdown
Contributor

Resolve the code conflict on this branch, once #297 merged into master, make sure this functionality is working and getting all the list types from database instead of .json file.

renovate Bot and others added 10 commits April 15, 2026 12:25
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…ption-list-types-rebased

# Conflicts:
#	apps/postgres/src/schema-discovery.ts
#	apps/web/helm/values.dev.yaml
#	e2e-tests/playwright.config.ts
#	e2e-tests/tests/bulk-unsubscribe.spec.ts
#	e2e-tests/tests/email-subscriptions.spec.ts
#	libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
#	libs/notifications/package.json
#	libs/subscription/src/repository/service.ts
#	yarn.lock
@KianKwa
KianKwa changed the base branch from master to feature/use-list-types-from-database April 22, 2026 14:26
@KianKwa KianKwa closed this Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[VIBE-307] Verified user - Select & Edit List Type (List Type Subscription Only)

4 participants