Skip to content

feat: update page structure and semantic markup BED-7212#2804

Open
Holocraft wants to merge 2 commits into
mainfrom
BED-7212
Open

feat: update page structure and semantic markup BED-7212#2804
Holocraft wants to merge 2 commits into
mainfrom
BED-7212

Conversation

@Holocraft
Copy link
Copy Markdown
Contributor

@Holocraft Holocraft commented May 20, 2026

Description

This changeset updates the entire platform with page structure and semantic markup for the BoA audit. Page titles, semantic HTML updates, and ARIA additions.

Motivation and Context

Resolves BED-7212

These changes are necessary for the BoA audit and for general quality of life/ARIA updates to our codebase. Note that an updated react-helmet-async library has been installed as the old react-helmet has been deprecated. An ADR will be created in a separate PR to approve this library.

How Has This Been Tested?

Unit tests were added or updated. Manually tested as well.

Screenshots (optional):

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

Summary by CodeRabbit

  • New Features

    • Added dynamic page titles displaying the application name alongside page content.
  • Improvements

    • Enhanced heading hierarchy and typography sizing across collector cards, early access features, and information panels.
    • Improved accessibility with descriptive ARIA labels for better screen reader support.

Review Change Stack

@Holocraft Holocraft self-assigned this May 20, 2026
@Holocraft Holocraft requested review from a team as code owners May 20, 2026 23:49
@Holocraft Holocraft added enhancement New feature or request user interface A pull request containing changes affecting the UI code. labels May 20, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

Migrates the UI from react-helmet to react-helmet-async, introduces AppNameProvider context for dynamic app naming, adds a top-level HelmetProvider, and integrates document title management across specific pages while updating UI typography and accessibility attributes.

Changes

React Helmet Async Migration with App Name Context

Layer / File(s) Summary
Dependency and Build Configuration
cmd/ui/package.json, cmd/ui/vite.config.ts, packages/javascript/bh-shared-ui/package.json
Swaps react-helmet for react-helmet-async in dependencies, removes @types/react-helmet, and adds Vite alias and dedupe configuration for the new package.
Core Provider Infrastructure
packages/javascript/bh-shared-ui/src/components/PageWithTitle.tsx, cmd/ui/src/App.tsx, packages/javascript/bh-shared-ui/src/test-utils.tsx, packages/javascript/bh-shared-ui/src/components/PageWithTitle.test.tsx
Introduces AppNameContext and AppNameProvider component, wraps the app with HelmetProvider at the root level with "BloodHound Community Edition" as the app name, integrates HelmetProvider into test utilities, and adds tests covering context-driven title behavior.
Page-Level Document Title Management
cmd/ui/src/views/Explore/GraphView.tsx, packages/javascript/bh-shared-ui/src/views/PrivilegeZones/PrivilegeZones.tsx
Adds Helmet elements to GraphView and PrivilegeZones pages to set browser document titles in the format "Page
UI Polish and Accessibility
cmd/ui/src/views/DownloadCollectors/DownloadCollectors.tsx, cmd/ui/src/views/EarlyAccessFeatures/EarlyAccessFeatures.tsx, packages/javascript/bh-shared-ui/src/components/CollectorCard/CollectorCard.tsx, packages/javascript/bh-shared-ui/src/views/Explore/EdgeInfo/EdgeInfoHeader.tsx, packages/javascript/bh-shared-ui/src/components/SSOProviderTable/SSOProviderTable.tsx
Updates Typography variants from h6 to h2/h3 for improved visual hierarchy, adds explicit component='div' props, and adds aria-label='Actions' for accessibility improvements.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • dcairnsspecterops
  • jvacca-specterops
  • TheNando
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: update page structure and semantic markup BED-7212' clearly and specifically summarizes the main changes—updating page structure, semantic markup, and ARIA additions across the codebase.
Description check ✅ Passed The PR description follows the template structure with all required sections completed: Description, Motivation and Context (with associated ticket BED-7212), How Has This Been Tested, Types of changes, and Checklist items marked as complete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch BED-7212

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
cmd/ui/src/views/Explore/GraphView.tsx (1)

267-269: ⚡ Quick win

Use AppNameContext instead of hardcoding the app name.

The title hardcodes "BloodHound Community Edition" rather than consuming the app name from AppNameContext. This creates inconsistency with the PageWithTitle component pattern and makes the code less maintainable.

Once AppNameContext or a useAppName hook is exported from PageWithTitle.tsx (see previous comment), update this component to use the context dynamically.

