Skip to content

Sw feature 8627#8728

Open
Seth-Wadsworth wants to merge 3 commits intomakeplane:previewfrom
WSU-CptS-481-Spring-2026:SW-Feature-8627
Open

Sw feature 8627#8728
Seth-Wadsworth wants to merge 3 commits intomakeplane:previewfrom
WSU-CptS-481-Spring-2026:SW-Feature-8627

Conversation

@Seth-Wadsworth
Copy link

@Seth-Wadsworth Seth-Wadsworth commented Mar 8, 2026

Description

This PR fixes an issue where users were unable to select or copy text inside issue rows in the list view. Previously, any attempt to highlight text triggered navigation or drag behavior, preventing users from copying work item IDs, titles, and other inline text. The update introduces selection‑safe regions, adjusts event‑handling logic, and ensures that drag and navigation behaviors no longer override text selection.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

To verify the fix, the following tests were performed:
Confirmed that text inside issue rows (ID, title, metadata) can be selected and copied without triggering navigation.
Verified that clicking a row still navigates correctly when no text is selected.
Ensured drag‑and‑drop behavior remains unchanged and still works exclusively through the drag handle.
Added unit tests for updated event‑handling logic where applicable.
Performed manual interaction testing across Chrome, Firefox, and Safari to validate consistent behavior.

References

#8627

Summary by CodeRabbit

  • New Features

    • Added text selection detection to prevent accidental dragging in list items.
    • Added new selectable option to control links for better interaction handling.
  • Bug Fixes

    • Fixed text truncation in list items to improve display of longer content without clipping.
    • Improved click handling with text selection guards to prevent unintended interactions.
  • Tests

    • Added comprehensive tests for drag-and-drop and expand/collapse behaviors in lists.

@CLAassistant
Copy link

CLAassistant commented Mar 8, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

Removes truncation styling from list items, introduces utility functions for text-selection-aware drag behavior and expand/collapse state management, updates the block component with selection guards and restructured DOM layout, enhances ControlLink with a selectable prop and guards, adds comprehensive tests, and updates TypeScript configuration.

Changes

Cohort / File(s) Summary
List Item Styling
apps/web/core/components/core/list/list-item.tsx
Removed truncate and overflow-hidden classes from container, left block, and title elements; changed title text handling to normal with select-text class.
Drag-and-Drop Logic
apps/web/core/components/issues/issue-layouts/list/block.logic.ts
Added four utility functions: shouldSuppressEvent() detects text selection, canDragBasedOnSelection() gates drag based on selection state, nextExpandState() toggles expand for nesting level < 3, getDragDisallowedToast() returns localized toast feedback.
Block Component Updates
apps/web/core/components/issues/issue-layouts/list/block.tsx
Integrated text-selection guards into drag, click, and chevron handlers; commented out sub-issue computation; restructured DOM with Tooltip wrappers, absolute overlay placeholder, revised checkbox/selection blocks, dedicated chevron area, and stats rendering via HOC; added onMouseDown propagation stop on action bar.
Block Logic Tests
packages/codemods/tests/block.logic.test.ts
Added test suite covering all four block.logic utilities, verifying text selection detection, drag gating, expand state toggling, and toast feedback generation.
TypeScript Configuration
packages/codemods/tsconfig.json
Added root-level include array specifying src, tests, and ../../apps/web paths for TypeScript compilation scope.
ControlLink Enhancement
packages/ui/src/control-link/control-link.tsx
Added optional selectable?: boolean prop; introduced text-selection guards in handleOnClick and handleMouseDown; conditionally render span wrapper when selectable is true; preserved disabled state handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With selection guards in place so neat,
No accidental drags will greet!
Truncation lifted, text runs free,
Toasts appear when they must be—
A rabbit's gift: interactions that feel just right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "Sw feature 8627" is vague and non-descriptive, using only a ticket reference without explaining the actual change or feature being implemented. Use a descriptive title summarizing the main change, such as "Allow text selection in issue list rows" or "Fix text selection blocking drag and navigation behaviors".
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description provides clear context, detailed change rationale, comprehensive test scenarios, and proper template sections, fully addressing the requirements.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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
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: 5

