Skip to content

✨ feat(editor): add sticky header bar filename on editor page#4573

Open
piedicianni wants to merge 8 commits into
developfrom
editor-filename-sticky-position
Open

✨ feat(editor): add sticky header bar filename on editor page#4573
piedicianni wants to merge 8 commits into
developfrom
editor-filename-sticky-position

Conversation

@piedicianni
Copy link
Copy Markdown
Contributor

@piedicianni piedicianni commented May 13, 2026

Summary

Add sticky project bar with filename on the editor page. The filename header bar remains visible and fixed at the top while scrolling through segments, with smooth animations when the file changes.

Type

  • feat — new user-facing feature
  • fix — bug fix
  • refactor — restructure without behavior change
  • chore — build, deps, config, docs
  • perf — performance improvement
  • test — test coverage

Changes

File Change
public/js/components/common/VirtualList/Rows/RowSegment.js Add sticky ProjectBar with file change animation
public/js/components/common/VirtualList/VirtualList.js Support sticky header rendering
public/js/components/segments/SegmentsContainer.js Render sticky ProjectBar based on first visible row
public/js/components/common/VirtualList/VirtualList.test.js New unit tests for VirtualList
public/js/components/common/VirtualList/Rows/RowSegment.test.js New unit tests for RowSegment and ProjectBar
public/js/components/segments/SegmentsContainer.test.js New unit tests for SegmentsContainer
public/js/hooks/useSegmentsLoader.test.js New unit tests for useSegmentsLoader hook

Testing

  • vendor/bin/phpunit --exclude-group=ExternalServices --no-coverage passes
  • ./vendor/bin/phpstan passes (0 errors, with baseline)
  • Manual testing performed (describe below)
  • New tests added for changed behavior
  • Regression tests added for bug fixes

Manual testing:

  • Opened editor pages with multiple files/chunks
  • Verified sticky filename bar remains visible while scrolling
  • Verified slide animation triggers when first visible file changes

AI Disclosure

  • No AI tools were used in this PR
  • AI tools were used — details below

GitHub Copilot (claude-sonnet-4-6)

Notes

The sticky bar shows the current file name and word count while scrolling. An animation is triggered when the visible file changes.

Copilot AI review requested due to automatic review settings May 13, 2026 09:57
@github-actions
Copy link
Copy Markdown

🧪 Test-Guard Report

✅ PASS — All changed source files have adequate test coverage.

Coverage Analysis: ❌ FAIL

No changed source files found in coverage report (threshold: 80%)

📋 5 files: 5 ❌ fail
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ❌ fail not in coverage report
public/js/components/common/VirtualList/VirtualList.js ❌ fail not in coverage report
public/js/components/segments/SegmentFooter.js ❌ fail not in coverage report
public/js/components/segments/SegmentsContainer.js ❌ fail not in coverage report
public/js/hooks/useSegmentsLoader.js ❌ fail not in coverage report

Test File Matching: ✅ PASS

File matching: 5 pass

📋 5 files: 5 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/Rows/RowSegment.test.js
public/js/components/common/VirtualList/VirtualList.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/VirtualList.test.js
public/js/components/segments/SegmentFooter.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentFooter.test.js
public/js/components/segments/SegmentsContainer.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentsContainer.test.js
public/js/hooks/useSegmentsLoader.js ✅ pass Test file modified in PR: public/js/hooks/useSegmentsLoader.test.js

Per-File Evaluation: ✅ PASS

Evaluated 5 files: 5 via AI (4 batches), 0 via shortcuts.

📋 5 files: 5 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Removed ProjectBar code; tests cover ProjectBar rendering and scroll behavior.
public/js/components/common/VirtualList/VirtualList.js ✅ pass Tests verify rendering, header, scroll behavior, and setFirstRowIdVisible callback.
public/js/components/segments/SegmentFooter.js ✅ pass Tests mock IntersectionObserver and verify conditional content rendering and height tracking.
public/js/components/segments/SegmentsContainer.js ✅ pass New ProjectBar sticky UI and row height logic tested indirectly via matched tests.
public/js/hooks/useSegmentsLoader.js ✅ pass Tests cover loading states, fetch calls, user login, and error handling.

Result: ✅ PASS

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a sticky “ProjectBar” (showing the current file name and word count) on the editor page, keeping it visible at the top while scrolling through segments and animating when the visible file changes.

Changes:

  • Add support for rendering a sticky header in VirtualList and wire it from SegmentsContainer using the first visible row id.
  • Refactor RowSegment to expose a reusable ProjectBar component with sticky/animation behavior.
  • Add new unit tests covering VirtualList, RowSegment/ProjectBar, SegmentsContainer, and useSegmentsLoader, plus required styling updates.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
public/js/components/common/VirtualList/Rows/RowSegment.js Extracts ProjectBar and adds sticky + file-change animation behavior.
public/js/components/common/VirtualList/Rows/RowSegment.test.js Adds tests for RowSegment and ProjectBar.
public/js/components/common/VirtualList/VirtualList.js Adds header rendering and first-visible-row reporting.
public/js/components/common/VirtualList/VirtualList.test.js Adds unit tests for VirtualList.
public/js/components/segments/SegmentsContainer.js Renders the sticky ProjectBar based on the first visible virtual row.
public/js/components/segments/SegmentsContainer.test.js Adds unit tests for sticky header integration and store wiring.
public/js/components/segments/SegmentFooter.js Adds IntersectionObserver-gated rendering and height caching for the footer.
public/js/components/segments/SegmentFooter.test.js Adds IntersectionObserver mock needed for the footer tests.
public/js/hooks/useSegmentsLoader.js Adjusts loading flag handling while fetching segments.
public/js/hooks/useSegmentsLoader.test.js Adds hook-level tests for fetching, flags, and cleanup.
public/css/sass/style.scss Tweaks projectbar word-counter layout and loader z-index/margins.
public/css/sass/components/SegmentsContainer.scss Adds styles and keyframes for the sticky project bar + animations.

Comment thread public/js/hooks/useSegmentsLoader.js Outdated
Comment thread public/js/components/common/VirtualList/Rows/RowSegment.js Outdated
Comment thread public/js/components/common/VirtualList/Rows/RowSegment.js
Comment thread public/js/components/common/VirtualList/Rows/RowSegment.js
Comment thread public/js/components/segments/SegmentFooter.js Outdated
Comment thread public/js/components/segments/SegmentFooter.js
Comment thread public/js/components/common/VirtualList/VirtualList.js
@piedicianni piedicianni force-pushed the editor-filename-sticky-position branch from aa32a34 to e5955c8 Compare May 14, 2026 11:19
@piedicianni piedicianni changed the title feat: ✨ sticky header bar filename on editor page ✨ feat(editor): add sticky header bar filename on editor page May 14, 2026
@github-actions
Copy link
Copy Markdown

🧪 Test-Guard Report

✅ PASS — All changed source files have adequate test coverage.

Coverage Analysis: ❌ FAIL

No changed source files found in coverage report (threshold: 80%)

📋 5 files: 5 ❌ fail
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ❌ fail not in coverage report
public/js/components/common/VirtualList/VirtualList.js ❌ fail not in coverage report
public/js/components/segments/SegmentFooter.js ❌ fail not in coverage report
public/js/components/segments/SegmentsContainer.js ❌ fail not in coverage report
public/js/hooks/useSegmentsLoader.js ❌ fail not in coverage report

Test File Matching: ✅ PASS

File matching: 5 pass

📋 5 files: 5 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/Rows/RowSegment.test.js
public/js/components/common/VirtualList/VirtualList.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/VirtualList.test.js
public/js/components/segments/SegmentFooter.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentFooter.test.js
public/js/components/segments/SegmentsContainer.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentsContainer.test.js
public/js/hooks/useSegmentsLoader.js ✅ pass Test file modified in PR: public/js/hooks/useSegmentsLoader.test.js

Per-File Evaluation: ✅ PASS

Evaluated 5 files: 5 via AI (4 batches), 0 via shortcuts.

📋 5 files: 5 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Removed getProjectBar function; tests cover ProjectBar component behavior and scroll listener.
public/js/components/common/VirtualList/VirtualList.js ✅ pass Tests verify rendering, header, scroll behavior, and setFirstRowIdVisible callback.
public/js/components/segments/SegmentFooter.js ✅ pass Tests cover IntersectionObserver usage and conditional content rendering.
public/js/components/segments/SegmentsContainer.js ✅ pass New ProjectBar rendering and row height logic tested indirectly via matched SegmentsContainer tests.
public/js/hooks/useSegmentsLoader.js ✅ pass Tests cover loading states, fetch calls, user login, and error handling for useSegmentsLoader.

Result: ✅ PASS

Copilot AI review requested due to automatic review settings May 14, 2026 13:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 11 out of 11 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (3)

public/js/components/common/VirtualList/Rows/RowSegment.js:202

  • This block runs as a side effect during render, and mutates refs based on listRef?.scrollTop. Because React render is not invoked on every scroll event, previousScrollTopRef.current only updates when the component re-renders for unrelated reasons, so isScrollingReverseRef will rarely reflect the actual scroll direction at the moment a file change animation begins. This logic should live inside the scroll event handler (effect) where it actually observes scroll changes, instead of in the render body.
  const previousScrollTopRef = useRef(0)
  const isScrollingReverseRef = useRef(false)
  if (isSticky && listRef?.scrollTop !== previousScrollTopRef.current) {
    isScrollingReverseRef.current =
      listRef?.scrollTop < previousScrollTopRef.current
  }
  previousScrollTopRef.current = listRef?.scrollTop

public/js/components/common/VirtualList/Rows/RowSegment.js:171

  • When isSticky is false (e.g. when ProjectBar is rendered inline by RowSegment), previousFileIdRef.current is updated on every render, meaning the inline ProjectBar will also try to trigger a file-change animation/cleanup if the same instance gets reused with a different file id. The whole effect (including previousFileIdRef.current = idFileSegment) should be guarded by isSticky, otherwise the bookkeeping diverges from the intent (animation only when sticky).
  useEffect(() => {
    let tmOutReset
    const stopFileChangeAnim = () => setIsFileChange(false)
    const filenameElement = ref.current.firstChild
    const wordcounterElement = ref.current.children[1]

    if (
      isSticky &&
      idFileSegment !== previousFileIdRef.current &&
      typeof previousFileIdRef.current === 'number'
    ) {
      ref.current.style.animation = 'none'
      if (filenameElement) filenameElement.style.animation = 'none'
      if (wordcounterElement) wordcounterElement.style.animation = 'none'

      tmOutReset = setTimeout(() => {
        ref.current.style.animation = ''
        if (filenameElement) filenameElement.style.animation = ''
        if (wordcounterElement) wordcounterElement.style.animation = ''
      }, 0)

      filenameElement?.addEventListener('animationend', stopFileChangeAnim)
      setIsFileChange(true)
    }

    previousFileIdRef.current = idFileSegment

    return () => {
      clearTimeout(tmOutReset)
      filenameElement?.removeEventListener('animationend', stopFileChangeAnim)
      stopFileChangeAnim()
    }
  }, [isSticky, idFileSegment])

