---
title: "Application Stepper and Review Feedback"
slug: application-stepper-review-feedback
section: Applicant Journey
status: stable
description: "How the Applicant Portal (PPPToo) marks steps as needing changes in the stepper, journey map and progress counts, including the Hub-supplied Camp Contract compliance feedback."
last-updated: 2026-10-07
---

# Application Stepper and Review Feedback

How the Applicant Portal (PPPToo) marks steps as needing changes in the stepper, journey map and progress counts, including the Hub-supplied Camp Contract compliance feedback.

## Overview

The Applicant Portal shows a step as complete only when its workflow status is completed and no review or compliance feedback is open against it. A completed step that staff have sent back, or that carries Hub compliance feedback, is shown with an error state and a `!` marker and is not counted in progress. Compliance feedback does not itself lock the step, so the applicant can correct and resubmit. The dashboard journey is built from the lean journey projection, which omits compliance feedback, so there Camp Contract compliance feedback does not change a step; staff review attention does. Everything on this page is presentation logic in the PPPToo client plus one BFF projection; the decisions themselves (staff rejection, compliance evaluation) are made in the Globus Hub. See [PPPToo — Participant Portal v2](/docs/pppto-architecture) for the portal architecture.

> **Raw status versus applicant-facing completion**
>
> `isDone` still means the raw lifecycle status is `completed`. Navigation, the journey map and progress use the stricter `isCompleteForApplicant`, so a step can be `completed` in the workflow and still render as incomplete to the applicant.

## Attention and Completion Model

### Predicates

The predicates live in `client/src/lib/steps.completion.ts` and are re-exported from `client/src/lib/steps.ts`.

| Predicate | Meaning |
| --- | --- |
| `isDone` | Raw status is `completed`. Lifecycle predicate, unchanged. |
| `needsReviewAttention` | Staff review requested changes: `needsAction` is true, or status is one of `rejected`, `changes_requested`, `action_required`, `flagged`. |
| `hasComplianceFeedback` | The step carries a non-empty `complianceFeedback` array from the Hub. Presentation only; it never drives access. |
| `needsAttention` | `needsReviewAttention` or `hasComplianceFeedback`. Compliance feedback is treated exactly like a staff rejection for display. |
| `isCompleteForApplicant` | `isDone` and not `needsAttention`. |
| `needsNavigationAttention` | `needsAttention`, or for a hub step, any of its mapped sub-tasks needs attention. |
| `isNavigationComplete` | For a non-hub step, `isCompleteForApplicant`. For a hub step, see below. |

`progress()` excludes hub steps and counts the remaining steps with `isCompleteForApplicant`, so a rejected completed step lowers the percentage.

### Hub Roll-Up

Sub-task steps are hidden from the top-level stepper and reached through the hub checklist. To keep a rejected sub-task from being invisible, a hub step is treated as needing attention when any sub-task in its `hubSubtaskMap` needs attention. A hub is navigation-complete only when all of these hold:

- The hub itself does not need attention.
- It maps at least one sub-task.
- Every mapped sub-task is present in the step list.
- Every mapped sub-task is complete for the applicant.

In the hub checklist, a sub-task is passed to the engine as `completed` when complete for the applicant, `in_progress` when it is raw-done or needs attention (so a rejected sub-task is not shown as untouched), and `not_started` otherwise.

**Step review sessions (Hub).** Field-level review feedback is stored in the Hub as review sessions with items. `GET /api/applications/:id/step-review-sessions` (optional `stepKey`; 404 for an unknown application) lists an application's sessions with their items, including private draft sessions and staff-only (`internal` visibility) comments, so it requires `applicant.journey.update` rather than the broader `dashboard.view`. `PUT /api/applications/:id/step-review-sessions/draft` creates or replaces the private draft session for a step: body `stepKey`, optional `overallComment` and `supersedesReviewSessionId`, and `items` with `targetType`, optional `targetItemId`, `targetLabel`, `fieldPath` and `comment`, a `status` and a `visibility` of `internal` or `applicant` (default `applicant`). Items are replaced wholesale on each save, a `changes_requested` item needs a non-blank comment, and a superseded session must be a published session on the same step (400 otherwise). Drafts are not visible to the applicant until published through the step-review decision route. Item statuses are `not_reviewed`, `approved`, `changes_requested`, `resolved` and `applicant_resubmitted`.

## Surfaces That Apply the Model