♻️ Proposed fix (after exporting useAppName hook)
+import { useAppName } from 'bh-shared-ui';
+
 const GraphView: FC = () => {
     /* Hooks */
     const theme = useTheme();
     const dispatch = useAppDispatch();
+    const appName = useAppName();
     
     // ... rest of component
     
     return (
         <div
             className='relative h-full w-full overflow-hidden'
             data-testid='explore'
             onContextMenu={(e) => e.preventDefault()}>
             <Helmet>
-                <title>Explore | BloodHound Community Edition</title>
+                <title>Explore | {appName}</title>
             </Helmet>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ui/src/views/Explore/GraphView.tsx` around lines 267 - 269, The page
title in GraphView currently hardcodes "BloodHound Community Edition" inside the
Helmet block; update GraphView to consume the app name from the shared
AppNameContext (or the exported useAppName hook from PageWithTitle.tsx) instead
of hardcoding. Import useAppName (or AppNameContext) into GraphView, call the
hook (or useContext) to get the app name, and use that value when rendering the
Helmet title (e.g., `${pageTitle} | ${appName}`) so the title follows the same
dynamic pattern as PageWithTitle.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/javascript/bh-shared-ui/src/components/PageWithTitle.tsx`:
- Around line 22-26: AppNameContext is created but not exported, so other
components can't consume it; either export AppNameContext or add and export a
small helper hook (e.g., export function useAppName() { return
useContext(AppNameContext); }) alongside the existing AppNameProvider to expose
the value; update exports from PageWithTitle.tsx to include AppNameContext or
the new useAppName so callers like GraphView can import and consume the app
name.

In
`@packages/javascript/bh-shared-ui/src/views/PrivilegeZones/PrivilegeZones.tsx`:
- Around line 118-120: The page title is hardcoded in the PrivilegeZones
component; import useContext from React and AppNameContext from
PageWithTitle.tsx, call const appName = useContext(AppNameContext) inside the
PrivilegeZones component, and replace the static <title>Privilege Zones |
BloodHound Enterprise</title> within Helmet with <title>Privilege Zones |
{appName}</title> so the title uses the single source of truth for the app name.

---

Nitpick comments:
In `@cmd/ui/src/views/Explore/GraphView.tsx`:
- Around line 267-269: The page title in GraphView currently hardcodes
"BloodHound Community Edition" inside the Helmet block; update GraphView to
consume the app name from the shared AppNameContext (or the exported useAppName
hook from PageWithTitle.tsx) instead of hardcoding. Import useAppName (or
AppNameContext) into GraphView, call the hook (or useContext) to get the app
name, and use that value when rendering the Helmet title (e.g., `${pageTitle} |
${appName}`) so the title follows the same dynamic pattern as PageWithTitle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f738b91a-cbb3-4e56-872c-c05d15141d77

📥 Commits

Reviewing files that changed from the base of the PR and between 378d0ed and 0e06dbd.

⛔ Files ignored due to path filters (6)
  • .yarn/cache/@types-react-helmet-npm-6.1.11-6d6a281744-e329d8ad82.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/react-helmet-async-npm-3.0.0-cf32885e2c-c828f59b73.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/react-helmet-npm-6.1.0-20fd5447ff-eff2523138.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/react-side-effect-npm-2.1.2-c18e5fd8bd-8c31aaec5b.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/shallowequal-npm-1.1.0-6688d419cb-f4c1de0837.zip is excluded by !**/.yarn/**, !**/*.zip
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • cmd/ui/package.json
  • cmd/ui/src/App.tsx
  • cmd/ui/src/views/DownloadCollectors/DownloadCollectors.tsx
  • cmd/ui/src/views/EarlyAccessFeatures/EarlyAccessFeatures.tsx
  • cmd/ui/src/views/Explore/GraphView.tsx
  • cmd/ui/vite.config.ts
  • packages/javascript/bh-shared-ui/package.json
  • packages/javascript/bh-shared-ui/src/components/CollectorCard/CollectorCard.tsx
  • packages/javascript/bh-shared-ui/src/components/PageWithTitle.test.tsx
  • packages/javascript/bh-shared-ui/src/components/PageWithTitle.tsx
  • packages/javascript/bh-shared-ui/src/components/SSOProviderTable/SSOProviderTable.tsx
  • packages/javascript/bh-shared-ui/src/test-utils.tsx
  • packages/javascript/bh-shared-ui/src/views/Explore/EdgeInfo/EdgeInfoHeader.tsx
  • packages/javascript/bh-shared-ui/src/views/PrivilegeZones/PrivilegeZones.tsx

Comment on lines +22 to +26
const AppNameContext = createContext('BloodHound Enterprise');

export const AppNameProvider: React.FC<{ name: string; children: React.ReactNode }> = ({ name, children }) => (
<AppNameContext.Provider value={name}>{children}</AppNameContext.Provider>
);
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Export AppNameContext or provide a useAppName hook for API completeness.

The AppNameContext is created but not exported, which prevents other components from consuming the app name directly. Components that need the app name but don't use PageWithTitle (like GraphView.tsx) currently cannot access it through the context.

Based on learnings, public utilities should be exported from the package's API surface to enable consumption by parent projects.

📦 Proposed solutions

Option 1: Export the context directly

-const AppNameContext = createContext('BloodHound Enterprise');
+export const AppNameContext = createContext('BloodHound Enterprise');

Option 2: Export a custom hook (preferred for better encapsulation)

 const AppNameContext = createContext('BloodHound Enterprise');
 
+export const useAppName = () => useContext(AppNameContext);
+
 export const AppNameProvider: React.FC<{ name: string; children: React.ReactNode }> = ({ name, children }) => (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const AppNameContext = createContext('BloodHound Enterprise');
export const AppNameProvider: React.FC<{ name: string; children: React.ReactNode }> = ({ name, children }) => (
<AppNameContext.Provider value={name}>{children}</AppNameContext.Provider>
);
export const AppNameContext = createContext('BloodHound Enterprise');
export const AppNameProvider: React.FC<{ name: string; children: React.ReactNode }> = ({ name, children }) => (
<AppNameContext.Provider value={name}>{children}</AppNameContext.Provider>
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/javascript/bh-shared-ui/src/components/PageWithTitle.tsx` around
lines 22 - 26, AppNameContext is created but not exported, so other components
can't consume it; either export AppNameContext or add and export a small helper
hook (e.g., export function useAppName() { return useContext(AppNameContext); })
alongside the existing AppNameProvider to expose the value; update exports from
PageWithTitle.tsx to include AppNameContext or the new useAppName so callers
like GraphView can import and consume the app name.

Comment on lines +118 to +120
<Helmet>
<title>Privilege Zones | BloodHound Enterprise</title>
</Helmet>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check AppNameProvider implementation and usage patterns

# Check for AppNameProvider/AppNameContext implementation
echo "=== Searching for AppNameProvider implementation ==="
rg -n -A5 "export.*AppNameProvider|export.*AppNameContext|export.*useAppName" --type=ts --type=tsx

# Check how GraphView uses Helmet (mentioned in same layer)
echo -e "\n=== Checking GraphView Helmet usage ==="
rg -n -B2 -A5 "<Helmet>" cmd/ui/src/views/Explore/GraphView.tsx

# Check if there's a hook or context for app name
echo -e "\n=== Searching for app name context usage ==="
ast-grep --pattern 'const $_ = useAppName$_($$$)'

Repository: SpecterOps/BloodHound

Length of output: 596


🏁 Script executed:

#!/bin/bash

# Fix: Search .tsx files without problematic --type flag
echo "=== Searching for AppNameProvider in all TS/TSX files ==="
rg "AppNameProvider|AppNameContext|useAppName" --type=ts -A3 2>/dev/null | head -50

# Broader search for app name context/provider patterns
echo -e "\n=== Searching for app name context/provider patterns ==="
rg "appName|AppName" --type=ts -l | head -20

# Check what exists in the Core Provider Infrastructure mentioned in the comment
echo -e "\n=== Looking for provider setup and context ==="
find . -type f -name "*Provider*" -o -name "*Context*" | grep -i "app\|provider" | head -20

# Check the packages/javascript directory structure
echo -e "\n=== Checking PrivilegeZones file location ==="
find . -path "*/PrivilegeZones*" -type f

# Search for Helmet usage patterns across the codebase
echo -e "\n=== Helmet usage patterns in bh-shared-ui ==="
rg "<Helmet>" packages/javascript/bh-shared-ui --type=ts -B2 -A2 2>/dev/null | head -60

Repository: SpecterOps/BloodHound

Length of output: 14782


🏁 Script executed:

#!/bin/bash

# Get the PageWithTitle implementation to see the exact pattern
echo "=== PageWithTitle.tsx implementation ==="
cat -n packages/javascript/bh-shared-ui/src/components/PageWithTitle.tsx | head -50

# Check if PrivilegeZones has any imports from PageWithTitle
echo -e "\n=== Current PrivilegeZones imports ==="
head -30 packages/javascript/bh-shared-ui/src/views/PrivilegeZones/PrivilegeZones.tsx

# Check if there are similar context usage patterns in bh-shared-ui views
echo -e "\n=== Other view files using context hooks ==="
rg "useContext.*import" packages/javascript/bh-shared-ui/src/views --type=ts -B1 | head -40

Repository: SpecterOps/BloodHound

Length of output: 3646


Use AppNameContext to make the page title dynamic instead of hardcoding it.

The component should follow the established pattern from PageWithTitle.tsx and use the AppNameContext to dynamically include the app name in the title. This provides a single source of truth for the app name and ensures consistency across the application. Additionally, the hardcoded value "BloodHound Enterprise" is inconsistent with the actual runtime value "BloodHound Community Edition" set by AppNameProvider in App.tsx.

Import useContext from React and AppNameContext from PageWithTitle.tsx, then replace the hardcoded title with:

const appName = useContext(AppNameContext);

And update the Helmet title to:

<title>Privilege Zones | {appName}</title>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/javascript/bh-shared-ui/src/views/PrivilegeZones/PrivilegeZones.tsx`
around lines 118 - 120, The page title is hardcoded in the PrivilegeZones
component; import useContext from React and AppNameContext from
PageWithTitle.tsx, call const appName = useContext(AppNameContext) inside the
PrivilegeZones component, and replace the static <title>Privilege Zones |
BloodHound Enterprise</title> within Helmet with <title>Privilege Zones |
{appName}</title> so the title uses the single source of truth for the app name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request user interface A pull request containing changes affecting the UI code.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant