feat(editor): Polish workflow review experience - #36941
Conversation
Improve review navigation, activity discussions, and workflow diff presentation for a clearer review flow. Co-authored-by: Cursor <cursoragent@cursor.com>
Bundle ReportChanges will decrease total bundle size by 124.61kB (-0.2%) ⬇️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: editor-ui-esmAssets Changed:
Files in
Files in
Files in
Files in
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
6 issues found across 18 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue">
<violation number="1" location="packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue:171">
P2: When one side is missing, the panel is fixed to 96px (--height--5xl) while .emptyWorkflow now adds 12px vertical padding, leaving only ~72px for the heading plus the description slot text. At the review panel's narrow width the explanatory sentence wraps, so the centered empty-state text gets clipped by the outer overflow:hidden container or spills over the sibling canvas. Give the empty panel room to fit the content (e.g. min-height instead of a fixed height, or allow it to size to its content up to a cap) so the new/deleted message stays fully visible.</violation>
</file>
<file name="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewDetailTabs.vue">
<violation number="1" location="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewDetailTabs.vue:182">
P2: When the activity feed first-page request fails, open reviews no longer show the comment composer, so users cannot post comments during a transient feed error. Keep the composer available outside the feed error/loading branch or render the composer slot in the feed’s initial error state.</violation>
</file>
<file name="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewChangesSection.vue">
<violation number="1" location="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewChangesSection.vue:221">
P2: The comment says the design calls for 8px (radii/m), but `--radius--lg` is 20px (`--radius--md` is the 16px one being replaced). The 8px token is `--radius--xs` (0.5rem). As written, the radius is now larger than it was before, the opposite of the stated intent.</violation>
</file>
<file name="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewDetailMetadata.vue">
<violation number="1" location="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewDetailMetadata.vue:46">
P2: This status text now hardcodes the separator and token order, so translations can no longer control how state and decision are composed. Render the localized combined label from `statusDisplay.label` here to keep status grammar and punctuation locale-safe.</violation>
</file>
<file name="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewCommentComposer.vue">
<violation number="1" location="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewCommentComposer.vue:90">
P2: At narrow review widths, this leaves only a 1px inset while `N8nChatInput`'s focus ring is 2px, so the left focus outline is clipped by the detail scrollport. Use the focus-ring width for the inset.</violation>
</file>
<file name="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewActivityFeed.vue">
<violation number="1" location="packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewActivityFeed.vue:19">
P2: At the narrow `review-detail` breakpoint, `detailBody` changes to a column and places metadata after the activity panel. With this shared root, `scrollToBottom()` can land at the bottom of a tall metadata rail instead of showing the newest comment or composer. Keep the activity feed bounded at the narrow container breakpoint, or scroll the activity list’s last item rather than the whole detail body.
(Based on your team's feedback about keeping the activity feed as the scroll container at narrow review-detail widths.)</violation>
</file>
Architecture diagram
sequenceDiagram
participant User
participant LS as Local Storage
participant View as WorkflowReviewRequestsView
participant Sidebar as WorkflowReviewRequestsSidebar
participant Detail as WorkflowReviewDetailTabs
participant Feed as WorkflowReviewActivityFeed
participant Composer as WorkflowReviewCommentComposer
participant Store as ReviewActivityStore
Note over View,LS: View Initialization
View->>LS: GET N8N_WORKFLOW_REVIEW_SIDEBAR_WIDTH
LS-->>View: persisted width
View->>Sidebar: NEW: Render inside ResizeWrapper
Note over Detail,Feed: Shared Scroll Coordination
Detail->>Detail: Create detailBodyRef (HTMLElement)
Detail->>Feed: NEW: Provide scroll root (InjectionKey)
Feed->>Feed: CHANGED: Attach IntersectionObserver to parent scroll root
Note over User,Composer: Composer & Activity Flow
User->>Composer: Types multiline comment
Composer->>Composer: NEW: ResizeObserver triggers scrollIntoView()
User->>Composer: Clicks Send
Composer->>Store: postComment(text)
Store->>Store: Set posting = true
Store-->>Feed: Reactive update (entries)
rect rgb(240, 240, 240)
Note over Feed: Animation Logic
Feed->>Feed: NEW: Sync watch (entries + posting)
Feed->>Feed: NEW: Set enteringCommentId
Feed->>User: Render entry with CSS fade-in-up + sharpen
end
Store->>Store: Set posting = false
Note over Detail,Feed: Metadata & Layout
Detail->>Detail: Render Metadata (Sticky self-start)
Note over Detail: Metadata and Feed now scroll<br/>together in shared detailBodyRef
alt User resizes sidebar
User->>View: Drag resize handle
View->>LS: NEW: Persist width as fraction
end
Tip: instead of fixing issues one by one fix them all with cubic
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
| /* A missing side is context, not a second canvas. GitHub/GitLab similarly keep | ||
| the "new/deleted" fact visible while giving the actual changed content the | ||
| reviewing space. */ | ||
| .emptyWorkflowPanel { |
There was a problem hiding this comment.
P2: When one side is missing, the panel is fixed to 96px (--height--5xl) while .emptyWorkflow now adds 12px vertical padding, leaving only ~72px for the heading plus the description slot text. At the review panel's narrow width the explanatory sentence wraps, so the centered empty-state text gets clipped by the outer overflow:hidden container or spills over the sibling canvas. Give the empty panel room to fit the content (e.g. min-height instead of a fixed height, or allow it to size to its content up to a cap) so the new/deleted message stays fully visible.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/editor-ui/src/features/workflows/workflowDiff/WorkflowDiffContent.vue, line 171:
<comment>When one side is missing, the panel is fixed to 96px (--height--5xl) while .emptyWorkflow now adds 12px vertical padding, leaving only ~72px for the heading plus the description slot text. At the review panel's narrow width the explanatory sentence wraps, so the centered empty-state text gets clipped by the outer overflow:hidden container or spills over the sibling canvas. Give the empty panel room to fit the content (e.g. min-height instead of a fixed height, or allow it to size to its content up to a cap) so the new/deleted message stays fully visible.</comment>
<file context>
@@ -165,6 +165,13 @@ function getEdgeStatusClass(id: string) {
+/* A missing side is context, not a second canvas. GitHub/GitLab similarly keep
+ the "new/deleted" fact visible while giving the actual changed content the
+ reviewing space. */
+.emptyWorkflowPanel {
+ flex: 0 0 var(--height--5xl);
+}
</file context>
| </N8nCallout> | ||
| </template> | ||
| <!-- Closed reviews take no new comments (the backend 409s) --> | ||
| <template v-if="review.state === 'open'" #composer> |
There was a problem hiding this comment.
P2: When the activity feed first-page request fails, open reviews no longer show the comment composer, so users cannot post comments during a transient feed error. Keep the composer available outside the feed error/loading branch or render the composer slot in the feed’s initial error state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewDetailTabs.vue, line 182:
<comment>When the activity feed first-page request fails, open reviews no longer show the comment composer, so users cannot post comments during a transient feed error. Keep the composer available outside the feed error/loading branch or render the composer slot in the feed’s initial error state.</comment>
<file context>
@@ -172,13 +178,11 @@ const tabOptions = computed(() => [
</N8nCallout>
</template>
+ <!-- Closed reviews take no new comments (the backend 409s) -->
+ <template v-if="review.state === 'open'" #composer>
+ <WorkflowReviewCommentComposer :can-comment="viewerCanComment" />
+ </template>
</file context>
| border: var(--border-width) var(--border-style) var(--border-color--subtle); | ||
| border-radius: var(--radius--md); | ||
| /* 8px — the design's radii/m. Not `--radius--md`: that primitive is 16px. */ | ||
| border-radius: var(--radius--lg); |
There was a problem hiding this comment.
P2: The comment says the design calls for 8px (radii/m), but --radius--lg is 20px (--radius--md is the 16px one being replaced). The 8px token is --radius--xs (0.5rem). As written, the radius is now larger than it was before, the opposite of the stated intent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewChangesSection.vue, line 221:
<comment>The comment says the design calls for 8px (radii/m), but `--radius--lg` is 20px (`--radius--md` is the 16px one being replaced). The 8px token is `--radius--xs` (0.5rem). As written, the radius is now larger than it was before, the opposite of the stated intent.</comment>
<file context>
@@ -217,7 +217,8 @@ const targetLabel = computed(() =>
border: var(--border-width) var(--border-style) var(--border-color--subtle);
- border-radius: var(--radius--md);
+ /* 8px — the design's radii/m. Not `--radius--md`: that primitive is 16px. */
+ border-radius: var(--radius--lg);
overflow: hidden;
}
</file context>
| {{ statusDisplay.stateLabel }} | ||
| <!-- Decorative, like the activity headline's divider; the dot's aria-label | ||
| keeps the shared combined form. --> | ||
| <span aria-hidden="true" :class="$style.statusSeparator">|</span> |
There was a problem hiding this comment.
P2: This status text now hardcodes the separator and token order, so translations can no longer control how state and decision are composed. Render the localized combined label from statusDisplay.label here to keep status grammar and punctuation locale-safe.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewDetailMetadata.vue, line 46:
<comment>This status text now hardcodes the separator and token order, so translations can no longer control how state and decision are composed. Render the localized combined label from `statusDisplay.label` here to keep status grammar and punctuation locale-safe.</comment>
<file context>
@@ -39,7 +39,13 @@ const statusSummary = computed(
+ {{ statusDisplay.stateLabel }}
+ <!-- Decorative, like the activity headline's divider; the dot's aria-label
+ keeps the shared combined form. -->
+ <span aria-hidden="true" :class="$style.statusSeparator">|</span>
+ {{ statusDisplay.decisionLabel }}
+ </N8nText>
</file context>
| /* Nearly the boxed entries' negative inset (see _activity-card.scss), held back by | ||
| the focus ring's width: flush with the feed's clip edge, the ring's left line | ||
| would be cut off entirely. */ | ||
| margin-inline: calc(-1 * var(--spacing--sm) + var(--border-width)); |
There was a problem hiding this comment.
P2: At narrow review widths, this leaves only a 1px inset while N8nChatInput's focus ring is 2px, so the left focus outline is clipped by the detail scrollport. Use the focus-ring width for the inset.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewCommentComposer.vue, line 90:
<comment>At narrow review widths, this leaves only a 1px inset while `N8nChatInput`'s focus ring is 2px, so the left focus outline is clipped by the detail scrollport. Use the focus-ring width for the inset.</comment>
<file context>
@@ -66,9 +84,10 @@ async function onSubmit() {
+ /* Nearly the boxed entries' negative inset (see _activity-card.scss), held back by
+ the focus ring's width: flush with the feed's clip edge, the ring's left line
+ would be cut off entirely. */
+ margin-inline: calc(-1 * var(--spacing--sm) + var(--border-width));
}
</file context>
| margin-inline: calc(-1 * var(--spacing--sm) + var(--border-width)); | |
| \tmargin-inline: calc(-1 * var(--spacing--sm) + var(--focus--border-width)); |
| const scrollContainer = ref<HTMLElement | null>(null); | ||
| const feed = ref<HTMLElement | null>(null); | ||
| const providedScrollContainer = inject(ReviewDetailScrollContainerKey, null); | ||
| const scrollContainer = computed(() => |
There was a problem hiding this comment.
P2: At the narrow review-detail breakpoint, detailBody changes to a column and places metadata after the activity panel. With this shared root, scrollToBottom() can land at the bottom of a tall metadata rail instead of showing the newest comment or composer. Keep the activity feed bounded at the narrow container breakpoint, or scroll the activity list’s last item rather than the whole detail body.
(Based on your team's feedback about keeping the activity feed as the scroll container at narrow review-detail widths.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/frontend/editor-ui/src/features/workflow-reviews/components/WorkflowReviewActivityFeed.vue, line 19:
<comment>At the narrow `review-detail` breakpoint, `detailBody` changes to a column and places metadata after the activity panel. With this shared root, `scrollToBottom()` can land at the bottom of a tall metadata rail instead of showing the newest comment or composer. Keep the activity feed bounded at the narrow container breakpoint, or scroll the activity list’s last item rather than the whole detail body.
(Based on your team's feedback about keeping the activity feed as the scroll container at narrow review-detail widths.) </comment>
<file context>
@@ -2,26 +2,56 @@
-const scrollContainer = ref<HTMLElement | null>(null);
+const feed = ref<HTMLElement | null>(null);
+const providedScrollContainer = inject(ReviewDetailScrollContainerKey, null);
+const scrollContainer = computed(() =>
+ providedScrollContainer ? providedScrollContainer.value : feed.value,
+);
</file context>
Cap native note resizing to the popover's available viewport height so actions remain reachable. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Polishes the workflow review experience across navigation, activity, metadata, and workflow diffs.
Depends on #36939 for the adaptive
N8nChatInputlayout.What changed and why
Made the reviews sidebar resizable and persisted its width.
Why: Lets users allocate more space to review titles and content.
Added full-width dividers beneath the Open/Closed and Activity/Changes tabs.
Why: Creates clearer separation between navigation, actions, and content.
Refined review list item states using design-system surface tokens.
Why: Removes unnecessary borders while making hover and active states clearer.
Stopped rendering empty review sections.
Why: Avoids showing headings that contain no actionable reviews.
Centered the activity column and limited it to 720px.
Why: Improves readability while accounting for the metadata rail.
Moved the comment composer into the activity timeline and applied the adaptive input.
Why: Keeps comments in context and allows the composer to grow smoothly with its content.
Added a fade, upward movement, and blur transition for newly posted comments.
Why: Makes successful comment submission feel smoother and easier to follow.
Refined activity typography and separators.
Why: De-emphasizes timestamps and system actions while keeping comment content readable.
Made the review detail a single scrollable surface with sticky metadata.
Why: Prevents the activity column from scrolling independently and keeps review information accessible.
Added an opaque, token-based background behind the sticky tab row.
Why: Prevents scrolling content from appearing behind the tabs and divider.
Replaced the status dot separator with the pipe divider used elsewhere in the UI.
Why: Uses the same divider consistently throughout the review experience.
Updated workflow diff containers to use the 8px radius token.
Why: Aligns the diff presentation with the design system.
Improved first-publish workflow diffs with a compact “New workflow” context area.
Why: Communicates that no baseline exists without wasting half the diff on an empty canvas.
Limited decision-note resizing to the popover’s available viewport height.
Why: Keeps Comment, Request changes, and Approve actions visible.
Updated tests and removed unused empty-state translations.
Why: Covers the new behavior and removes copy that can no longer be displayed.
Test plan