Skip to content

feat: Implement copy paste functionality for feedbacks/actions#4083

Open
njibhu wants to merge 1 commit into
bitfocus:mainfrom
njibhu:feat_copy_paste_action_feedback
Open

feat: Implement copy paste functionality for feedbacks/actions#4083
njibhu wants to merge 1 commit into
bitfocus:mainfrom
njibhu:feat_copy_paste_action_feedback

Conversation

@njibhu

@njibhu njibhu commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Implementing #4028

Adds a new copy action which copies the selected action to a clipboard:
Screenshot 2026-04-08 234946

Once a selected action is in the clipboard, a new paste action is available:
Screenshot 2026-04-08 235030

The copy pasting works across buttons, which is a very much wanted QoL to smooth out the iterative process for large surfaces with many similar buttons.
This feature also works with feedbacks.

Summary by CodeRabbit

Release Notes

  • New Features
    • Implemented entity copy/paste functionality with a new clipboard system, enabling users to duplicate entities throughout the application.
    • Added Copy button in entity controls and Paste button in the entity panel for easy access.
    • Type validation ensures users can only paste compatible entity types, maintaining data consistency across operations.

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces entity clipboard functionality, enabling users to copy and paste entities throughout the control system. Changes include a new backend paste API endpoint, a MobX-backed frontend clipboard store, integration with the entity editor service, and UI controls for copy/paste operations in entity panels.

Changes

Cohort / File(s) Summary
Entity Model & Type System
shared-lib/lib/Model/EntityModel.ts, companion/lib/Controls/Entities/EntityInstance.ts
Added zodSomeEntityModel schema validator for entity models; updated ControlEntityInstance.addChild() signature to accept optional isCloned parameter for controlling cloning behavior.
Backend Implementation
companion/lib/Controls/Entities/EntityListPoolBase.ts, companion/lib/Controls/EntitiesTrpcRouter.ts
Introduced entityPaste() method to insert entities with ID regeneration; added TRPC paste procedure endpoint accepting control ID, entity location, owner ID, and entity models.
Frontend Store & Context
webui/src/Stores/EntityClipboardStore.ts, webui/src/Stores/RootAppStore.tsx, webui/src/ContextData.tsx
Created new EntityClipboardStore with observable state for copied entities; integrated into RootAppStore and context provider for app-wide accessibility.
Frontend Services
webui/src/Services/Controls/ControlEntitiesService.ts, webui/src/Services/Controls/ControlActionsService.ts
Added performPaste() to entity editor service wired to TRPC mutation; added placeholder performPaste() to action recorder service.
Frontend UI Components
webui/src/Controls/Components/AddEntityPanel.tsx, webui/src/Controls/Components/EntityCellControls.tsx
Converted AddEntityPanel to MobX observer with conditional paste button and logic; added copy button to EntityRowHeader with clipboard integration.

Poem

📋 ✨ Copy, paste, and clone with ease,
Entities fly on the digital breeze,
Clipboard stores what you hold dear,
Then pastes it right back, crystal clear!

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature addition: implementing copy-paste functionality for feedbacks and actions, which aligns perfectly with the changeset's objectives.

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


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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
webui/src/Controls/Components/AddEntityPanel.tsx (1)

52-58: Consider if clipboard should clear after paste (design decision)

The clipboard isn't cleared after a successful paste, which means users can paste the same entity multiple times. This is actually similar to how OS clipboards work and could be a nice feature for duplicating the same action/feedback to multiple buttons!

Just wanted to flag this as a conscious design choice. If you'd prefer "cut-style" behavior (clear after paste), you could call entityClipboard.clear() after a successful paste. But the current approach seems like a reasonable UX for your use case of "large surfaces with many similar buttons" mentioned in the PR description.

webui/src/Stores/EntityClipboardStore.ts (1)

7-19: Consider marking getters as computed for MobX optimization

The getters work correctly as-is, but MobX can cache computed values and only recalculate when their dependencies change. This is a minor optimization, but it's a nice-to-have for MobX best practices.

💡 Optional improvement to add computed decorators
 	constructor() {
 		makeObservable<EntityClipboardStore, '_copiedEntity'>(this, {
 			_copiedEntity: observable,
+			copiedEntity: computed,
+			copiedEntityType: computed,
 		})
 	}

You'll also need to import computed:

-import { action, makeObservable, observable } from 'mobx'
+import { action, computed, makeObservable, observable } from 'mobx'

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 72451ea5-85f6-42d6-8796-4425ce50686e

📥 Commits

Reviewing files that changed from the base of the PR and between 9d843b3 and 30320f6.

