Skip to content

Conversation

@stanislavsemeniuk
Copy link
Collaborator

@stanislavsemeniuk stanislavsemeniuk commented Dec 18, 2025

This PR is trying to resolve this issue: https://issues.redhat.com/browse/TC-3370

Implemented useDocumentTitle & useRouteDocumentTitle hooks that give us an opportunity to provide a unique title to each page by simply providing handle: {title : string | func} to each route inside AppRoutes.

Example of usage:

path: Paths.advisories,
       handle: { title: "Advisories" }, //here is the title
       element: (
         <LazyRouteElement
           identifier="advisory-list"
           component={<AdvisoryList />}
         />
       ),
     },

Title can also be provided as a function: ((match: UIMatch) => string)

Example of usage:

{
  path: Paths.advisoryDetails,
  handle: {
    title: (match: UIMatch) =>
      `Advisory details - ${match.params[PathParam.ADVISORY_ID] ?? ""}`,
  },
  element: <LazyRouteElement ... />,
}

I didn't use it , because ADVISORY_ID looks scary IMHO , but if we want to use it in path - we could.

Also worth mentioning that this title changes are client only and if we ll use curl http://localhost:3000/search we would still have default title from index.html template file. In the browser it is dynamically changed by browser's JS.

i'm not sure if we could change it to be server side somehow and i'm not sure if we need it server side

Summary by Sourcery

Update the application to set the browser document title based on the active route using reusable hooks and route metadata.

New Features:

  • Add a useDocumentTitle hook that composes the page title with the branded application title.
  • Add a useRouteDocumentTitle hook that derives the page title from the active route's handle metadata.
  • Define route-level titles for major application routes so each page has a distinct document title in the browser.

Summary by Sourcery

Set per-page browser titles using a shared document metadata component across major application routes.

New Features:

  • Introduce a DocumentMetadata component that sets the page title using branding information and an optional per-page title.

Enhancements:

  • Apply DocumentMetadata to dashboard, list, detail, upload, scan, and search pages so each view presents a meaningful, context-specific document title in the browser.

…cumentTitle hooks that let us provide an unique title for pages inside AppRoutes
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 18, 2025

Reviewer's Guide

Introduce a reusable DocumentMetadata component that sets the browser document title (including branding) and wire it into key pages so each route renders a page-specific title.

Sequence diagram for setting document title via DocumentMetadata

sequenceDiagram
  actor User
  participant Router
  participant PageComponent
  participant DocumentMetadata
  participant useBranding
  participant BrowserDocument

  User->>Router: navigate to route
  Router->>PageComponent: render()
  PageComponent->>DocumentMetadata: render(title)
  DocumentMetadata->>useBranding: getBranding()
  useBranding-->>DocumentMetadata: branding (application title)
  DocumentMetadata->>DocumentMetadata: compose documentTitle
  DocumentMetadata-->>PageComponent: <title> element
  PageComponent-->>Router: rendered tree
  Router-->>BrowserDocument: update DOM
  BrowserDocument->>BrowserDocument: set document.title to documentTitle
Loading

Class diagram for DocumentMetadata component and consuming pages

classDiagram
  class DocumentMetadata {
    +string~optional~ title
    +render()
    -string baseTitle
    -string documentTitle
  }

  class useBranding {
    +getBranding()
    +application
  }

  class Home {
    +render()
  }

  class AdvisoryDetails {
    +render()
  }

  class AdvisoryList {
    +render()
  }

  class AdvisoryUpload {
    +render()
  }

  class ImporterList {
    +render()
  }

  class LicenseList {
    +render()
  }

  class PackageDetails {
    +render()
  }

  class PackageList {
    +render()
  }

  class SbomDetails {
    +render()
  }

  class SbomList {
    +render()
  }

  class SbomScan {
    +render()
  }

  class SbomUpload {
    +render()
  }

  class Search {
    +render()
  }

  class VulnerabilityDetails {
    +render()
  }

  class VulnerabilityList {
    +render()
  }

  DocumentMetadata ..> useBranding : uses

  Home ..> DocumentMetadata : renders
  AdvisoryDetails ..> DocumentMetadata : renders
  AdvisoryList ..> DocumentMetadata : renders
  AdvisoryUpload ..> DocumentMetadata : renders
  ImporterList ..> DocumentMetadata : renders
  LicenseList ..> DocumentMetadata : renders
  PackageDetails ..> DocumentMetadata : renders
  PackageList ..> DocumentMetadata : renders
  SbomDetails ..> DocumentMetadata : renders
  SbomList ..> DocumentMetadata : renders
  SbomScan ..> DocumentMetadata : renders
  SbomUpload ..> DocumentMetadata : renders
  Search ..> DocumentMetadata : renders
  VulnerabilityDetails ..> DocumentMetadata : renders
  VulnerabilityList ..> DocumentMetadata : renders
