Skip to content

Real 404 pages and per-route document titles#1261

Closed
RhysSullivan wants to merge 1 commit into
ux/setup-status-retryfrom
ux/not-found-and-titles
Closed

Real 404 pages and per-route document titles#1261
RhysSullivan wants to merge 1 commit into
ux/setup-status-retryfrom
ux/not-found-and-titles

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

Unknown URLs (e.g. /default/does-not-exist, /default/org) silently rendered the Integrations dashboard under the wrong URL. Root cause: the org-slug gate treated any URL without an orgSlug param as a bare URL and canonicalized it back to the dashboard, which destroyed the router's not-found state before it could render. The browser tab title was also a single static string on every page.

The gate now skips canonicalization when the router reports a global not-found, self-host and the local app get a proper 404 page (404 / Page not found / Go home), and a small dependency-free helper sets document.title per route (Policies · Executor, API keys · Executor, and so on), including the integration name on detail pages.

Verified in the browser: garbage URLs render the 404 with the URL preserved, one-segment garbage canonicalizes cleanly, and all real routes still work. Typecheck is green.

Stacked on #1260.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 11d7e4d Commit Preview URL

Branch Preview URL
Jul 02 2026, 07:24 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 11d7e4d Jul 02 2026, 07:25 PM

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two distinct UX issues: garbage URLs silently rendered the Integrations dashboard (root cause: OrgSlugGate canonicalized not-found routes back to a valid URL before the 404 component could render), and the browser tab always showed a static app title. The gate now checks useRouterState for globalNotFound before deciding to canonicalize, and a small useExecutorDocumentTitle hook sets document.title per route.

  • OrgSlugGate fix: needsCanonicalize is broadened from urlSlug === null to urlSlug !== activeSlug && !isNotFound, correctly skipping canonicalization for genuinely unmatched paths while still redirecting bare URLs and wrong-slug URLs.
  • Per-route titles: A new executorDocumentTitle helper and useExecutorDocumentTitle hook are wired into every existing page component, producing titles like "Policies · Executor".
  • 404 pages: Both the packages/app and apps/host-selfhost roots register an identical inline NotFoundPage as notFoundComponent on the root route.

Confidence Score: 5/5

Safe to merge — the gate change is well-scoped and well-documented, and the title hook is a thin, dependency-free effect with no side-effects beyond document.title.

The OrgSlugGate fix is logically sound: checking globalNotFound from router state before canonicalizing correctly threads the needle between redirecting legitimately wrong slugs and allowing genuine 404 URLs to reach the notFoundComponent. All page components receive straightforward one-line additions. The only open gaps (no title reset on unmount, duplicated NotFoundPage) are already tracked in previous review threads.

No files require special attention beyond what prior review threads have already flagged.

Important Files Changed