| Surface | Behavior | Source |
| --- | --- | --- |
| Desktop stepper (`ApplicationStepperNav`) | Each step button gets an `aria-label` of the form `Title: changes requested`, `Title: complete` or `Title: incomplete`. An attention step has the `err` class and a `!` circle, and is never `done`. The connector line to the next step is lit only when the previous step is navigation-complete. A hub shows `x/y` sub-tasks complete, where `x` counts sub-tasks complete for the applicant. The active attention step is rendered with the `err` class. | PPPToo#314 |
| Phase tabs | The `done/total` count on each tab uses `isNavigationComplete`. | PPPToo#314 |
| Mobile nav (`MobileApplicationNav` in `ApplicationPage.tsx`) | Phase progress uses `isNavigationComplete`. The top rail segment is the error colour for attention, the ok colour for complete, brand for the current step, otherwise the line colour. The vertical step list uses the same two navigation predicates. | PPPToo#314 |
| Dashboard journey (`ProgramJourney`) | Nodes use `isCompleteForApplicant`. Label colour checks attention first, then done, then current. An attention node is clickable. The journey counts as complete only when the `complete` prop is true and every step is complete for the applicant, so a rejected step removes the completed-journey state, the solid track overlay and the completed bookends. The current node is the first step that is not complete for the applicant. The journey is built from the lean projection, so it reflects staff review attention only, never Camp Contract compliance feedback. | PPPToo#315 |

## Camp Contract Compliance Feedback

When the Hub finds that a Camp Counselor placement runs longer than four calendar months, the applicant sees feedback on the Camp Contract step and the step is marked as needing changes. The evaluation and its blocking of Ready for SEVIS are described on [Camp Program Rules](/docs/camp-program-rules); this section covers only the portal side.

### BFF Projection

`server/src/applications/application-step-projection.ts` adds a `complianceFeedback` property to the full step projection, for these step keys only:

- `camp_contract_v0_1`
- `swt_camp_contract_v0_1`
- `swt_camp_contract_v0_1_v0_1`

The source may use `complianceFeedback` or the snake_case alias `compliance_feedback`. Each item is kept only if it has a string `reasonCode` and `message`, an `evaluatedAt` that parses as a date, and a `freshness` of `fresh` or `stale`; malformed items are dropped. The value is `null` when the source is not an array. The lean journey projection (used for the dashboard journey) does not include `complianceFeedback`. On the client, `normaliseSteps` passes the array through unchanged, or sets `null`.

### In-Step Banner

For the reason code `CAMP_PROGRAM_DURATION_EXCEEDS_FOUR_MONTHS`, the step form renders a banner titled **Contract dates need attention** with `role="alert"`, using the same `review-feedback-banner` class as a staff rejection. The body is a fixed applicant message: the Camp Contract dates cover more than four calendar months, the applicant should correct the Contract Start Date and Contract End Date (and upload a corrected signed contract if the signed dates changed), then save and resubmit; Ready for SEVIS stays blocked until staff verify the corrected contract and update the official placement dates. If `freshness` is `stale`, the banner adds "This evaluation may be out of date." The banner is rendered in the step form of the Camp Contract step.

### Editable Correction Fields

Compliance feedback is not a lock. Read-only and submit state still come only from the step-access snapshot; compliance feedback does not change them. While the duration feedback is present, the contract start date, contract end date and camp contract upload fields are removed from the read-only field list, matched by either the field `name` or `key` in the step's `fieldsConfig`, so an approved-document lock does not prevent replacing the contract. Corrections are submitted through the generic step submit endpoint, with no Camp-specific endpoint.

### Clearing the Feedback

The portal never clears the feedback itself. The banner and the attention state remain until a refreshed step response arrives without the feedback, at which point `needsAttention` is false and a completed step counts as complete again.

## Analytics Boundary

The `rejection_envelope_shown` event, fired from the application page and the dashboard, is computed with `needsReviewAttention` rather than `needsAttention`. Compliance feedback therefore does not inflate the staff-rejection funnel. The "Changes requested" banner on the step form is shown when the step has `needsAction` and a `rejectionMessage`, and the plain applicant-message banner is shown when the step has a `globusMessage` and is not already showing that rejection; neither is gated on `needsReviewAttention` or affected by compliance feedback.

## Sources

- PPPToo#314 — mark rejected steps incomplete in stepper
- PPPToo#315 — mark rejected dashboard journey steps incomplete
- PPPToo#330 — show Camp Contract compliance feedback to applicants
- PPPToo#334 — Camp Contract compliance feedback treated like a staff rejection
- `client/src/lib/steps.completion.ts`, `client/src/lib/steps.normalisation.ts` (PPPToo, `development`)
- `client/src/components/ApplicationStepperNav.tsx`, `client/src/components/ProgramJourney.tsx`
- `client/src/pages/ApplicationPage.tsx`
- `server/src/applications/application-step-projection.ts`