Loading

File-Level Changes

Change Details Files
Add a shared DocumentMetadata component to compute and render a branded document title and integrate it into main pages and detail views.
  • Create a DocumentMetadata component that uses branding to derive a base application title and composes it with an optional page-specific title.
  • Update dashboard, search, and list pages (advisories, importers, licenses, packages, sboms, vulnerabilities) to render DocumentMetadata with static titles.
  • Update detail and upload pages (advisory details, package details, sbom details, vulnerability details, advisory upload, sbom upload, sbom scan) to render DocumentMetadata using context-derived or static titles before main page content.
client/src/app/components/DocumentMetadata.tsx
client/src/app/pages/home/home.tsx
client/src/app/pages/advisory-details/advisory-details.tsx
client/src/app/pages/advisory-list/advisory-list.tsx
client/src/app/pages/advisory-upload/advisory-upload.tsx
client/src/app/pages/importer-list/importer-list.tsx
client/src/app/pages/license-list/license-list.tsx
client/src/app/pages/package-details/package-details.tsx
client/src/app/pages/package-list/package-list.tsx
client/src/app/pages/sbom-details/sbom-details.tsx
client/src/app/pages/sbom-list/sbom-list.tsx
client/src/app/pages/sbom-scan/sbom-scan.tsx
client/src/app/pages/sbom-upload/sbom-upload.tsx
client/src/app/pages/search/search.tsx
client/src/app/pages/vulnerability-details/vulnerability-details.tsx
client/src/app/pages/vulnerability-list/vulnerability-list.tsx

Assessment against linked issues

Issue Objective Addressed Explanation
#360 Make the browser page title dynamic so it changes based on the currently viewed page instead of staying static across the application.
#360 Include page-specific context (e.g., list names, search, and entity identifiers/names for detail pages) in the page title, combined with the branded application title, so multiple tabs and history entries can be distinguished.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In useRouteDocumentTitle, you create a new array with [...matches].reverse() on every render; consider iterating from the end of matches instead to avoid the extra allocation and reversal on each render.
  • The cast to MatchWithTitleHandle[] for useMatches() is a bit loose and assumes all matches conform to that shape; you could narrow more safely (e.g., by checking typeof match.handle === 'object' && 'title' in match.handle) instead of casting the whole array.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `useRouteDocumentTitle`, you create a new array with `[...matches].reverse()` on every render; consider iterating from the end of `matches` instead to avoid the extra allocation and reversal on each render.
- The cast to `MatchWithTitleHandle[]` for `useMatches()` is a bit loose and assumes all matches conform to that shape; you could narrow more safely (e.g., by checking `typeof match.handle === 'object' && 'title' in match.handle`) instead of casting the whole array.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link

codecov bot commented Dec 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.41%. Comparing base (a77178f) to head (bb7f41d).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #852      +/-   ##
==========================================
- Coverage   61.83%   61.41%   -0.42%     
==========================================
  Files         175      176       +1     
  Lines        3139     3144       +5     
  Branches      716      717       +1     
==========================================
- Hits         1941     1931      -10     
- Misses        928      952      +24     
+ Partials      270      261       -9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@carlosthe19916 carlosthe19916 linked an issue Dec 18, 2025 that may be closed by this pull request
@stanislavsemeniuk
Copy link
Collaborator Author

By the way, we can just use <title>...</title> inside components in react 19 , i just think to set it where the app routes are set is more structured approach but we can discuss that

@carlosthe19916
Copy link
Collaborator

By the way, we can just use <title>...</title> inside components in react 19 , i just think to set it where the app routes are set is more structured approach but we can discuss that

@stanislavsemeniuk thanks for the PR. Yeah, seems there are multiple paths we could take.

I wonder how would we evolve the current approach if we need the title of the page to reflect the current object we are looking at. E.g. When we navigate to the details of a Vulnerability and we are in the Vulnerability Details Page then how to set the title of the page to something like CVE-1234 | Trustification, currently it renders Vulnerability Details | Trustification.

Although the requirement in the ticket does not mention that, maybe it is worth evaluating such scenario. WDYT?

@stanislavsemeniuk
Copy link
Collaborator Author

@carlosthe19916 Yeah, i think the only way can pass some data that we got from BE into title is by using <title> inside components rendering. But i think we should also agree to have some "rule" of proper way of using <title> not to create mess in the project.

How about use <title> only in components that are passed as route element, i mean this:

{
        path: Paths.advisoryDetails,
        handle: { title: "Advisory details" },
        element: (
          <LazyRouteElement
            identifier="advisory-details"
            component={<AdvisoryDetails />} //AdvisoryDetails should have <title></title>
          />
        ),
      },

Or if route element don't have data that is needed for <title> then implement it in the closest child possible

Sounds right?