📒 Files selected for processing (11)
  • companion/lib/Controls/Entities/EntityInstance.ts
  • companion/lib/Controls/Entities/EntityListPoolBase.ts
  • companion/lib/Controls/EntitiesTrpcRouter.ts
  • shared-lib/lib/Model/EntityModel.ts
  • webui/src/ContextData.tsx
  • webui/src/Controls/Components/AddEntityPanel.tsx
  • webui/src/Controls/Components/EntityCellControls.tsx
  • webui/src/Services/Controls/ControlActionsService.ts
  • webui/src/Services/Controls/ControlEntitiesService.ts
  • webui/src/Stores/EntityClipboardStore.ts
  • webui/src/Stores/RootAppStore.tsx

Comment on lines +71 to +73
performPaste: (_ownerId: EntityOwner | null, _entities: SomeEntityModel[]) => {
// Not supported in action recorder
},

@coderabbitai coderabbitai Bot Apr 8, 2026

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 | 🟠 Major

Paste is currently exposed as a no-op in action recorder.

On Line 71, this handler does nothing, so users can hit “Paste” and get no effect. For unsupported features in this service, it’s better to keep the action unavailable (or surface an explicit “not supported” notification).

Suggested fix
-			performPaste: (_ownerId: EntityOwner | null, _entities: SomeEntityModel[]) => {
-				// Not supported in action recorder
-			},
+			performPaste: undefined,
📝 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
performPaste: (_ownerId: EntityOwner | null, _entities: SomeEntityModel[]) => {
// Not supported in action recorder
},
performPaste: undefined,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't want to break with what seems to be the existing convention, all other functions that are not supported in that file are implemented like this.

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.

@njibhu, that's a fair point! Looking at the file more carefully, the convention here is indeed mixed — performLearn, setEnabled, and setHeadline are set to undefined, while setInverted, setVariableName, setVariableValue, and a few others use the no-op pattern you followed. Both approaches exist side by side, so your choice is completely reasonable. I'll defer to your judgment on keeping it consistent with the no-op style you were following. Thanks for explaining! 🐇


🧠 Learnings used
Learnt from: arikorn
Repo: bitfocus/companion PR: 3916
File: webui/src/Hooks/useShowSecondaryPanel.ts:27-29
Timestamp: 2026-03-24T00:26:41.745Z
Learning: In the bitfocus/companion repo’s TanStack Router configuration, setting `trailingSlash: 'never'` on `createRouter()` does not normalize/strip trailing slashes at runtime (it had no effect in testing). When reviewing route-matching issues related to trailing slashes, do not recommend `trailingSlash: 'never'` as a fix in this repo; look for other causes/solutions instead.

Learnt from: Julusian
Repo: bitfocus/companion PR: 4077
File: companion/test/Service/Satellite/SatelliteApi.test.ts:650-654
Timestamp: 2026-04-08T21:40:58.021Z
Learning: In the bitfocus/companion repo, avoid adding code review comments that only restate TypeScript compiler type errors (e.g., TS18048 “possibly undefined”, type mismatches). Rely on the TypeScript compiler/CI to catch these issues, and focus review comments on problems that are not already covered by compilation (logic, correctness, maintainability, security, etc.).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The pattern here is that ui 'features' which are optional can be set to undefined, which is used by the ui to hide any ui for that feature.
some fo the editing is no-ops because the entities here are set to readonly, so in reality these can never be fired.

so maybe the performPaste should be undefined here, to let the ui use this to hide the paste button?

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@arikorn

arikorn commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

I haven't reviewed or tried the code but this is much-needed functionality. Thanks for taking it on @njibhu !

One suggestion: from your description it looks like one can only paste to the end of the actions/feedbacks list. It would be great if one could paste to any position: (1) select an existing item to replace it. (2) select the line between two items or line at top/bottom of list to insert the item. Either way, the paste location could be highlighted with a border/line, similar to the buttons grid.

@njibhu

njibhu commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

I haven't reviewed or tried the code but this is much-needed functionality. Thanks for taking it on @njibhu !

One suggestion: from your description it looks like one can only paste to the end of the actions/feedbacks list. It would be great if one could paste to any position: (1) select an existing item to replace it. (2) select the line between two items or line at top/bottom of list to insert the item. Either way, the paste location could be highlighted with a border/line, similar to the buttons grid.

Thanks for checking this.
Concerning the suggestion, I'm not sure how that would work, there's currently no way to select where an action is being added to the list, and pasting an action is pretty much like adding an action with prefilled data, it's not replacing any existing action. So I think being able to "add" new actions in the middle of the list would likely be outside of the scope of this change.
(See screenshot below for context)

Screenshot 2026-04-09 032422

@arikorn

arikorn commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

@njibhu I agree: your current code provides a foundation to build upon. The good news is that most of the bits and pieces needed to add the type of flexibility I described is already in other parts of the codebase, so if you're interested, here's an outline of sorts for how to "supercharge" your PR.

