Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Show a proper 404 / error screen on detail pages instead of an infinite spinner - #3302

Merged
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen
Sep 2, 2026
Merged

Show a proper 404 / error screen on detail pages instead of an infinite spinner#3302
zackcl merged 14 commits into
devfrom
feature/3129-details-page-404-screen

Conversation

@zackcl

@zackclzackcl commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Resolves#3129

Opening a detail page (experiment, feature flag, or segment) with an ID that can't be loaded currently leaves the user on an endless spinner. This PR replaces that with a proper error screen on all three pages.

CaseWhat the user sees now
Entity doesn't exist — wrong link, malformed ID, or deleted by someone else"Experiment not found" + Back to Experiments button (same for flags/segments)
Unexpected failure (5xx, network error)"Something went wrong" + Try Again and Back buttons
Detail URL without an ID (/home/detail/)Redirects to the list page

Changes

  • Added a shared CommonPageErrorComponent (not-found / load-failed variants) to the standalone component lib, plus i18n strings. All three detail pages use it.
  • Added detailsPageError to the experiments / feature-flags / segments stores: the fetch-by-id failure actions now carry the failed ID and error type, new route-scoped selectors expose it, and the experiments effect got its missing catchError — the root cause of the endless spinner.
  • The three detail-page content components render the error screen, and it takes priority over cached list data, so an entity deleted elsewhere shows "not found" instead of a stale details page. Once a route-scoped error is established, it remains visible during retries until a fetch for the same entity succeeds, so cached editable content and stale breadcrumb names do not reappear.
  • Non-canonical IDs show not-found without firing a request. The check (isCanonicalEntityId) is the lowercase-only subset of the backend's @IsUUID() rule: the backend rejects malformed IDs with 400 (not 404), and the app only generates lowercase URLs.
  • The "Network call failed" toast is skipped for 404s on the three details-page fetches only (opt-in HttpContextToken set in the data services) — the page communicates the error itself now; preview-user and all other non-details requests keep their existing toast behavior.
  • Experiment detail fetches cancel an older contextual detail request when the route changes or the user retries, while non-details requests for different experiment IDs remain concurrent. Only contextual failures can write detailsPageError, so preview-user and other background failures cannot overwrite the details page's error state.
  • Added focused tests for each fetch effect's malformed / 404 / 5xx paths (plus the experiment stats-only-failure case), the route-scoped error selectors, the interceptor's scoped 404 suppression, and the canonical-ID check.
  • Fallout: updated the affected specs, added the new state field to the local-storage initial states, and added the missing @shared-component-lib alias to jest.config.js.
  • Added explicit ID-less detail redirects, plus requireRouteParam for Angular's trailing-empty-segment case.
  • Added regression tests for the shared error component, accessibility announcement, retry-time error retention, request cancellation/error ownership, data-service HttpContext opt-in, and slash/no-slash routing behavior.

Screenshots

Screenshot 2026-09-01 at 5 31 47 PMScreenshot 2026-09-01 at 5 32 12 PM

@zackclzackcl self-assigned this Aug 28, 2026

CopilotAI 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

Adds explicit not-found and load-failure states to experiment, feature-flag, and segment detail pages.

Changes:

  • Introduces a reusable localized page-error component.
  • Tracks route-specific loading errors in NgRx stores and supports retries.
  • Adds ID validation, ID-less route handling, and scoped 404-toast suppression.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
packages/frontend/projects/upgrade/src/assets/i18n/en.jsonAdds error-page translations.
packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/index.tsExports the error component.
.../common-page-error/common-page-error.model.tsDefines error models and ID validation.
.../common-page-error/common-page-error.model.spec.tsTests ID validation.
.../common-page-error/common-page-error.component.tsImplements shared error UI logic.
.../common-page-error/common-page-error.component.scssStyles the error screen.
.../common-page-error/common-page-error.component.htmlRenders error actions and messaging.
.../segment-details-page-content.component.tsConnects segment errors and retry.
.../segment-details-page-content.component.htmlPrioritizes segment error rendering.
.../require-route-param.guard.tsRedirects ID-less detail routes.
.../feature-flag-details-page-content.component.tsConnects flag errors and retry.
.../feature-flag-details-page-content.component.htmlPrioritizes flag error rendering.
.../experiment-details-page-content.component.tsConnects experiment errors and retry.
.../experiment-details-page-content.component.htmlPrioritizes experiment error rendering.
.../dashboard-routing.module.tsApplies detail-route parameter guards.
.../segments/store/segments.selectors.tsSelects route-scoped segment errors.
.../segments/store/segments.selectors.spec.tsTests segment error selection.
.../segments/store/segments.reducer.tsStores segment detail failures.
.../segments/store/segments.reducer.spec.tsTests segment error transitions.
.../segments/store/segments.model.tsExtends segment state.
.../segments/store/segments.effects.tsClassifies segment fetch failures.
.../segments/store/segments.effects.spec.tsTests segment failure branches.
.../segments/store/segments.actions.tsAdds segment failure metadata.
.../segments/segments.service.tsExposes segment detail errors.
.../segments/segments.data.service.tsEnables contextual 404 handling.
.../local-storage/local-storage.service.tsInitializes new error state.
.../local-storage/local-storage.service.spec.tsUpdates persisted-state expectations.
.../http-interceptors/http-error.interceptor.tsSuppresses opted-in 404 notifications.
.../http-interceptors/http-error.interceptor.spec.tsTests notification suppression.
.../http-interceptors/http-context-tokens.tsDefines the 404 context token.
.../feature-flags/store/feature-flags.selectors.tsSelects route-scoped flag errors.
.../feature-flags/store/feature-flags.selectors.spec.tsTests flag error selection.
.../feature-flags/store/feature-flags.reducer.tsStores flag detail failures.
.../feature-flags/store/feature-flags.model.tsExtends feature-flag state.
.../feature-flags/store/feature-flags.effects.tsHandles malformed, empty, and failed fetches.
.../feature-flags/store/feature-flags.effects.spec.tsTests flag fetch branches.
.../feature-flags/store/feature-flags.actions.tsAdds flag failure metadata.
.../feature-flags/feature-flags.service.tsExposes flag detail errors.
.../feature-flags/feature-flags.data.service.tsEnables contextual 404 handling.
.../experiments/store/experiments.selectors.tsSelects route-scoped experiment errors.
.../experiments/store/experiments.selector.spec.tsTests experiment error selection.
.../experiments/store/experiments.reducer.tsStores experiment detail failures.
.../experiments/store/experiments.reducer.spec.tsTests experiment error transitions.
.../experiments/store/experiments.model.tsExtends experiment state.
.../experiments/store/experiments.effects.tsHandles fetch and auxiliary-stat failures.
.../experiments/store/experiments.effects.spec.tsTests experiment fetch branches.
.../experiments/store/experiments.actions.tsAdds experiment failure metadata.
.../experiments/experiments.service.tsExposes experiment detail errors.
.../experiments/experiments.data.service.tsEnables contextual 404 handling.
.../experiments/experiments.data.service.spec.tsTests the request context token.
packages/frontend/jest.config.jsMaps shared-component aliases in Jest.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CopilotAI 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

Copilot reviewed 51 out of 51 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:18

  • This guard only runs after segments/detail/:segmentId matches. /segments/detail has no parameter segment, so it bypasses the guard and reaches the global ** redirect to /home instead of the Segments list. Add an explicit ID-less route before the parameterized route; keep this guard for the trailing-empty-segment case.
    packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts:91
  • This guard cannot handle /featureflags/detail because that URL does not match a route requiring :flagId. It falls through to the global wildcard and redirects to /home, contrary to the intended Feature Flags list destination. Add an explicit ID-less redirect before this parameterized route.
    packages/frontend/projects/upgrade/src/app/core/experiments/experiments.data.service.ts:89
  • getExperimentById is also used by ExperimentService.selectExperimentById for the Preview User editor (preview-user.component.ts:156), where no page-level 404 UI exists. Setting this token unconditionally suppresses the only 404 feedback in that flow. Pass contextual handling from the details-page request (or split the details fetch) so non-details callers retain the generic notification.
 return this.http.get(url, { context: new HttpContext().set(HANDLES_404_CONTEXTUALLY, true) });

packages/frontend/projects/upgrade/src/app/shared-standalone-component-lib/components/common-page-error/common-page-error.component.html:1

  • This error state replaces the spinner asynchronously, but the new content has no live-region semantics or focus change, so screen-reader users are not notified that loading finished with an error. Mark the inserted error container as an alert (or implement equivalent focus management) so the title and recovery actions are announced.

…for non-details fetches, and announce the error state to screen readers

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts:344

  • detailsPageError is a single value, but this effect intentionally keeps requests for different IDs alive. After navigating from experiment A to B, if B fails and then A's older request fails, A overwrites B's error; the route-scoped selector hides A's error and B returns to the spinner this PR is meant to remove. Preserve concurrent preview-user requests without letting stale detail requests share this slot—for example, cancel any prior contextual details fetch when a newer contextual fetch starts, and ensure non-contextual/background failures do not write details-page error state (or key errors by entity ID).
 takeUntil(
this.actions$.pipe(
ofType(experimentAction.actionGetExperimentById),
filter((newerAction) => newerAction !== action && newerAction.experimentId === experimentId)
)

CopilotAI 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

Copilot reviewed 53 out of 53 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

CopilotAI 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

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

@zackcl
zackcl marked this pull request as ready for review August 31, 2026 13:41
Comment threadpackages/frontend/projects/upgrade/src/assets/i18n/en.json Outdated
@danoswaltCL

Copy link
Copy Markdown
Collaborator

seems we might as well make the GET flags return a 404 instead of a 204 like the other ones, I don't think there should be a reason it's different.

@danoswaltCL

Copy link
Copy Markdown
Collaborator

@zackcl the PR to update feature-flags GET call to 404 has been merged into dev

…age-404-screen
# Conflicts:
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.spec.ts
#	packages/frontend/projects/upgrade/src/app/core/experiments/store/experiments.effects.ts
#	packages/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts
#	packages/frontend/projects/upgrade/src/app/features/dashboard/dashboard-routing.module.ts
@zackcl

zackcl commented Sep 2, 2026

Copy link
Copy Markdown
CollaboratorAuthor

@bcb37@danoswaltCL Addressed the latest feedback: consolidated the duplicate error translations, clarified the not-found message with the current environment, and aligned feature flag handling with the backend’s 404 response. Feel free to take another look!

@zackcl
zackcl requested a review from bcb37September 2, 2026 14:22
@zackcl
zackcl merged commit bfc048e into devSep 2, 2026
5 checks passed
@zackcl
zackcl deleted the feature/3129-details-page-404-screen branch September 2, 2026 17:50
Sign up for freeto 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.

proper "404" screen on details page

4 participants

@zackcl@danoswaltCL@bcb37