Draw the macOS Azure tab as the scope tree it already describes - #198
Merged
Merged
Conversation
The Azure tab listed its eligibilities flat, which does not scale: a platform engineer eligible for Contributor on sixty subscriptions read sixty sibling rows and clicked sixty times before the single activation Elevate promised. Windows and the CLI got the tree in #186; the logic and its tests came across to ElevateCore in #195. This is the SwiftUI layer that draws it. AppModel gains the Azure tab's tree and the state around it, mirroring AppModel.Panel.cs: azureTree(for:) builds from the already-filtered rows, so a search narrows the tree rather than sitting beside it; collapsedScopes is keyed per tenant, because two accounts can reach the same scope; and isScopeCollapsed answers false while filtering, so a match is never hidden behind a node closed earlier. The subtree checkbox carries its own canSelect, stricter than canActivate: the rule for "is this row's checkbox enabled" lived in the view, and without it a scope checkbox would select rows whose own checkbox is off. A partly chosen subtree fills up rather than emptying — the checkbox is offering the rest. TenantRoles walks ScopeTree.flatten on the Azure tab and emits a ScopeRow or a RoleRow per entry. The two foldings the Core already performs are what keep the tree from costing more rows than it saves, so the view only has to respect them: a scope that just passes through is drawn as a dimmed "Alpha /" ahead of the title it was folded into, and a scope leading to a single role gets no header at all. Indent is 12 pt a step and stops after three; the panel is 380 pt wide and a management group path is longer than that, and the header's own path says where you are. Management groups sit beside the subscriptions, not above them, for the reason ArmScope documents. Verified on a Mac: 444 ElevateCore tests and 158 ElevateApp tests pass, and the app builds with no new warnings. The twelve new AppModelScopeTreeTests mirror the C# suite case for case and were watched failing first. The panel was also rendered offscreen in light and dark, with and without select mode, to check the folding, the elision and the three-state checkbox against real scopes. Closes #194 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #194. The SwiftUI half of #186; the logic and its tests landed in #195.
The Azure tab listed its eligibilities flat, which does not scale: a platform engineer eligible for Contributor on sixty subscriptions read sixty sibling rows and clicked sixty times before the single activation Elevate promised.
The model
AppModelgains the tree and the state around it, mirroringAppModel.Panel.cs:azureTree(for:)builds from the already-filtered rows, so a search narrows the tree instead of sitting beside it.collapsedScopesis keyed per tenant — two accounts can reach the same scope — in memory, and scopes start open.isScopeCollapsedanswersfalsewhile filtering, so a match is never hidden behind a node closed earlier.subtreeKeys/subtreeState/toggleSubtree, where a partly chosen subtree fills up rather than emptying: the checkbox is offering the rest.canSelectis ported deliberately and is stricter thancanActivate. The "is this row's checkbox enabled" rule lived in the view; without it a scope checkbox would select rows whose own checkbox is off.The view
TenantRoleswalksScopeTree.flattenon the Azure tab and emits aScopeRowor aRoleRowper entry. The two foldings the Core already performs are what keep the tree from costing more rows than it saves, so the view only has to respect them: a pass-through scope is drawn as a dimmedAlpha /ahead of the title it was folded into, and a scope leading to a single role gets no header at all.Indent is 12 pt a step and stops after three, as on Windows — the panel is 380 pt wide, a management group path is longer than that, and the header's own path says where you are.
Management groups sit beside the subscriptions rather than above them, for the reason
ArmScopedocuments: ARM writes a management group scope as its own flat path and never repeats it in a subscription's.Verification
Built and tested on a Mac (Swift 6.4 / Xcode 26):
AppModelScopeTreeTestsmirror the C#AppModelScopeTreeTestscase for case, and were watched failing before the accessors existed.Worth a reviewer's judgment
A folded ancestor with no caption to borrow shows its raw subscription id, so a pass-through header can read
33333333-3333-… / sandbox-rg. That is what the data says — no eligibility sits on that subscription, so nothing named it — and it matches Windows. Truncating it differently would be a Core change and a divergence.Separately, and not from this branch: a full local
ElevateApprun can block indefinitely on an unanswered login-keychain prompt, which looks exactly like a hang in whatever is under test — the blamed test differs run to run and each passes alone.TestModel.swift:40documents the path:ownAppViaLoopbackis false by default, so a test whose settings pick up a client id reconciles own-app accounts against the real login keychain. Keeping the suite off the real keychain would be worth doing on its own.Co-authored-by: Claude Opus 5 noreply@anthropic.com