After thinking about it (and looking a Trello -- see image, below), I realized that, using your design, each action/feedback ("entity" i think?) needs only a single new HTML element added at its top. The element replaces the current separation and acts as a selectable marker for where to add the next entity. The cool thing is that it could apply to both "add action" and "paste." Adding an entity below the last item is the current behavior, so it's already covered. Here's the idea:

  1. The code at the end of this note creates the dashed-line effect on hover and focus. The <div> could be made into a component which, on click, toggles the ".selected" class and also gets info either from a data- prop specifying its position or maybe more cleanly, deduces it from its parent. The CSS code in the <style> section would go in _button-edit.scss or similar file. The current padding between elements should be replaced by this new element. (If you're not comfortable with ::after, you can just make an explicitly nested div instead.)
  2. Adding an entity mid-list is already covered in the existing "duplicate entity" and drag-and-drop functionality (see PR Feature: make action-list drag-and-drop action clearer #3908 , for possible guidance to moving things around).
  3. (optional/bonus) Add keyboard shortcuts for cut/copy/paste (cut would just be copy + delete)
  4. (optional/bonus) Add a context menu for cut/copy/paste (see Sidebar.tsx for a context-menu example).

The first two steps build strictly upon what you've done. The other steps might need to make the whole action selectable in order to handle cut/copy (again, see the drag-and-drop or button-grid for example of handling highlighting) - though paste would be possible without that.

Minor notes about your code:

  • Currently the clipboard is never cleared, which might leave a "random paste" in the UI after the user has moved on. (I think the rabbit pointed this one out too.)
  • Related: perhaps this PR could use a shared clipboard? No doubt there are pros and cons, but it might also help with clearing the currently copied action/feedback.

Example of how to visually mark adding between elements (from Trello). I'm not recommending the "+" element here, just the dashed line.

image

This CSS/HTML creates a "spacer" which shows a dashed line upon hover, etc. (You should be able to copy this as-is into a "test.html" file to see it work in your browser.) Colors and thicknesses are a suggestion.

<html>
<head>
<style>
  .spacer {
	background-color: lightgrey;
	display: flex;
	align-items: center;
	height: 10px; /* choose a height for the spacer */
	width: 100%;
  }

  .spacer::after {
	content: '';
	border: 1px dashed darkred; /* this forms a 2-pixel border (top & bottom with 0 height intervening) */
	height: 0px;
	flex: 100%;
	visibility: hidden;
  }

	/* in React, manage the .selected property: toggle onClick of the spacer div, for example */
  .spacer:is(:hover, :focus, .selected)::after {
    visibility: visible;
  }
</style>
</head>
<body>
  <!-- the tabindex makes it able to acquire focus, including by keyboard -->
  <div class="spacer" tabindex="0"></div>

</body>
</html>

@Julusian Julusian added this to the v4.4 milestone Apr 17, 2026
@github-project-automation github-project-automation Bot moved this to In Progress in Companion Plan Apr 17, 2026
@njibhu

njibhu commented Apr 19, 2026

Copy link
Copy Markdown
Contributor Author

I think it would be probably best to keep the discussion around the scope of the issue and the PR.
The current code is easy to review as fully self contained feature, and I do not want to make this PR rethink how adding actions work.

@Julusian, any feedback or changes required ?

@Julusian

Copy link
Copy Markdown
Member

while I would really like this to use the system clipboard, I think that would require the navigator clipboard api which only works over https (or off localhost).
I expect there to be a follow up request quite soon after this, to allow copy and pasting across tabs (easier copying across pages) as well as copying multiple at a time.

my brain isnt in a space of thinking about ui much toady, so not 100% sure how I feel about this. it does feel like we are collecting a lot of buttons, and maybe some kind of dropdown or context menu might be appropriate now/soon?

but if we want to allow for multi select for bulk copy/paste/move as a follow up that will probably require a larger rethink of how these elements look or are arranged or something to allow for that, so maybe the concern of number of buttons is not good to think about before that

@njibhu

njibhu commented Apr 22, 2026

Copy link
Copy Markdown
Contributor Author

while I would really like this to use the system clipboard, I think that would require the navigator clipboard api which only works over https (or off localhost). I expect there to be a follow up request quite soon after this, to allow copy and pasting across tabs (easier copying across pages) as well as copying multiple at a time.

So the original reason why I didn't want to use the navigator's clipboard api, was that I would see it only really be relevant if we were trying to "export/import" outside of companion (but there's already an export feature). But since this is a copy/paste action scoped to the companion instance, I do see a lot of benefits to keep it fully in-browser, which means that for example we don't need to parse unknown inputs. And as you mentioned, the clipboard api also has further restrictions, and can even be blocked fully by certain browsers/policies.

I think right now supporting copying across tabs could be added without too much changes via localstorage. I can investigate this change here.

@Julusian

Copy link
Copy Markdown
Member

I think right now supporting copying across tabs could be added without too much changes via localstorage. I can investigate this change here.

Im not sure if local storage is best, as that will persist between restarts of the browser. but I think there is some way tabs can communicate, which could work and be good enough?
or Im happy for it to be left as-is.

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

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants