Skip to content

LEAN-5459 Pick and Highlight changes of a single author - #1142

Open
mbartenev-atypon wants to merge 12 commits into
masterfrom
LEAN-5459
Open

mbartenev-atypon wants to merge 12 commits into
masterfrom
LEAN-5459

Conversation

@mbartenev-atypon

@mbartenev-atypon mbartenev-atypon commented Apr 10, 2026

Copy link
Copy Markdown
Contributor
  • Added authors picker.
  • Introduced compact mode for the header
  • Refactored header a bit to fit in new components

Comment thread package.json Outdated
Copilot AI review requested due to automatic review settings April 14, 2026 11:32

Copilot AI 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 UI support for selecting a single collaborator/author and highlighting their tracked changes, alongside a refactor of the manuscript editor header to support a compact layout.

Changes:

  • Introduces a new EditorHeaderGroup component (with compact mode) and wires it into ManuscriptPageContainer.
  • Adds collaborator roster + dropdown components to toggle an “author highlight” state in the editor.
  • Updates dependency lockfile content (currently includes local link: overrides).

Reviewed changes

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

Show a summary per file
File Description
src/components/track-changes/utils.ts Adds TODO/commentary near suggestion selection logic.
src/components/track-changes/SortByDropdown.tsx Updates header copyright year.
src/components/projects/ManuscriptPageContainer.tsx Refactors header rendering to use EditorHeaderGroup.
src/components/projects/EditorHeaderGroup.tsx New combined header component with compact mode + track-changes toggle + collaborators roster.
src/components/projects/CollaboratorsRoster.tsx New roster UI that toggles highlighted author via editor plugin meta.
src/components/projects/CollaboratorsDropdownRoster.tsx New dropdown UI for overflow collaborators.
pnpm-lock.yaml Adds overrides + link:-based dependency resolution (machine-local paths).
package.json Minor ordering/formatting change in devDependencies.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +75
const users = roster.length ? [...roster] : [...usersDict.values()]
const highlightedAuthor = highlightedAuthorId
? usersDict.get(highlightedAuthorId)
: undefined
let pos = maxInMainRoster - 1

if (highlightedAuthor && users.indexOf(highlightedAuthor) > pos) {
// offsetting items when the selected user is in the dropdown has to move into the main roster to provide nicer UX
// insted of just swapping with the last in the main roster and making the rest look stale
while (users.indexOf(highlightedAuthor) != pos) {
const current = users.indexOf(highlightedAuthor)
const swap = current == users.length - 1 ? 0 : current + 1
;[users[current], users[swap]] = [users[swap], users[current]]
}
}
setRoster(users)

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

This effect uses the existing roster when roster.length is non-zero, which prevents newly discovered authors (from usersDict) from ever being added after the first run. As a result the roster can become stale when new pending changes appear. Consider always deriving users from usersDict.values() and then applying your reordering logic on top of that (preserving previous order where possible).

Suggested change
const users = roster.length ? [...roster] : [...usersDict.values()]
const highlightedAuthor = highlightedAuthorId
? usersDict.get(highlightedAuthorId)
: undefined
let pos = maxInMainRoster - 1
if (highlightedAuthor && users.indexOf(highlightedAuthor) > pos) {
// offsetting items when the selected user is in the dropdown has to move into the main roster to provide nicer UX
// insted of just swapping with the last in the main roster and making the rest look stale
while (users.indexOf(highlightedAuthor) != pos) {
const current = users.indexOf(highlightedAuthor)
const swap = current == users.length - 1 ? 0 : current + 1
;[users[current], users[swap]] = [users[swap], users[current]]
}
}
setRoster(users)
setRoster((prevRoster) => {
const latestUsers = [...usersDict.values()]
const users = [
...prevRoster.filter((user) => latestUsers.includes(user)),
...latestUsers.filter((user) => !prevRoster.includes(user)),
]
const highlightedAuthor = highlightedAuthorId
? usersDict.get(highlightedAuthorId)
: undefined
const pos = maxInMainRoster - 1
if (highlightedAuthor && users.indexOf(highlightedAuthor) > pos) {
// offsetting items when the selected user is in the dropdown has to move into the main roster to provide nicer UX
// insted of just swapping with the last in the main roster and making the rest look stale
while (users.indexOf(highlightedAuthor) != pos) {
const current = users.indexOf(highlightedAuthor)
const swap = current == users.length - 1 ? 0 : current + 1
;[users[current], users[swap]] = [users[swap], users[current]]
}
}
return users
})

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +36
return {
trackState: state.trackState,
collaboratorsById: state.collaboratorsById,
permittedActions: state.permittedActions,
isViewingMode: state.isViewingMode,
editor: state.editor,
isActive: state.isTrackingChangesVisible,
project: state.project,
}

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

The useStore selector is subscribing to permittedActions and isViewingMode, but those values are never used by this component. This creates unnecessary re-renders when those fields change. Please remove unused fields from the selector return value.

Copilot uses AI. Check for mistakes.
import React, { useCallback, useMemo, useState, useEffect } from 'react'
import styled from 'styled-components'

import { useStore } from '../../store/useStore'

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

This file imports useStore directly from ../../store/useStore, but the project’s barrel export already re-exports it (src/store/index.ts exports ./useStore). Prefer importing from ../../store to keep imports consistent and make refactors easier.

Suggested change
import { useStore } from '../../store/useStore'
import { useStore } from '../../store'

Copilot uses AI. Check for mistakes.
Comment on lines +145 to +155
export function GetName(user: UserProfile, project: Project, full = false) {
// @ts-ignore
const name = user.bibliographicName?.given as string

if (!name) {
return getUserRole(project, user.userID)
}
return full ? name : name[0]
}
export function GetSurname(
user: UserProfile,

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

GetName/GetSurname are regular helper functions but are named like React components (PascalCase). This is inconsistent with typical function naming and makes callsites harder to scan. Consider renaming them to getName/getSurname (and updating imports/usages).

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +69
<DropdownButtonContainer
ref={toggleButtonRef}
isOpen={isOpen}
onClick={toggleOpen}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
toggleOpen()
}
}}
className={'dropdown-toggle'}
tabIndex={0}
>
+{roster.length}
</DropdownButtonContainer>
{isOpen && (
<Dropdown minWidth={100}>
<RosterDropdown>
{roster.map(
(u, i) =>
u &&
u._id && (
<DropdownItem
type="button"
key={u._id}
onClick={() => onUserClick(u._id)}
aria-label={`Show changes made by ${GetName(u, project, true) + ' ' + GetSurname(u, collaboratorsById, true)}`}
>
<UserIconDisplay>
{GetName(u, project)}
{GetSurname(u, collaboratorsById)}
</UserIconDisplay>
<span>
{GetName(u, project, true)}{' '}
{GetSurname(u, collaboratorsById, true)}
</span>
</DropdownItem>
)
)}
</RosterDropdown>
</Dropdown>

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

When restRoster is empty this still renders a toggle button showing +0, which is confusing and creates an unnecessary focus target. Consider rendering this dropdown button only when roster.length > 0.

Suggested change
<DropdownButtonContainer
ref={toggleButtonRef}
isOpen={isOpen}
onClick={toggleOpen}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
toggleOpen()
}
}}
className={'dropdown-toggle'}
tabIndex={0}
>
+{roster.length}
</DropdownButtonContainer>
{isOpen && (
<Dropdown minWidth={100}>
<RosterDropdown>
{roster.map(
(u, i) =>
u &&
u._id && (
<DropdownItem
type="button"
key={u._id}
onClick={() => onUserClick(u._id)}
aria-label={`Show changes made by ${GetName(u, project, true) + ' ' + GetSurname(u, collaboratorsById, true)}`}
>
<UserIconDisplay>
{GetName(u, project)}
{GetSurname(u, collaboratorsById)}
</UserIconDisplay>
<span>
{GetName(u, project, true)}{' '}
{GetSurname(u, collaboratorsById, true)}
</span>
</DropdownItem>
)
)}
</RosterDropdown>
</Dropdown>
{roster.length > 0 && (
<>
<DropdownButtonContainer
ref={toggleButtonRef}
isOpen={isOpen}
onClick={toggleOpen}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
toggleOpen()
}
}}
className={'dropdown-toggle'}
tabIndex={0}
>
+{roster.length}
</DropdownButtonContainer>
{isOpen && (
<Dropdown minWidth={100}>
<RosterDropdown>
{roster.map(
(u, i) =>
u &&
u._id && (
<DropdownItem
type="button"
key={u._id}
onClick={() => onUserClick(u._id)}
aria-label={`Show changes made by ${GetName(u, project, true) + ' ' + GetSurname(u, collaboratorsById, true)}`}
>
<UserIconDisplay>
{GetName(u, project)}
{GetSurname(u, collaboratorsById)}
</UserIconDisplay>
<span>
{GetName(u, project, true)}{' '}
{GetSurname(u, collaboratorsById, true)}
</span>
</DropdownItem>
)
)}
</RosterDropdown>
</Dropdown>
)}
</>

Copilot uses AI. Check for mistakes.

if (highlightedAuthor && users.indexOf(highlightedAuthor) > pos) {
// offsetting items when the selected user is in the dropdown has to move into the main roster to provide nicer UX
// insted of just swapping with the last in the main roster and making the rest look stale

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

Typo in comment: "insted" → "instead".

Suggested change
// insted of just swapping with the last in the main roster and making the rest look stale
// instead of just swapping with the last in the main roster and making the rest look stale

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +93
aria-label="show tracked changes"
aria-pressed={isTrackingChangesVisible}
data-tooltip-content={compact ? label : ''}
>
{!compact && <Label>Show tracked changes</Label>}

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

The visible label and accessibility label are inconsistent with the current state: the text always renders "Show tracked changes" and aria-label is hardcoded, while the action toggles between show/hide. Use the computed label for the visible text (when not compact) and for aria-label so both reflect the current state.

Suggested change
aria-label="show tracked changes"
aria-pressed={isTrackingChangesVisible}
data-tooltip-content={compact ? label : ''}
>
{!compact && <Label>Show tracked changes</Label>}
aria-label={label}
aria-pressed={isTrackingChangesVisible}
data-tooltip-content={compact ? label : ''}
>
{!compact && <Label>{label}</Label>}

Copilot uses AI. Check for mistakes.
const view = editor.view
const tr = state.tr
/* @TODO consider moving this logic into tc plugin */
/** do we actually need a specia case for structure changes? will it not be covered by the node selection? */

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

Typo in comment: "specia" → "special".

Suggested change
/** do we actually need a specia case for structure changes? will it not be covered by the node selection? */
/** do we actually need a special case for structure changes? will it not be covered by the node selection? */

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +147
export const ManuscriptMenusContainer = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
background-color: ${(props) => props.theme.colors.background.secondary};
`
export const ManuscriptMenusContainerInner = styled.div`
width: 100%;
max-width: ${(props) => props.theme.grid.editorMaxWidth}px;
margin: 0 auto;
padding: ${(props) => props.theme.grid.unit / 2}px
${(props) => props.theme.grid.unit * 5}px
${(props) => props.theme.grid.unit}px
${(props) => props.theme.grid.unit * 15}px;
box-sizing: border-box;
`

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

ManuscriptMenusContainer / ManuscriptMenusContainerInner are now defined (and exported) here, but the same exports still exist in ManuscriptPageContainer.tsx. This duplication makes it unclear which should be imported going forward. Consider keeping a single source of truth (e.g., export them only from EditorHeaderGroup or move them to a shared styles module and re-export from both if backward compatibility is required).

Copilot uses AI. Check for mistakes.
Comment thread pnpm-lock.yaml Outdated
Comment on lines +7 to +10
overrides:
'@manuscripts/body-editor': link:../../../Library/pnpm/global/5/node_modules/@manuscripts/body-editor
'@manuscripts/track-changes-plugin': link:../../../Library/pnpm/global/5/node_modules/@manuscripts/track-changes-plugin

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

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

The lockfile introduces overrides and dependency specifiers that point to a developer-machine specific link:../../../Library/pnpm/global/... path. Committing these links will break installs/CI for other environments and also removes the resolved package snapshots from the lockfile. Please revert these links and pin @manuscripts/body-editor / @manuscripts/track-changes-plugin to published versions (or a workspace-local path within the repo if this is meant to be a monorepo link).

Copilot uses AI. Check for mistakes.
@zax-assistant

zax-assistant Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Published version 4.13.19-LEAN-5459.0

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.

3 participants