🧹 Nitpick comments (1)
apps/web/core/components/issues/issue-layouts/list/block.logic.ts (1)

5-35: Please route IssueBlock through these helpers before relying on the new tests.

packages/codemods/tests/block.logic.test.ts exercises this module, but apps/web/core/components/issues/issue-layouts/list/block.tsx still duplicates the same selection, drag, expand, and toast decisions inline instead of importing these functions. That leaves you with green tests over helper code while the live component can drift separately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/core/components/issues/issue-layouts/list/block.logic.ts` around
lines 5 - 35, The IssueBlock component in block.tsx is still computing
selection, drag, expand and toast logic inline; instead import and use the
helper functions shouldSuppressEvent, canDragBasedOnSelection, nextExpandState,
and getDragDisallowedToast from block.logic.ts so the component behavior matches
the tests—replace inline checks for selectionText, drag permission,
nesting/expanded state toggling, and building the drag-disallowed toast with
calls to these helpers (use shouldSuppressEvent(selectionText) for suppressing
events, canDragBasedOnSelection(selectionText, isAllowed) for drag gating,
nextExpandState(nestingLevel, isExpanded) for toggling expansion, and
getDragDisallowedToast(canDrag, canEdit) for the toast).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/core/components/issues/issue-layouts/list/block.tsx`:
- Around line 121-127: The draggable() is being applied to an invisible absolute
element (the variable element) so the drag hotspot spans the whole row; instead,
limit dragging to the visible handle by attaching draggable() to the actual
handle node or by guarding canDrag to only allow drag when the event target is
the handle (e.g., check event.target or a handleRef/class) and keep checking
window.getSelection() and isDraggingAllowed; apply the same fix where
draggable(...) is used later (the other combine call around the second element
usage) so only the handle can initiate drags.
- Around line 324-329: The epic stats are shown when displayProperties is true
even if the actual count is zero; update the WithDisplayPropertiesHOC guard so
it also checks the runtime sub-issue count. Change the shouldRenderProperty
callback used with displayPropertyKey="sub_issue_count" to return a boolean that
combines the display property and the local sub-issue count (e.g.,
shouldRenderProperty = (properties) => !!properties.sub_issue_count &&
!!subIssuesCount) so IssueStats stays hidden when subIssuesCount is 0.

In `@packages/codemods/tsconfig.json`:
- Line 13: The tsconfig "include" currently pulls in "../../apps/web" which
forces the codemods package to type-check unrelated TSX files and path-aliases;
update the tsconfig for the codemods package (the "include" array in
packages/codemods/tsconfig.json) to remove "../../apps/web" so the program is
scoped to only "src" and "tests" (optionally add an "exclude" if you want to be
explicit); ensure no other cross-package project references are added so tests
still transitively pick up block.logic.ts without importing the whole web app.

In `@packages/ui/src/control-link/control-link.tsx`:
- Around line 54-60: The fallback branches for ControlLink break link semantics:
the disabled branch still renders an href so disabled links remain navigable,
and the selectable branch replaces the anchor with a span removing href/onClick
and keyboard semantics. Fix by changing the disabled branch (where code checks
disabled && (ref || className)) to render an <a> without an href (remove href or
set href={undefined}) and add aria-disabled="true" and tabIndex={-1} so it is
non-navigable but still an anchor; and change the selectable branch (the branch
that currently returns a <span>) to render an <a> that preserves href and
onClick/keyboard handlers (or if no href is provided, render an <a role="button"
tabIndex={0} onKeyDown={...} onClick={onClick}) so selectable links keep
keyboard and link semantics. Ensure these changes reference the component props
href, onClick, selectable, disabled, ref, className, and children.
- Around line 37-40: The guard inside handleOnClick that checks
window.getSelection()?.toString() should call event.preventDefault() (and
event.stopPropagation() if you want to fully mirror the other guard) before
returning so the anchor's default navigation is suppressed when the user has a
text selection; update the handleOnClick function (the
React.MouseEvent<HTMLAnchorElement, MouseEvent> handler) to call
event.preventDefault() (and optionally event.stopPropagation()) then return.

