Skip to content

Port remaining elsa-studio 3.9 UX PRs into src/studio - #8587

Merged
sfmskywalker merged 67 commits into
mainfrom
cursor/forward-port-39-studio-ux-62d2
Oct 3, 2026
Merged

sfmskywalker merged 67 commits into
mainfrom
cursor/forward-port-39-studio-ux-62d2

Conversation

@sfmskywalker

@sfmskywalker sfmskywalker commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Purpose

Ports the remaining merged elsa-studio release/3.9.0 UX PRs into consolidated src/studio.

This PR is stacked on #8586 and must merge after it. The branch merges the current #8586 head (c67ff78d0) so identity gating, the scoped permission-refresh signal, atomic cache generation, the overtaken-reload cap, access-boundary coalesce, view-only mutations during re-check, the mid-check test wait, BusyState, and the shared Studio test helpers stay in sync.


Scope

  • New feature
  • Bug fix (behavior change)

Description

Problem

Several merged elsa-studio 3.9 PRs never landed in core src/studio: Users still show a tenant-scope column, sign-in forms ignore Enter, 401/403 API failures still surface as raw ApiException text, and test projects each keep their own TestLocalizer.

Solution

Port the remaining 3.9 UX PRs onto the #8586 Studio identity stack.

Source PR status

Source Title Status
elsa-studio#1077 Drop tenant scope labels from Users pages Ported
elsa-studio#1089 Wait for navigation after async MudButton clicks Ported (Roles, Role editor, Connection editor). ElsaIdentitySignOutTests is present via #8586; that wait is already in the stacked identity sign-out tests.
elsa-studio#1090 Enter-to-sign-in Tests only in this PR (Enter in either field, IME composition, missing credentials, Enter while busy, plus LoginChooserTests). Production (IsSubmitKey, Immediate + OnKeyDown, BusyState guard) is already on #8586; this PR does not change login production code.
elsa-studio#1094 Friendly 401/403 mapper Ported (AuthorizationFailureExtensions, Error.razor, mappers, call sites, tests, and RefusedWriteTests: Labels create, Alteration dry-run, Alteration submit). The three ExtAuth files now use the shared ApiExceptions.Create helper.
elsa-studio#1096 Flaky workflow permission menu popover tests Ported (PopoverTestExtensions.OpenMenu) now that #8586 brought in WorkflowActionPermissionTests / WorkflowListPermissionTests / ToolbarMenu_OffersUnpublish.
elsa-studio#1097 Language/Environment/User menus open below their buttons Ported (TransformOrigin TopRight, MenuPopoverAssert, Environments + Localization tests). UserMenu_OpensBelowItsButton (studio #1091 via #1097) is ported here; production UserMenu.razor TransformOrigin is already on main. ExtAuth tests now link MenuPopoverAssert.
elsa-studio#1098 Shared TestLocalizer Ported (src/studio/testing/TestLocalizer.cs). Workflows.Tests uses the shared helper; local TestTimeFormatter stays in TestDoubles.

Design notes


Verification

HEAD: cf32c527f (on #8586 c67ff78d0)

Check Result
Elsa.Studio.Security.Tests (AccessBoundary filter, from B) 12 passed
Mid-check tests × 5 2 passed each run

Checklist

  • The PR is focused on a single concern
  • Commit messages follow the recommended convention
  • Tests added or updated (if applicable)
  • No unrelated cleanup included
  • Affected Studio tests pass
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added permission-based access controls across Studio navigation, pages, dashboard widgets, and protected actions.
    • Added clearer access-denied guidance and safer, more actionable handling of authorization errors.
    • Added sign-in and sign-out options to authentication menus, with progress feedback during authentication.
    • Added a dashboard welcome view that highlights pages available to the signed-in user.
  • Bug Fixes
    • Improved login form submission and prevented duplicate sign-in requests while authentication is underway.
    • Made login return paths safer and protected server-side sign-out requests.
    • Updated dashboard and navigation content as authentication and available permissions change.

@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

Comment thread src/studio/modules/Elsa.Studio.Dashboard/Pages/Index.razor.cs
cursoragent and others added 2 commits October 3, 2026 16:50
Bring in #8572 so Studio logout and dashboard permissions resolve
against the merged backend.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Environments and JWT refresh raise a no-dep IPermissionRefreshSignal
instead of taking IPermissionSnapshotCache, so Security plus
Environments can resolve. Invalidate bumps a generation token, drops
the snapshot, then raises Changed so PermissionView and NavMenu
re-fetch after sign-in, environment switch, and silent refresh.
Sign-out stays on the page when token clear fails.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

@sfmskywalker sfmskywalker left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Elsa 3 Code Review: REQUEST_CHANGES + HIGH @ fc5f9a5

Code Review, Round 1/4

Scope. I reviewed only this PR's own commits on top of #8586 @ 7b1c75ca: d0cf63c4e, beb0202eb, the two merges of B (875800bb9, a21693540) and fc5f9a55f. That is 109 files, +3,270/−2,415, and 4,125 of those lines are Elsa.sln. I compared each source PR's merged diff on elsa-studio release/3.9.0 line by line against this delta, then checked every touched file against the current release/3.9.0 tip.

This PR contains #8586 @ 7b1c75ca unchanged, so it inherits #8586's open R2 blockers. It needs a main merge or rebase once B's fix lands. B's issues are not repeated here.

Verdict

The port itself is good. The production changes from #1077, #1094, #1096, #1097 and #1098 are close to verbatim. The 401/403 guidance fits main's fail-closed permission model without touching it. The build is clean and every affected suite passes. Four of five revert checks fail as they should.

Three things block it:

  1. A required CI check fails.
  2. One source test file from #1094 was silently dropped, so three call sites have no coverage.
  3. The #1097 user-menu test hunk is missing, although the PR body says it is covered.

Blockers

1. Solution grouping and filters are up to date fails.
Running python3 scripts/solution/solution_groups.py --check on this head reports Out of date: Elsa.sln, Elsa.Studio.slnf. B @ 7b1c75ca passes the same check.

  • The new Elsa.Studio.Localization.Tests sits under two stray solution folders, studio → modules, nested under src. It should be in the generated Studio grouping.
  • Elsa.Studio.slnf doesn't list it.

Fix: run python3 scripts/solution/solution_groups.py and commit the result. I tried this on a scratch copy and --check passed afterwards. Separately, most of the 4,125-line Elsa.sln diff is dotnet sln add reordering the whole ProjectConfigurationPlatforms block. I reproduced that from B's Elsa.sln, so it is a tooling side effect, not a content change. It's optional, but adding the one project entry by hand would keep this file's diff to about 20 lines and make later solution diffs reviewable.

2. #1094 RefusedWriteTests.cs was not ported, and the PR body doesn't say so.
The source adds three tests:

  • Labels: create refused → snackbar shows ForbiddenMessage.
  • Alteration dry-run refused → Dry-run failed: <guidance>.
  • Alteration submit refused → guidance in the dialog, with no HTTP 403.

Without them, the production call-site changes in Labels.razor.cs, AlterationDesignerHost.razor.cs and AlterationSubmitDialog.razor have no tests. I confirmed this with a revert check: I put Labels.razor.cs back to B's version and the Administration suite still passed (55/55).

Porting needs only small adaptations:

  • labels:view and labels:create are valid core names (Elsa.Labels.Permissions.LabelPermissions), and StubPermissionService.Grants parses them through B's strict Permission.TryParse.
  • Add the Elsa.Studio.Alterations project reference to Elsa.Studio.Administration.Tests.csproj.
  • Align NoVariablesInstanceService with core's IWorkflowInstanceService.

If something can't be ported, list it in the source table as adapted or deferred, with the reason.

3. #1097's AppBarUserMenuTests hunk is missing.
The source changes UserMenu_OpensBelowItsButton to use MenuPopoverAssert and links MenuPopoverAssert.cs into Elsa.Studio.ExternalAuthentication.Tests. On this head, AppBarUserMenuTests.cs exists but has no UserMenu_OpensBelowItsButton test at all. That test came from studio #1091, whose production fix, UserMenu.razor TransformOrigin="Origin.TopRight", is already on main. The test never was.

So the user-menu third of the "opens below its button" fix has no regression test, while the language and environment pickers do. The PR body's "AppBarUserMenuTests is now present via #8586" is true of the file but not of this test. Fix: add the test in its #1097 form and add the MenuPopoverAssert.cs <Compile Include … Link> to the ExternalAuthentication test project.

Fidelity, per source PR

Source Ported Adapted (why) Missing
#1077 Users tenant-scope label All 5 files, line-for-line None None
#1089 wait for navigation after async clicks ConnectionEditorTests, RoleEditorSurfaceTests, RolesPageTests hunks in this PR The ElsaIdentitySignOutTests hunk arrives with #8586. The file is identical to release/3.9.0. None. Covered.
#1090 Enter-to-sign-in Login tests: Enter in either field, IME composition, missing credentials, Enter while busy, plus LoginChooserTests IsSubmitKey, Immediate/OnKeyDown and the BusyState guard are already in #8586's diff, so this PR contributes the tests plus an @using Elsa.Studio.Components that looks redundant, since B's tree compiles without it. Adds AngleSharp package refs because main's test projects don't get it transitively. None. The body's table should say the production half comes from #8586.
#1094 friendly 401/403 AuthorizationFailureExtensions, Error.razor/.cs, IdentityApiErrorMapper, UserTaskErrorMapper, the Workflows, Secrets, ExtAuth, Labels, Alterations, AI, OpenTelemetry and PlatformIntegration call sites, and the Core, Admin, ExtAuth, Security, UserTasks and Workflows tests MainLayoutErrorBoundaryTests stub implements IsInitialized because main's IFeatureService has that member. The extra using Elsa.Studio.Localization in Labels.razor.cs is needed because the core file doesn't import it already. RefusedWriteTests.cs (blocker 2). The shared-helper switch in ConnectionEditorTests, ExternalIdentityLinksTests and RemoteFeatureProviderTests was skipped; each still has a private CreateApiException* although ApiExceptions.cs is already linked into that project (nit).
#1096 MudMenu popover waits PopoverTestExtensions is byte-identical, and the edits to WorkflowActionPermissionTests, WorkflowListPermissionTests and WorkflowEditorLifecycleTests match None None. 5 repeat runs of those 3 classes were 27/27 each time.
#1097 pickers open below their buttons LanguagePicker/EnvironmentPicker TransformOrigin, MenuPopoverAssert, EnvironmentPickerTests, the new Elsa.Studio.Localization.Tests Elsa.Studio.sln became Elsa.sln because core's solution is canonical, but it isn't regenerated (blocker 1) The AppBarUserMenuTests hunk and the ExtAuth MenuPopoverAssert link (blocker 3)
#1098 shared TestLocalizer src/studio/testing/TestLocalizer.cs is identical to the source, the csproj links are in place, and the local copies are removed (no class TestLocalizer is left outside testing/) ActivityExecutionDetailsTests, ActivityExecutionsTabTests and ExecutionDetailsDrawerRenderTests don't exist on main. Dashboard.Tests already uses the shared helper. None

Fit with #8586's permission model

  • This PR adds no production permission names. Every grant in its tests (secrets:view, workflows/*:view, alterations:execute) goes through B's strict Permission.TryParse, which rejects the legacy names, so they are valid {resource}:{verb} names.
  • It changes only how a backend 401/403 is presented. IdentityPermissionContext still turns 401/403 or an unavailable snapshot into FromGrants([]) and never throws into Error.razor, so fail-closed resolution is untouched.
  • The Error.razor "Sign in" hand-off appears only for a 401 and only when the user clicks it, as in the source, so a rejected token can't cause a redirect loop.

Verification on fc5f9a5

  • dotnet build Elsa.Studio.slnf -c Release gave 0 errors. Elsa.Studio.Localization.Tests isn't in the filter (blocker 1), so I built it separately: 0 errors.

  • All tests passed (Release, --no-build):

    Suite Passed
    Core 149
    Security 119
    Administration 62
    Authentication.UI 33
    Environments 15
    Localization 1
    StructuredLogs 23
    UserTasks 31
    Dashboard 107
    Workflows 484
    ExternalAuthentication 465
  • Revert checks:

    • Error.razor back to B → 5 Errors tests fail ✔
    • IdentityApiErrorMapper back to B → 1 test fails ✔
    • LanguagePicker TransformOrigin back to BottomRight → LanguagePickerTests fails ✔
    • Enter handler removed from ElsaIdentityLoginMethod → 2 Enter tests fail ✔
    • Labels.razor.cs back to B → nothing fails ✘ (blocker 2)

CI, Greptile and threads

  • CI: everything is green except Solution grouping and filters are up to date, which fails (blocker 1). run-affected-tests is skipped (dispatch-only).
  • Greptile: 0/5 on fc5f9a55. All 4 findings are in files only #8586 changes: PermissionTrackingComponentBase, DashboardPermissions, ElsaIdentitySignOutService and the Dashboard Index.razor.cs runtime chip. None is in this PR's own delta. The /identity/logout finding should clear now that #8572 has merged.
  • Threads: 7 unresolved, all on #8586-owned files. This PR's own threads (ForbiddingBackend ×3, ApiExceptions ×2) are resolved.
  • Effect on the gate: because this PR's diff against main includes all of #8586, its Greptile score and threads can only clear once #8586 is fixed and merged and this branch is updated on main.

Maintainability (non-blocking)

  • Elsa.sln churn: see blocker 1. A hand-inserted entry keeps the history readable.
  • Use the shared ApiExceptions.Create in the three ExtAuth test files, as the source does, rather than keeping three private copies next to the linked helper.
  • PR body accuracy:
    • The #1090 row should say the production code comes from #8586.
    • The #1094 row should list RefusedWriteTests until it is ported.
    • The #1097 row shouldn't claim the user-menu test.
  • Small tidy-ups:
    • The TestLocalizer removal in WorkflowEditorLifecycleTests.cs leaves a double blank line.
    • using Elsa.Studio.Testing; was appended out of order in several test files (the source kept them sorted).
    • The @using Elsa.Studio.Components added to both login methods looks redundant.

Required for Round 2

  1. Regenerate the solution grouping so the CI check passes.
  2. Port RefusedWriteTests (or document a reasoned adaptation).
  3. Port the #1097 AppBarUserMenuTests hunk with the MenuPopoverAssert link.
  4. Update the source table.
  5. After #8586's R2 fixes land, merge B's new head (or main, once B has merged) and re-request Greptile.

Merge gate

This PR merges only on APPROVE + HIGH on its tip, with green CI and Greptile 5/5 on that same tip. Order is A, then B, then C: #8572 (A) is merged (8d88d04f), #8586 (B) must merge next, and this PR (C) only after B. Today the gate is not met: REQUEST_CHANGES, one failing required check, Greptile 0/5 from the inherited #8586 findings, and #8586 still in REQUEST_CHANGES.

cursoragent and others added 2 commits October 3, 2026 17:05
Adding the Environments project reference made Microsoft.Extensions
and Elsa.Studio.Security extension types collide.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Bring #8586 (1572857) onto this branch so C stays stacked on B
after the permission-cache cycle break and notify/re-render work.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

Regenerate Studio solution grouping so Localization.Tests is in
Elsa.Studio.slnf. Port #1094's refused-write tests and #1097's
UserMenu_OpensBelowItsButton hunk, switch the ExtAuth suites onto the
shared ApiExceptions helper, and tidy import order.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

Comment thread src/studio/modules/Elsa.Studio.Security/Services/IdentityPermissionContext.cs Outdated
Comment thread src/studio/framework/Elsa.Studio.Core/Extensions/ServiceCollectionExtensions.cs Outdated
cursoragent and others added 2 commits October 3, 2026 17:58
Register IPermissionRefreshSignal per circuit, publish (generation,
snapshot) atomically, reload overtaken GetAsync callers, and re-resolve
Users/Roles boundaries plus NavMenu on Changed.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Bring scoped IPermissionRefreshSignal, atomic cache generation, and the R3 proving tests onto the stacked UX branch.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
cursoragent and others added 3 commits October 3, 2026 18:46
Raise IPermissionRefreshSignal only when a JWT refresh fails or the
sub/permissions claims change, and cap overtaken GetAsync reloads so a
near-expiry token cannot livelock. Keep Users and Roles content mounted
while re-checking access, and honour a Changed that arrives mid-check.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Bring the permission-refresh livelock cap and access-boundary coalesce onto the stacked UX branch.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Keep C's stacked UX nits to import order and a double blank line. Do not
reorder Elsa.sln.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

cursoragent and others added 2 commits October 3, 2026 18:57
Keep Users and Roles editors mounted so drafts survive a permission
refresh, but pass view-only create/update/delete flags until the new
access result arrives.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Bring in the single B push that disables Users/Roles mutation controls
while a permission re-check is in flight and keeps editor drafts mounted.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

cursoragent and others added 2 commits October 3, 2026 19:22
CI failed when create:False appeared before the coalesced third GetAsync.
WaitForAssertion now requires Calls == 3 and an empty Results queue.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Bring the boundary-test WaitForAssertion race fix from #8586 so C stays stacked on B.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

@sfmskywalker sfmskywalker left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Elsa 3 Code Review: APPROVE + HIGH @ cf32c52

Code Review, Round 3/4

Scope. This is a confirmation round. I checked this PR's own delta on top of #8586 @ c67ff78d, the three #8586 merges since R2, and the R2 nits. #8586's own code is reviewed on #8586, and the findings on it are not repeated as blockers here. This approval covers this PR's own delta only.

Merge confirmation @ cf32c52

  • cf32c527 is a two-parent merge of bbbd2aef and #8586 @ c67ff78d. c67ff78d is one test-only commit on top of c937a27a: "wait for queued access refresh in boundary tests", touching SecurityMenuAndBoundaryTests.cs only.
  • git show --remerge-diff is empty, so the merge had no conflicts and no hand edits.
  • git diff bbbd2aef cf32c527 is byte-identical to git diff c937a27a c67ff78d. The merge brings in exactly c67ff78d.
  • This PR's own delta is unchanged: git diff c67ff78d cf32c527 is byte-identical to git diff c937a27a bbbd2aef.
  • #8586 @ c67ff78d is an ancestor and contains main (8d88d04f).

Earlier merges since R2 (confirmed)

  • 930e88f0 merges #8586 @ a244d07e into 36eace01. bbbd2aef merges #8586 @ c937a27a into 8eb64348.
  • Both have an empty --remerge-diff, so neither needed hand edits.
  • Each brings in exactly #8586's delta:
    • 36eace01..930e88f0 equals e07d2e3a..a244d07e.
    • 8eb64348..bbbd2aef equals a244d07e..c937a27a.
  • Outside the 3 files touched by the style commit 8eb64348, this PR's own delta is byte-identical to R2.

Nits

  • ExtAuth using order: done (8eb64348). ConnectionEditorTests, LoginChooserTests and RemoteFeatureProviderTests are sorted, with the aliases at the end. ✔
  • Double blank line: the one in ConnectionEditorTests is removed. The one flagged in R2, WorkflowEditorLifecycleTests.cs:469, is still there. Cosmetic.
  • #1090 row: improved. It still lists LoginChooserTests among this PR's tests, but those Enter tests arrive with #8586; this PR only changes that file's imports and drops the local TestLocalizer. Cosmetic.
  • Workflows and Administration test files: about 30 of these from the #1098 port still list using Elsa.Studio.Testing; last. Cosmetic.
  • Elsa.sln reorder: skipped, as agreed. Content is correct and the grouping check passes.

None of these block. They can be folded into the next #8586 re-merge or left as they are.

Fidelity

Unchanged from R2. #1077, #1089, #1090, #1094 (including RefusedWriteTests), #1096, #1097 (including UserMenu_OpensBelowItsButton) and #1098 are all ported. The R2 revert checks still apply because the code under test is byte-identical.

Verification

  • On 8eb64348 (Release): all suites passed.

    Suite Passed
    ExternalAuthentication 479
    Administration 65
    Security 147
    Core 150
    Environments 21
    Localization 1
    Authentication.UI 33
    Workflows 484
    StructuredLogs 23
    UserTasks 31
    Dashboard 107
  • On bbbd2aef: Administration (65), Environments (21) and ExternalAuthentication (479) passed.

  • Security on bbbd2aef: failed intermittently, 2 of 51 runs. Render_WhenChangedArrivesDuringACheck asserted before the queued access refresh had finished.

  • Security on cf32c527: stable. A Release build and first run gave 147/147. After that, every run passed:

    Run type Failures
    Full suite, consecutive 0 of 30
    Full suite, alongside parallel Workflows and ExternalAuthentication runs 0 of 15
    Targeted Render_WhenChangedArrivesDuringACheck, both the role and user variants 0 of 40

    c67ff78d moves the call-count and "Checking access" assertions inside WaitForAssertion and adds Assert.Empty(service.Results) and the draft-preserved check. That is the right fix for the race.

CI, Greptile and threads @ cf32c52 (checked at 21:44 CEST)

  • CI: green.
    • ubuntu-latest passed (19m28s).
    • Solution grouping and filters are up to date passed.
    • CodeQL passed.
    • Both Analyze (csharp) jobs passed.
    • import-rehearsal-tests and pinned-source-contract passed.
    • run-affected-tests is skipped (dispatch-only).
  • Greptile: 3/5 on cf32c527, reviewed at 21:30 CEST. It reports no new issue for this head. It holds the score only because of the earlier permission-visibility finding in #8586's User/RoleAdministrationAccessBoundary.razor. Neither file is in this PR's delta.
  • Threads: 20 in total, all 20 resolved.

This PR inherits #8586's open findings. It must merge #8586's final head (or main, once #8586 has merged) again before it can merge.

Merge gate

This PR merges only on APPROVE + HIGH on its tip, with green CI and Greptile 5/5 on that same tip. #8572 (A) has merged (8d88d04f). #8586 (B) is open at c67ff78d and must merge next. This PR (C) merges only after it.

State at cf32c527:

  • This review: APPROVE + HIGH ✔
  • CI: green ✔
  • Threads: 0 unresolved ✔
  • Greptile: 3/5 ✘, from #8586-owned code.
  • #8586: not merged ✘

Gate not met. If #8586 moves again, this PR needs another merge confirmation: a clean merge of exactly #8586's new head, this PR's own delta byte-identical, CI green and Greptile 5/5 on the new tip.

@sfmskywalker
sfmskywalker merged commit b104fd2 into main Oct 3, 2026
22 checks passed
@sfmskywalker
sfmskywalker deleted the cursor/forward-port-39-studio-ux-62d2 branch October 3, 2026 20:11
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