public/js/components/common/VirtualList/Rows/RowSegment.js:158

  • ref.current.firstChild and ref.current.children[1] are read unconditionally at the top of the effect, but the rendered children are conditional on file and file.weighted_words > 0. When file is falsy, ref.current.firstChild will be the next conditional element (or null), and children[1] may not be the wordcounter at all, leading to misapplied style.animation = 'none' resets on the wrong nodes. Compute these references inside the if (isSticky && ...) branch and after verifying the elements exist by class.
    const filenameElement = ref.current.firstChild
    const wordcounterElement = ref.current.children[1]

    if (
      isSticky &&
      idFileSegment !== previousFileIdRef.current &&
      typeof previousFileIdRef.current === 'number'
    ) {
      ref.current.style.animation = 'none'
      if (filenameElement) filenameElement.style.animation = 'none'
      if (wordcounterElement) wordcounterElement.style.animation = 'none'

      tmOutReset = setTimeout(() => {
        ref.current.style.animation = ''
        if (filenameElement) filenameElement.style.animation = ''
        if (wordcounterElement) wordcounterElement.style.animation = ''
      }, 0)

const file = files ? files.find((file) => file.id == idFileSegment) : false
let fileType = ''
if (file) {
fileType = file.file_name ? file.file_name.split('.')[1] : ''
}

setHeight(sid, value) {
this.map.clear()
Comment on lines +661 to +663
if (canDisplayContent && ref.current) {
segmentFooterHeight.setHeight(segment.sid, ref.current.offsetHeight)
}
Comment on lines 79 to +131
@@ -76,6 +94,7 @@ const VirtualList = forwardRef(
position: 'relative',
}}
>
{overlapHeader && overlapHeader}
{virtualItems.map((item) => (
<div
key={items[item.index].id ? items[item.index].id : item.index}
@@ -109,6 +128,8 @@ VirtualList.propTypes = {
align: PropTypes.string,
}),
onRender: PropTypes.func.isRequired,
setFirstRowIdVisible: PropTypes.func.isRequired,
Comment on lines 34 to +81
@@ -62,6 +76,10 @@ const VirtualList = forwardRef(
}`
}, [ref, width, height])

useEffect(() => {
setFirstRowIdVisible(firstRowIdVisible)
}, [firstRowIdVisible, setFirstRowIdVisible])
openSegmentComment: jest.fn(),
setBulkSelectionInterval: jest.fn(),
getMoreSegments: jest.fn(),
scrollToCurrentSegment: jest.fn(),
Comment on lines +846 to +858
if (typeof firstRowIdVisible !== 'undefined') {
const props = getSegmentPropsBySid(firstRowIdVisible)

return (
<div
className={`sticky-project-bar ${props.sideOpen ? 'sticky-project-bar-slide-right' : ''}`}
>
<ProjectBar
{...{...props, listRef: listRef.current, isSticky: true}}
/>
</div>
)
}
...(index === essentialRows.length - 1 && {
isLastRow: true,
}),
scrollValue: listRef.current ? listRef.current.scrollTop : 0,
@github-actions
Copy link
Copy Markdown

🧪 Test-Guard Report

✅ PASS — All changed source files have adequate test coverage.

Coverage Analysis: ❌ FAIL

No changed source files found in coverage report (threshold: 80%)

📋 4 files: 4 ❌ fail
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ❌ fail not in coverage report
public/js/components/common/VirtualList/VirtualList.js ❌ fail not in coverage report
public/js/components/segments/SegmentFooter.js ❌ fail not in coverage report
public/js/components/segments/SegmentsContainer.js ❌ fail not in coverage report

Test File Matching: ✅ PASS

File matching: 4 pass

📋 4 files: 4 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/Rows/RowSegment.test.js
public/js/components/common/VirtualList/VirtualList.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/VirtualList.test.js
public/js/components/segments/SegmentFooter.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentFooter.test.js
public/js/components/segments/SegmentsContainer.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentsContainer.test.js

Per-File Evaluation: ✅ PASS

Evaluated 4 files: 4 via AI (4 batches), 0 via shortcuts.

📋 4 files: 4 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Removed getProjectBar function; tests cover ProjectBar component behavior and scroll listener.
public/js/components/common/VirtualList/VirtualList.js ✅ pass Tests verify rendering, header overlap, and setFirstRowIdVisible callback invocation.
public/js/components/segments/SegmentFooter.js ✅ pass Tests mock IntersectionObserver and render SegmentFooter with content display logic.
public/js/components/segments/SegmentsContainer.js ✅ pass New ProjectBar rendering and row height logic tested via SegmentsContainer.test.js and RowSegment.test.js.

Result: ✅ PASS

@github-actions
Copy link
Copy Markdown

🧪 Test-Guard Report

⚠️ WARNING — Test coverage has minor gaps — review recommended.

Coverage Analysis: ❌ FAIL

No changed source files found in coverage report (threshold: 80%)

📋 4 files: 4 ❌ fail
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ❌ fail not in coverage report
public/js/components/common/VirtualList/VirtualList.js ❌ fail not in coverage report
public/js/components/segments/SegmentFooter.js ❌ fail not in coverage report
public/js/components/segments/SegmentsContainer.js ❌ fail not in coverage report

Test File Matching: ✅ PASS

File matching: 4 pass

📋 4 files: 4 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/Rows/RowSegment.test.js
public/js/components/common/VirtualList/VirtualList.js ✅ pass Test file modified in PR: public/js/components/common/VirtualList/VirtualList.test.js
public/js/components/segments/SegmentFooter.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentFooter.test.js
public/js/components/segments/SegmentsContainer.js ✅ pass Test file modified in PR: public/js/components/segments/SegmentsContainer.test.js

Per-File Evaluation: ⚠️ WARNING

Evaluated 4 files: 4 via AI (4 batches), 0 via shortcuts.

📋 4 files: 1 ⚠️ warning, 3 ✅ pass
File Verdict Reason
public/js/components/common/VirtualList/Rows/RowSegment.js ✅ pass Removed ProjectBar function; tests cover its rendering and scroll behavior thoroughly.
public/js/components/common/VirtualList/VirtualList.js ✅ pass Tests verify rendering, scroll behavior, header rendering, and setFirstRowIdVisible callback.
public/js/components/segments/SegmentFooter.js ⚠️ warning Tests mock IntersectionObserver and render component, but no direct height logic tests.
public/js/components/segments/SegmentsContainer.js ✅ pass New ProjectBar sticky rendering and row height margin logic tested indirectly via matched tests.

Result: ⚠️ WARNING


Why this WARNING?

  • Coverage Analysis: Four changed source files (RowSegment.js, VirtualList.js, SegmentFooter.js, SegmentsContainer.js) are missing from the coverage report, indicating a coverage instrumentation or configuration issue → update coverage configuration to include these files.
  • Per-File Evaluation: SegmentFooter.js tests mock IntersectionObserver and render the component but do not directly test height-related logic → add targeted tests for height logic in SegmentFooter.js.
  • Test File Matching: All changed source files have matching test files modified in the PR → no action needed.
  • Per-File Evaluation: Other files (RowSegment.js, VirtualList.js, SegmentsContainer.js) have adequate test coverage and logic testing → no action needed.

To resolve: fix coverage instrumentation/configuration to include the changed source files and add direct tests for height logic in SegmentFooter.js.

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.

2 participants