---

Nitpick comments:
In `@apps/web/core/components/issues/issue-layouts/list/block.logic.ts`:
- Around line 5-35: The IssueBlock component in block.tsx is still computing
selection, drag, expand and toast logic inline; instead import and use the
helper functions shouldSuppressEvent, canDragBasedOnSelection, nextExpandState,
and getDragDisallowedToast from block.logic.ts so the component behavior matches
the tests—replace inline checks for selectionText, drag permission,
nesting/expanded state toggling, and building the drag-disallowed toast with
calls to these helpers (use shouldSuppressEvent(selectionText) for suppressing
events, canDragBasedOnSelection(selectionText, isAllowed) for drag gating,
nextExpandState(nestingLevel, isExpanded) for toggling expansion, and
getDragDisallowedToast(canDrag, canEdit) for the toast).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19095843-1c50-418b-86c1-d1a8c1fbdad4

📥 Commits

Reviewing files that changed from the base of the PR and between 6627282 and c3ea967.

📒 Files selected for processing (6)
  • apps/web/core/components/core/list/list-item.tsx
  • apps/web/core/components/issues/issue-layouts/list/block.logic.ts
  • apps/web/core/components/issues/issue-layouts/list/block.tsx
  • packages/codemods/tests/block.logic.test.ts
  • packages/codemods/tsconfig.json
  • packages/ui/src/control-link/control-link.tsx

Comment on lines 121 to +127
return combine(
draggable({
element,
canDrag: () => isDraggingAllowed,
canDrag: () => {
if (window.getSelection()?.toString()) return false;
return isDraggingAllowed;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This makes the drag hotspot effectively row-wide again.

draggable() is now attached to an invisible absolute element that spans the main content area, so dragging can start from almost any blank space in the row instead of a dedicated handle. That reintroduces the same click/selection conflicts this change is trying to eliminate.

Also applies to: 223-227

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/core/components/issues/issue-layouts/list/block.tsx` around lines
121 - 127, The draggable() is being applied to an invisible absolute element
(the variable element) so the drag hotspot spans the whole row; instead, limit
dragging to the visible handle by attaching draggable() to the actual handle
node or by guarding canDrag to only allow drag when the event target is the
handle (e.g., check event.target or a handleRef/class) and keep checking
window.getSelection() and isDraggingAllowed; apply the same fix where
draggable(...) is used later (the other combine call around the second element
usage) so only the handle can initiate drags.

Comment on lines +324 to 329
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep epic stats hidden when the sub-issue count is zero.

This guard now only checks the display flag, so IssueStats renders for every epic even when subIssuesCount is 0. The Kanban block keeps the old behavior by also gating on !!subIssuesCount.

Suggested fix
                 {isEpic && displayProperties && (
                   <WithDisplayPropertiesHOC
                     displayProperties={displayProperties}
                     displayPropertyKey="sub_issue_count"
-                    shouldRenderProperty={(properties) => !!properties.sub_issue_count}
+                    shouldRenderProperty={(properties) =>
+                      !!properties.sub_issue_count && !!subIssuesCount
+                    }
                   >
📝 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
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count}
>
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) =>
!!properties.sub_issue_count && !!subIssuesCount
}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/core/components/issues/issue-layouts/list/block.tsx` around lines
324 - 329, The epic stats are shown when displayProperties is true even if the
actual count is zero; update the WithDisplayPropertiesHOC guard so it also
checks the runtime sub-issue count. Change the shouldRenderProperty callback
used with displayPropertyKey="sub_issue_count" to return a boolean that combines
the display property and the local sub-issue count (e.g., shouldRenderProperty =
(properties) => !!properties.sub_issue_count && !!subIssuesCount) so IssueStats
stays hidden when subIssuesCount is 0.

"noFallthroughCasesInSwitch": true
}
},
"include": ["src", "tests", "../../apps/web"]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the codemods TS program scoped to codemods sources.

Line 13 pulls the entire apps/web project into a node-library tsconfig that does not carry the web app's JSX/path-alias settings. That turns one cross-package test import into type-checking for unrelated TSX files and @/* imports, which is likely to break linting/type-checking for this package. The test already brings block.logic.ts into the program transitively, so the broad include is unnecessary.

Suggested fix
-  "include": ["src", "tests", "../../apps/web"]
+  "include": ["src", "tests"]
📝 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
"include": ["src", "tests", "../../apps/web"]
"include": ["src", "tests"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/codemods/tsconfig.json` at line 13, The tsconfig "include" currently
pulls in "../../apps/web" which forces the codemods package to type-check
unrelated TSX files and path-aliases; update the tsconfig for the codemods
package (the "include" array in packages/codemods/tsconfig.json) to remove
"../../apps/web" so the program is scoped to only "src" and "tests" (optionally
add an "exclude" if you want to be explicit); ensure no other cross-package
project references are added so tests still transitively pick up block.logic.ts
without importing the whole web app.

Comment on lines 37 to +40
const handleOnClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (window.getSelection()?.toString()) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent the anchor's default navigation when selection exists.

This guard returns early, but it never calls event.preventDefault(). On an <a>, that means the browser can still follow href, which defeats the text-selection fix and bypasses the explicit preventDefault/stopPropagation guard already added in apps/web/core/components/issues/issue-layouts/list/block.tsx.

Suggested fix
   const handleOnClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
     if (window.getSelection()?.toString()) {
+      event.preventDefault();
+      event.stopPropagation();
       return;
     }
📝 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 handleOnClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (window.getSelection()?.toString()) {
return;
}
const handleOnClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (window.getSelection()?.toString()) {
event.preventDefault();
event.stopPropagation();
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/control-link/control-link.tsx` around lines 37 - 40, The
guard inside handleOnClick that checks window.getSelection()?.toString() should
call event.preventDefault() (and event.stopPropagation() if you want to fully
mirror the other guard) before returning so the anchor's default navigation is
suppressed when the user has a text selection; update the handleOnClick function
(the React.MouseEvent<HTMLAnchorElement, MouseEvent> handler) to call
event.preventDefault() (and optionally event.stopPropagation()) then return.

Comment on lines 54 to 60
// if disabled but still has a ref or a className then it has to be rendered without a href
if (disabled && (ref || className))
return (
<a ref={ref} className={className}>
<a ref={ref as React.Ref<HTMLAnchorElement>} className={className} href={href ?? "#"}>
{children}
</a>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The new fallback branches no longer preserve ControlLink semantics.

The disabled branch still renders an href, so "disabled" links remain navigable. The selectable branch switches to a span and drops both href and onClick, so the same component stops navigating and loses keyboard/link semantics entirely. This is already reachable today from apps/web/core/components/issues/issue-layouts/list/block.tsx:180-195, which passes both disabled and className.

Also applies to: 65-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/control-link/control-link.tsx` around lines 54 - 60, The
fallback branches for ControlLink break link semantics: the disabled branch
still renders an href so disabled links remain navigable, and the selectable
branch replaces the anchor with a span removing href/onClick and keyboard
semantics. Fix by changing the disabled branch (where code checks disabled &&
(ref || className)) to render an <a> without an href (remove href or set
href={undefined}) and add aria-disabled="true" and tabIndex={-1} so it is
non-navigable but still an anchor; and change the selectable branch (the branch
that currently returns a <span>) to render an <a> that preserves href and
onClick/keyboard handlers (or if no href is provided, render an <a role="button"
tabIndex={0} onKeyDown={...} onClick={onClick}) so selectable links keep
keyboard and link semantics. Ensure these changes reference the component props
href, onClick, selectable, disabled, ref, className, and children.

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