@carlosthe19916
Copy link
Collaborator

@stanislavsemeniuk good point.
I wonder if using react-router-dom loader strategy would help us here?
Maybe we could setup a loader for each page defined in the Routes file, then the loader would be in charge of loading the entity the page is looking at. E.g. Vulnerability Details... then since the loader has the entity info then maybe we can setup the title or any other SEO info within the Routes file. But this is just an idea, maybe it is not possible, I need to validate it

@carlosthe19916
Copy link
Collaborator

@stanislavsemeniuk in regards of my previous comment. I have created a draft PR to validate the idea #853 . That PR is able to create titles like CVE-1234 | Trustification. Since the PR is just an idea it has the draft status; moreoever, it only covers the Advisory List and Advisory Details Page. I wonder if the strategy of that PR could help us here? My PR is incomplete and not perfect, I just wanted to share it with you.

There might be other strategies, so let's evaluate them :)

@stanislavsemeniuk
Copy link
Collaborator Author

Just duplication comment from #853

@carlosthe19916 to be honest in some way it feels like overcomplicating things. And also there can be usage of loader without actual loading of data that feels like misconception (f.e in Advisory list)

I think the other way to not make it overcomplicated and don't create mess of inside JSX everywhere at the same time could be something like this:

Took SeoMetadata component that u implemented (or implement something similar), change it a bit to make it as a context provider that will have some setSeoMetadata func that can be used accross components for setting metadata including title. That would help us have a rendering of metadata stored only in one component and provide a simple func for changing it on different pages.

What do you think?

@carlosthe19916
Copy link
Collaborator

@stanislavsemeniuk

And also there can be usage of loader without actual loading of data that feels like misconception (f.e in Advisory list)

Good point

Took SeoMetadata component that u implemented (or implement something similar), change it a bit to make it as a context provider that will have some setSeoMetadata func that can be used accross components for setting metadata including title.

If we use a Context then whenever the value of the context changes it will re-render all child components. I wonder if we could use your proposal without the need of a Context and just use a standard component? E.g.

const page1 = (
<>
  <SeoComponent seoProperties={value}/>
  ...rest of page here
</>)

this way the seo component lives only while the page lives and it does not force a tree hierarchy to be re-rendered

This would still enforce your idea of having a single place where to define seo and yet each page can set its own values there, including the details page like Vulnerability Details Page

@stanislavsemeniuk
Copy link
Collaborator Author

@carlosthe19916 I was thinking about pages where there are tabs. and I think we should also change title when tabs are changed and that mean that we can't set title on some high level components and we should use it deeply inside the components. So the solution i have right now for it to use shared useDocumentTitle hook that lets us set up a proper title accroding to tab and even using data from BE. You can see example of usage in home.tsx , SearchTabs.tsx and vulnerability-details.tsx

What do you think?

@stanislavsemeniuk
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In SearchTabs, the document title is derived from getTabsProps().activeKey, which appears to be an internal key (e.g. sboms, packages) rather than a user-facing label; consider using the tab's display label so the browser title is more readable.
  • In VulnerabilityDetails, useDocumentTitle(vulnerability?.identifier ?? "") can result in an empty or flickering title while the data loads; consider providing a stable fallback (e.g. "Vulnerability details") and then appending the identifier once available.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SearchTabs`, the document title is derived from `getTabsProps().activeKey`, which appears to be an internal key (e.g. `sboms`, `packages`) rather than a user-facing label; consider using the tab's display label so the browser title is more readable.
- In `VulnerabilityDetails`, `useDocumentTitle(vulnerability?.identifier ?? "")` can result in an empty or flickering title while the data loads; consider providing a stable fallback (e.g. `"Vulnerability details"`) and then appending the identifier once available.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@CryptoRodeo
Copy link
Collaborator

CryptoRodeo commented Jan 6, 2026

Overall this LGTM. I used the hook on the SBOMS list page and the SBOM details page, see demo:

useDocumentTitleDemo.mp4

Adding this functionality to a component is easy and if the component has the data we need it's easy to add additional information to the title.

It looks like the current options discussed are:

  • Use <title>...</title>
  • Use this hook
  • Use the Route loader + an <SeoMetadata /> component that's presets this information for each component

I haven't taken a deep look at #853 yet, but I'm wondering if the data for a component can also be fetched from the loader on top of setting the SEO data? 🤔 For example, fetching the data for AdvisoryDetails from the loader and then grabbing it in the component using useLoaderData? That way the data can be fetched once, passed to the component and used to set the SEO data. This might be too much work though 😅

@carlosthe19916
Copy link
Collaborator

@stanislavsemeniuk some thoughts:

  • See the video below, I navigated from the Dashboard to the Vulnerabilities page. The Vulnerabilities page has wrongly assigned the title Dashboard to the Vulnerabilities page.
    • Could we do something so if the page does not use useDocumentTitle then we leave the default title (the original <title> tag of the HTML page that is injected at build time) untouched?
  • I don't think we need a different title for each tab in a page
    • If the tab would require a change in the root path then we would indeed need to change the title. E.g. /page/tab1, /page/tab2, /page/tab3. This is not our case
    • In our case we are not changing the path, and at most we are changing some url query parameters but not the path. E.g. /page?activeTab=tab1. What do you think?
  • Just for readability I think it would be better if instead of a hook useDocumentTitle we use a Component.
    • Seeing the metadata in the JXS tree makes it easier to understand it. E.g.
<>
  <DocumentTitle title={"something"} />
  <PageSection>
    // patternfly elements
  </PageSection>
</>
  • Also it would be good if the name of the component does not mention Title because it would mean it only handles titles. If we name it something like Metadata, Seo, or similar we could extend it in the future so it handles title together with other metadata if required.
Screencast.From.2026-01-07.09-11-53.mp4

…a, get rid of useDocumentTitle hook and titles in tabs
@stanislavsemeniuk
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Rendering a <title> element inside the page JSX will not update document.title unless it’s mounted into <head> (e.g., via react-helmet or a similar mechanism), so consider switching DocumentMetadata to a hook that sets document.title or integrating a head-management library.
  • You’re now manually adding <DocumentMetadata> to each page component; since the PR description mentions route-level metadata, you might simplify this by deriving the title from route handle data in a single place (e.g., a layout or router wrapper) rather than scattering title components across all pages.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Rendering a `<title>` element inside the page JSX will not update `document.title` unless it’s mounted into `<head>` (e.g., via `react-helmet` or a similar mechanism), so consider switching `DocumentMetadata` to a hook that sets `document.title` or integrating a head-management library.
- You’re now manually adding `<DocumentMetadata>` to each page component; since the PR description mentions route-level metadata, you might simplify this by deriving the title from route `handle` data in a single place (e.g., a layout or router wrapper) rather than scattering title components across all pages.

## Individual Comments

### Comment 1
<location> `client/src/app/components/DocumentMetadata.tsx:3-12` </location>
<code_context>
+import useBranding from "@app/hooks/useBranding";
+
+export const DocumentMetadata = ({ title }: { title?: string }) => {
+  const branding = useBranding();
+  const baseTitle = branding.application.title;
+  const documentTitle = title ? `${title} | ${baseTitle}` : baseTitle;
+
+  return (
+    <>
+      <title>{documentTitle}</title>
+    </>
+  );
+};
</code_context>

<issue_to_address>
**issue (bug_risk):** Rendering a <title> element in the component tree is unlikely to update the document title correctly; consider setting document.title via an effect instead.

Rendering `<title>` inside the React tree (under `<body>`) is invalid HTML and usually won’t update the browser tab title in an SPA. Instead, compute the title here and set it via a side effect:

```tsx
import { useEffect } from "react";
import useBranding from "@app/hooks/useBranding";

export const DocumentMetadata = ({ title }: { title?: string }) => {
  const branding = useBranding();
  const baseTitle = branding.application.title;
  const documentTitle = title ? `${title} | ${baseTitle}` : baseTitle;

  useEffect(() => {
    document.title = documentTitle;
  }, [documentTitle]);

  return null;
};
```

This avoids invalid markup and reliably updates the tab title wherever this component is used.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@stanislavsemeniuk
Copy link
Collaborator Author

@carlosthe19916 @CryptoRodeo Updated pr. Implemented DocumentMetadata component and used new <title> JSX tag in it

Copy link
Collaborator

@carlosthe19916 carlosthe19916 left a comment

Choose a reason for hiding this comment

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

@stanislavsemeniuk thanks for the enhancements. I tested it and all issues reported in my previous comments are gone, thanks!

Just one minor thing, I noticed that in github actions and also locally when i execute npm run check then the biome rules are throwing a warning about DocumentMedatada and suggesting to remove <></> . Could you do that please?

I wonder why CI and github actions didn't fail, i was expecting it to fail since our biome commands defined in packge.json has biome check --error-on-warnings so I was expecting even the warnings to thrown error. But that is something to investigate separately.

@carlosthe19916 carlosthe19916 changed the title Page title does not change based on viewed page feat: Set page title on viewed page Jan 7, 2026
@carlosthe19916 carlosthe19916 changed the title feat: Set page title on viewed page feat: set page title on viewed page Jan 7, 2026
Copy link
Collaborator

@CryptoRodeo CryptoRodeo left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@stanislavsemeniuk stanislavsemeniuk added this pull request to the merge queue Jan 7, 2026
Merged via the queue into guacsec:main with commit 73f28d8 Jan 7, 2026
11 checks passed
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.

Page title does not change based on viewed page

3 participants