Filename Overview
packages/react/src/multiplayer/org-slug-gate.tsx Core logic change: needsCanonicalize now guards against isNotFound using useRouterState, preventing silent canonicalization of unmatched URLs that should render the 404 page.
packages/react/src/lib/document-title.tsx New hook and helper for per-route document titles; no cleanup on unmount means the title persists when navigating to pages that don't call the hook (e.g. the 404 component).
packages/app/src/routes/__root.tsx Adds notFoundComponent: NotFoundPage to the root route and an inline NotFoundPage component — identical to the one added in the selfhost root.
apps/host-selfhost/web/routes/__root.tsx Adds notFoundComponent: NotFoundPage and the duplicated NotFoundPage component; also adds useExecutorDocumentTitle("Admin") to AdminPage.
packages/react/src/pages/integration-detail.tsx Uses `
packages/react/package.json Adds a named export entry for ./lib/document-title so the hook is accessible across the monorepo via the public package surface.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[URL navigated to] --> B{Router resolves URL}
    B -->|Matched a route| C{OrgSlugGate: isNotFound?}
    B -->|No match| D[globalNotFound = true on match]
    D --> C
    C -->|isNotFound = true| E[needsCanonicalize = false]
    C -->|isNotFound = false| F{urlSlug === activeSlug?}
    F -->|Yes| G[needsCanonicalize = false - Render children normally]
    F -->|No - e.g. bare URL or wrong slug| H[needsCanonicalize = true - navigate replace to activeSlug URL]
    E --> I[notFoundComponent renders - 404 page]
    G --> J[Page renders + document title set via useExecutorDocumentTitle]
    H --> A
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[URL navigated to] --> B{Router resolves URL}
    B -->|Matched a route| C{OrgSlugGate: isNotFound?}
    B -->|No match| D[globalNotFound = true on match]
    D --> C
    C -->|isNotFound = true| E[needsCanonicalize = false]
    C -->|isNotFound = false| F{urlSlug === activeSlug?}
    F -->|Yes| G[needsCanonicalize = false - Render children normally]
    F -->|No - e.g. bare URL or wrong slug| H[needsCanonicalize = true - navigate replace to activeSlug URL]
    E --> I[notFoundComponent renders - 404 page]
    G --> J[Page renders + document title set via useExecutorDocumentTitle]
    H --> A
Loading

Reviews (2): Last reviewed commit: "Show real not found pages and route titl..." | Re-trigger Greptile

Comment on lines +1 to +14
import { useEffect } from "react";

const APP_NAME = "Executor";

export function executorDocumentTitle(page: string): string {
return `${page} · ${APP_NAME}`;
}

export function useExecutorDocumentTitle(page: string | null | undefined): void {
useEffect(() => {
if (!page || typeof document === "undefined") return;
document.title = executorDocumentTitle(page);
}, [page]);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 File extension should be .ts, not .tsx

The file contains no JSX — it only imports useEffect and returns void. The .tsx extension enables the JSX transform unnecessarily. Renaming to document-title.ts would keep the extension honest and slightly shorten the generated output.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +10 to +29
notFoundComponent: NotFoundPage,
component: RootComponent,
});

function NotFoundPage() {
return (
<main className="flex min-h-screen items-center justify-center bg-background px-6 py-10">
<section className="w-full max-w-md text-center">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">404</p>
<h1 className="mt-2 text-xl font-semibold text-foreground">Page not found</h1>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
There&apos;s nothing at this address.
</p>
<a
href="/"
className="mt-6 inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Go home
</a>
</section>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 NotFoundPage is duplicated verbatim across two root files

The identical component exists in both packages/app/src/routes/__root.tsx and apps/host-selfhost/web/routes/__root.tsx. Any future copy/style change will need to be made in both places. AGENTS.md recommends keeping package boundaries clear and extracting shared logic when the behavior is genuinely shared — this is a good candidate. A single export from packages/react (e.g. alongside the other page components) would keep both roots in sync automatically.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +9 to +13
export function useExecutorDocumentTitle(page: string | null | undefined): void {
useEffect(() => {
if (!page || typeof document === "undefined") return;
document.title = executorDocumentTitle(page);
}, [page]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 document.title is never reset on unmount, so navigating to the 404 page shows the previous page's title

The hook sets document.title on mount but has no cleanup. The NotFoundPage components in both roots don't call useExecutorDocumentTitle, so any user who navigates from a real page to a 404 URL will see the previous page's title persisting in the browser tab (e.g. "Policies · Executor" while the 404 content is shown). Adding a return function that resets to APP_NAME (or calling the hook with a fixed string like "404" inside NotFoundPage) would close this gap.

}, [namespace]);

const integrationData = AsyncResult.isSuccess(integration) ? integration.value : null;
useExecutorDocumentTitle(integrationData?.name || namespace);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 || coerces empty-string names to the namespace fallback

|| treats an empty string as falsy, so an integration whose name is "" would silently fall back to showing the namespace slug as the document title. Use ?? to only fall back when the name is null/undefined, which is the actual sentinel for "not yet loaded or not present".

Suggested change
useExecutorDocumentTitle(integrationData?.name || namespace);
useExecutorDocumentTitle(integrationData?.name ?? namespace);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Console https://executor-preview-pr-1261.executor-e2e.workers.dev
MCP https://executor-preview-pr-1261.executor-e2e.workers.dev/mcp
Deployed commit 11d7e4d

Sign-in is Cloudflare Access (one-time PIN to an allowed email). The preview has its own database and encryption key; it is destroyed when this PR closes.

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1261

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1261

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1261

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1261

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1261

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1261

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1261

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1261

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1261

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1261

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1261

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1261

executor

npm i https://pkg.pr.new/executor@1261

commit: 11d7e4d

@RhysSullivan RhysSullivan force-pushed the ux/not-found-and-titles branch from ebe36bd to a511bb1 Compare July 2, 2026 17:52
@RhysSullivan RhysSullivan force-pushed the ux/setup-status-retry branch from ff9ad76 to 8fe8dc4 Compare July 2, 2026 17:52
@RhysSullivan RhysSullivan force-pushed the ux/setup-status-retry branch from 8fe8dc4 to 376f68c Compare July 2, 2026 17:55
@RhysSullivan RhysSullivan force-pushed the ux/not-found-and-titles branch from a511bb1 to 11d7e4d Compare July 2, 2026 17:55
@RhysSullivan RhysSullivan deleted the branch ux/setup-status-retry July 2, 2026 18:12
@RhysSullivan RhysSullivan deleted the ux/not-found-and-titles branch July 2, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant