Skip to content

Feature/dev/is 11570 haapi stepper history navigation alternative - #279

Draft
luisgoncalves wants to merge 2 commits into
feature/dev/IS-11570-haapi-stepper-history-navigationfrom
feature/dev/IS-11570-haapi-stepper-history-navigation-alternative
Draft

Feature/dev/is 11570 haapi stepper history navigation alternative#279
luisgoncalves wants to merge 2 commits into
feature/dev/IS-11570-haapi-stepper-history-navigationfrom
feature/dev/IS-11570-haapi-stepper-history-navigation-alternative

Conversation

@luisgoncalves

@luisgoncalves luisgoncalves commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Proposal on top of #266. This is a quick spike; typings are not final and I didn't update tests.

  • Non-reproducible steps are replaced in history as soon as a reproducible step is found. At most the last entry is non-reproducible.
  • Change browser URL when history is changed
  • Update what is saved in stepper history when redirects are automatically followed. This is for POST-redirect-GET cases. Previously, the stepper history entry would say "triggered by" the POST action, but with the step that results of the GET (redirect) action. This means that the purpose of the redirect to created a safe point is lost. In this PR, the final GET action becomes the "triggered by" - maybe we could keep two entries instead, but this was a quick spike.

@aleixsuau

Copy link
Copy Markdown
Contributor

Proposal on top of #266. This is a quick spike; typings are not final and I didn't update tests.

  • Non-reproducible steps are replaced in history as soon as a reproducible step is found. At most the last entry is non-reproducible.
  • Change browser URL when history is changed
  • Update what is saved in stepper history when redirects are automatically followed. This is for POST-redirect-GET cases. Previously, the stepper history entry would say "triggered by" the POST action, but with the step that results of the GET (redirect) action. This means that the purpose of the redirect to created a safe point is lost. In this PR, the final GET action becomes the "triggered by" - maybe we could keep two entries instead, but this was a quick spike.
  • Regarding keeping two "triggered by" entries instead, the initial goal of the HaapiStepper history feature was to track the auth flow steps. Now the newnavigation feature adds a new requirement: "reproducible" history entries should contain the required data to reproduce them (navigate to them).

Proposal on top of #266. This is a quick spike; typings are not final and I didn't update tests.

  • Non-reproducible steps are replaced in history as soon as a reproducible step is found. At most the last entry is non-reproducible.
  • Change browser URL when history is changed
  • Update what is saved in stepper history when redirects are automatically followed. This is for POST-redirect-GET cases. Previously, the stepper history entry would say "triggered by" the POST action, but with the step that results of the GET (redirect) action. This means that the purpose of the redirect to created a safe point is lost. In this PR, the final GET action becomes the "triggered by" - maybe we could keep two entries instead, but this was a quick spike.

Brilliant simplification @luisgoncalves ❤️ !

Some notes:

  • I'd separate the URL feature to its own task so we can clarify requirements (page reloads...).
  • Regarding "triggered by": I think your proposal is actually fixing a "lie": the POST action is the triggeredByAction of the redirection step, which is never recorded in the history. The GET action the redirection step returns (nextStepResponse.actions[0]) is the correct triggeredByAction of the resulting step. I see it as a separate bug that could even go in a separate PR as a fix.
  • I left some comments for improvements you probably already accounted for. I wanted to avoid forgetting them and re-reviews; all minor.

Copilot AI 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.

Pull request overview

This PR spikes an alternative approach to HAAPI stepper history navigation by adjusting how stepper history entries are recorded (especially around automatic redirections), and by syncing those history changes into the browser’s URL/history state.

Changes:

  • Update HaapiStepper next-step processing to carry “triggered by” action/payload through automatic redirections so history entries reflect the final GET step.
  • Extend the browser history adapter to optionally update the URL when pushing/replacing history entries.
  • Rework useHaapiStepperHistoryNavigation to encode reproducibility into the browser history state and to push/replace entries accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper.tsx Propagates “triggered by” action/payload through redirect handling and normalizes action objects for downstream consumers.
src/haapi-react-sdk/haapi-stepper/feature/stepper/data-formatters/format-next-step-data.ts Adds a more specific overload for form actions to support redirect-following logic.
src/haapi-react-app/src/shared/util/browser-apis.ts Allows pushState/replaceState to optionally update the browser URL.
src/haapi-react-app/src/shared/feature/history/useHaapiStepperHistoryNavigation.ts Changes the browser-history state model and updates push/replace behavior (including URL updates).
Suppressed comments (2)

src/haapi-react-app/src/shared/feature/history/useHaapiStepperHistoryNavigation.ts:81

  • Polling steps are marked non-reproducible by isReproducibleHistoryEntry, but this hook still writes them into browser history and derives url from the polling action's model.href. That can change the address bar to the internal poll endpoint and introduces an extra history entry when polling starts (previous behavior/tests skipped polling entirely).
  const currentBrowserEntry = browserNavigation.getState() as BrowserHistoryEntryIndex | undefined | null;
  const newStepperEntry = stepperHistory[stepperHistory.length - 1];

src/haapi-react-app/src/shared/feature/history/useHaapiStepperHistoryNavigation.ts:112

  • syncStepperHistoryOnBrowserHistoryChange uses React.RefObject but this file doesn't import React, which will fail typechecking. Use the already-imported RefObject type instead (and fix the malformed spacing in the signature).
function syncStepperHistoryOnBrowserHistoryChange(
    currentBrowserHistoryEntry: BrowserHistoryEntryIndex | null,
    haapiStepperRef: React.RefObject<HaapiStepperAPI>    ,
    browserNavigation: HistoryNavigation): void {
  if (!currentBrowserHistoryEntry) {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 20 to +24
/** Updates the current history entry's state, keeping the current URL (reuses the entry). */
replaceEntry(state: unknown): void;
replaceEntry(state: unknown, url?: string): void;

/** Appends a new history entry (keeping the current URL), discarding any forward entries. */
pushEntry(state: unknown): void;

/** Moves the browser through its history by `delta` entries (negative = back, positive = forward). */
go(delta: number): void;
pushEntry(state: unknown, url?: string): void;
Comment on lines +22 to +28
type BrowserHistoryEntryIndex = {
reproducible: true;
action: HaapiStepperNextStepAction;
payload?: HaapiStepperNextStepPayload;
}

interface BrowserHistoryEntryIndex {
index: number;
}

interface BrowserHistory {
entries: BrowserHistoryEntryData[];
index: number;
}
} | {
reproducible: false;
};
import { browserHistoryNavigation, type HistoryNavigation } from '../../util/browser-apis';
import { isReproducibleHistoryEntry } from './reproducible-action';

interface BrowserHistoryEntryData {

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.

index seems too limited (number) to describe the data it contains.

reproducible: false
};

const url = 'href' in newStepperEntry.triggeredByAction

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.

This third occurrence of 'href' in X probably deserves a util method (isLink) that avoids duplication while documenting the intent.


const url = 'href' in newStepperEntry.triggeredByAction
? newStepperEntry.triggeredByAction.href
: newStepperEntry.triggeredByAction.subtype === 'form'

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.

Probably HAAPI_ACTION_TYPES.FORM. It should have warn you 🤔


async function processHaapiNextStep(params: ProcessHaapiNextStepParams): Promise<{
nextStepData?: HaapiStepperStep | null;
nextStepData?: {

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.

Probably already accounted for, but just in case so I don't forget:

  • We could create the nextStepData interface and then HaapiStepperHistoryEntry extends it with the timestamp
  • Helpers (handlePollingStep, handleAuthenticationOrRegistrationStep, handleCompletedStep) should probably return nextStepData.
  • Some redundancy in the nextStepData construction. Maybe a helper getNextStepData could simplify.

@luisgoncalves

Copy link
Copy Markdown
Contributor Author

Brilliant simplification @luisgoncalves ❤️ !

Some notes:

* I'd separate the URL feature to its own task so we can clarify requirements (page reloads...).

* Regarding "triggered by": I think your proposal is actually fixing a "lie": the POST action is the `triggeredByAction` of the redirection step, which is never recorded in the history. The GET action the redirection step returns (`nextStepResponse.actions[0]`) is the correct `triggeredByAction` of the resulting step.  I see it as a separate bug that could even go in a separate PR  as a fix.

* I left some comments for improvements you probably already accounted for. I wanted to avoid forgetting them and re-reviews; all minor.

Thanks! As we discussed on the side, I'll put up smaller PRs on top of the base PR for the different changes I drafted here. I'll keep this one as draft while I do that, but I'll drop it in the end.

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.

3 participants