SharePoint permissions map (file-plan style, grouped by team) + an admin screen that creates document libraries correctly and registers them automatically, so nothing gets forgotten again.
Built 13 Aug 2026 10:23 BST
Every purple line like this one, directly above a copy box, shows the page version and the exact local time that specific box's content was last changed. If a copy still errors after re-pasting, check this stamp against what's shown before assuming the fix didn't take — refresh the page fully first.
LibraryFilePlan and PermissionIndex via the CSV templates (fastest — creates every column for you), or manually per the schema tables. Column is PrettyName, not DisplayName — SharePoint silently drops writes to that name on this list.Create_FilePlan_Row's Body so it writes PrettyName instead of DisplayName. One line, no re-import.&$top=5000 to Get_Unique_Perm_Libraries's Uri so it stops capping out around 20-30 libraries. One line, no re-import.LibraryFilePlanLocation: your main site — https://modgovuk.sharepoint.com/teams/1598/.
LibraryFilePlan_list_template.csv — download this, then Site Contents → New → List → From CSV (or Excel) → upload it. SharePoint reads the embedded schema and creates every column with the correct type and Team's choices already filled in — no manual column-by-column setup. Delete the single placeholder row afterward, and rename the list to exactly LibraryFilePlan if it doesn't pick that up from the filename.
| Column name (exact, no spaces) | Type | Notes |
|---|---|---|
| Title | Single line of text | Built-in — the flow writes the library's pretty display name into it automatically |
| LibraryUrl | Single line of text | The real join key — a library's server-relative URL, survives display-name renames |
| Team | Choice | Top-level file-plan grouping. Fixed set — type in your known team names as the choices, no "Allow fill-in values" |
| SubTeam | Choice | Optional nested grouping. Tick "Allow fill-in values" — same pattern as the CAT column, so new sub-teams can be added on the fly |
| PrettyName | Single line of text | Optional override; falls back to live library Title if blank. Not "DisplayName" — SharePoint silently drops writes to a column with that exact name on this list (collides with a reserved sub-property on the two Person-or-Group columns, Created By/Modified By) |
| SiteUrl | Single line of text | Which of your sites the library is on |
| SortOrder | Number | Optional — controls order within a group |
| IsLimited | Yes/No | True if the library was created as Limited — lets you set conditional formatting on the list view to colour-code Limited rows |
_x0020_ internally, and the flows' requests need the plain names to match exactly.PermissionIndexLocation: your main site, same as LibraryFilePlan.
PermissionIndex_list_template.csv — same idea: Site Contents → New → List → From CSV → upload, delete the placeholder row, rename to exactly PermissionIndex if needed.
| Column name (exact, no spaces) | Type | Notes |
|---|---|---|
| Title | Single line of text | Built-in — set to the same value as GroupName, so the list is readable at a glance |
| LibraryUrl | Single line of text | Which library this permission row belongs to — joins back to LibraryFilePlan.LibraryUrl |
| LibraryTitle | Single line of text | Display name, so search results don't need a lookup back to LibraryFilePlan |
| GroupName | Single line of text | The actual SG_/DG_ group name (or username) this row represents — what the group-search screen filters on |
| PrincipalType | Single line of text | "SharePoint Group", "Security Group", or "User" |
| PermissionLevel | Single line of text | e.g. "Full Control", "Contribute", "Read" |
| NestedUnderGroup | Single line of text | Blank for a group/user granted directly on the library. If this row is a DG_ AD group found nested inside an SG_ group, this holds that SG_ group's name |
LibraryFilePlan.In Power Automate, open Create Library (App) → Edit → click Create_FilePlan_Row to expand it → click into its Body box → select all, delete → paste this in as the replacement → Save. Existing action, existing field, no re-import needed.
v2.6 — Updated 12 Aug 2026 08:30 BST
{
"__metadata": { "type": "SP.Data.LibraryFilePlanListItem" },
"Title": "@{triggerBody()?['DisplayName']}",
"LibraryUrl": "@{body('Get_New_Library_Url')?['d']?['RootFolder']?['ServerRelativeUrl']}",
"Team": "@{triggerBody()?['Team']}",
"SubTeam": "@{triggerBody()?['SubTeam']}",
"PrettyName": "@{triggerBody()?['DisplayName']}",
"SiteUrl": "@{triggerBody()?['SiteUrl']}",
"IsLimited": @{if(equals(triggerBody()?['IsLimited'], true), 'true', 'false')}
}
DisplayName → PrettyName). The value is still populated from triggerBody()?['DisplayName'] — that's the Power Apps trigger's own parameter name, unrelated to the SharePoint column, and it hasn't changed.Cause: this action asks SharePoint for every library with unique permissions, but never told it how many to return. SharePoint's _api/web/lists endpoint defaults to a page size of around 100 items per response and doesn't automatically continue to the next page — with enough lists on the site (including hidden/system ones counted before the filter narrows them down), the real Limited libraries near the end of that list never got reached, so the index silently stopped at whatever fit in the first page.
In Power Automate, open Rebuild Permission Index → Edit → click Get_Unique_Perm_Libraries to expand it → click into its Uri box → add &$top=5000 to the end → Save. Existing action, one field, no re-import needed.
v4.2 — Updated 13 Aug 2026 09:26 BST
_api/web/lists?$filter=BaseTemplate eq 101 and Hidden eq false and HasUniqueRoleAssignments eq true&$select=Title,Id,RootFolder/ServerRelativeUrl&$expand=RootFolder&$top=5000
CreateLibraryApp_flow_import.zip — creates a library, registers it into LibraryFilePlan. The core flow everything else builds on.
BackfillFilePlan_flow_import.zip — one-time scan of every existing library on the site, registers anything not already tracked.
RebuildPermissionIndex_flow_import.zip — scans libraries with broken permissions and builds a fast, searchable index of their groups.
ManageLibraryPermissions_flow_import.zip — lists, adds, or removes a group directly on a chosen library, called live from the Admin screen.
DeleteLibrariesApp_flow_import.zip — checks each library for files, deletes the ones that are safe to delete (or all of them if overridden), and cleans up their LibraryFilePlan/PermissionIndex rows. Called live from the Manage Libraries screen.
All five: Power Automate → My flows → Import → Import Package (Legacy).
Triggered from Power Apps (or manually, for testing). Per library:
BaseTemplate 101) with the correct sensitivity label for Normal vs LimitedLibraryFilePlanSite URL defaults to the main site if left blank — every action falls back to it via coalesce().
Power Automate → My flows → Create Library (App) → Test → Manually.
| Field | First test — plain | Second test — Limited |
|---|---|---|
| Site URL | leave blank | leave blank |
| Technical Name | ZZTest_Library | ZZTest_LTD_Library |
| Display Name | ZZ Test Library | ZZ Test LTD Library |
| Is Limited | false / off | true / on |
| Team | Test | Test |
| SubTeam, Categories | leave blank | leave blank |
First test: check the library exists with CAT + Status columns and WiP/Records views, its Title shows the pretty name, and a new row appeared in LibraryFilePlan with PrettyName populated.
SG_Cadets-Admin-Ltd_MEM and SG_IHUB are there — no inherited groups, no extra groups, no direct user entry for whoever ran the flow.LibraryFilePlan once both tests pass.LibraryFilePlan only tracks libraries created after this system existed. This flow seeds it from what already exists: scans every real document library (skipping SharePoint's own system libraries), and for anything not already tracked, adds a row with LibraryUrl, PrettyName, Title, SiteUrl filled in automatically, and IsLimited guessed from whether the library already has broken permission inheritance. Team, SubTeam are left blank — fill those in by hand afterward via SharePoint's grid/quick-edit view. Safe to run more than once — only adds what's missing.
Power Apps can't read SharePoint permissions directly, and a search screen can't afford to live-query every library's permissions on every keystroke. This flow scans permissions once and writes a flat, fast-searchable copy into PermissionIndex; the group-search screen just filters that list.
Only scans libraries with broken/unique permissions (mostly your Limited libraries) — Normal libraries all inherit the exact same site-wide permissions, so indexing each individually would be pure noise. If a group is a native SharePoint Group, it also looks one level inside that group's own membership — this is how a nested DG_... AD group shows up in the index too, without needing Microsoft Graph.
PermissionIndex and rebuilds it from scratch every run — intentional, avoids stale/duplicate rows.This is the write counterpart to Rebuild Permission Index / Group Search, which are both read-only snapshots. This flow is called live, on demand, from the Admin screen — one flow, one trigger, an Action field ("List" / "Add" / "Remove") decides what it does:
SG_...) a permission level on that library, reusing the exact same Get_Group_Id → Get_Role_Def_Id → Add_Role_Assignment pattern already proven in Create Library (App)roleassignments/getbyprincipalid(id) with an HTTP DELETE), rather than needing to know which specific permission level to unwindSG_...) directly on a library — matches how every Limited library in this system is actually set up: a DG_... AD group is never granted directly, it's always nested inside an SG_... wrapper group. If a genuinely new AD group needs adding, the other team creates it and its SG_ wrapper first, same as always — this screen doesn't change that workflow, just removes the "someone has to do it by hand in native SharePoint" step.Called from the Manage Libraries screen with a JSON array of library URLs, an Action (currently always "Delete" from the app — "Check" is accepted too but every branch other than Delete just reports counts and changes nothing, so it's there if you ever want a preview-only call), and a Force flag.
Per library: gets its live ItemCount via _api/web/GetList(url) (server-relative URL, not title — the one join key in this whole system guaranteed not to drift if someone renames a library directly in SharePoint). If Force is false and ItemCount > 0, that library is skipped and reported back, untouched. Otherwise it deletes the document library itself, its LibraryFilePlan row, and every PermissionIndex row that pointed at it.
Create a new blank canvas app in Power Apps Studio.
Data panel → Add data → add these before pasting any screen YAML:
v4.3 — Updated 13 Aug 2026 10:23 BST
'LibraryFilePlan' (SharePoint → your main site → LibraryFilePlan) 'PermissionIndex' (SharePoint → your main site → PermissionIndex) 'Create Library (App)' (search Power Automate flows in Add data) 'Rebuild Permission Index' (search Power Automate flows in Add data) 'Manage Library Permissions' (search Power Automate flows in Add data) 'Delete Libraries (App)' (search Power Automate flows in Add data)
Select the App object (top of the tree, not any screen) → OnStart property → paste this in full:
v3.8 — Updated 13 Aug 2026 10:23 BST
Set(varTenantHost, "https://modgovuk.sharepoint.com");
ClearCollect(colFilePlan, 'LibraryFilePlan');
ClearCollect(
colFilePlanFiltered,
AddColumns(colFilePlan, TeamName, Team.Value, SubTeamName, Coalesce(SubTeam.Value, ""), EffectiveName, If(IsBlank(PrettyName) || PrettyName = "", Title, PrettyName))
);
ClearCollect(
colTeamGroups,
Sort(
AddColumns(
GroupBy(colFilePlanFiltered, TeamName, GroupedItems),
ItemCount, CountRows(GroupedItems)
),
TeamName,
SortOrder.Ascending
)
);
Clear(colFlatSource);
Collect(colFlatSource, AddColumns(colTeamGroups, RowType, "Team", SortKey, TeamName, RankInGroup, 0));
Collect(colFlatSource, AddColumns(colFilePlanFiltered, RowType, "Library", SortKey, TeamName, RankInGroup, 1));
ClearCollect(colExpandedKeys, Filter(Table({TeamName: "seed"}), false));
Set(varIsAdmin, Lower(User().Email) in ["allan.hamilton775@mod.gov.uk", "dan.smith391@mod.gov.uk"]);
ClearCollect(colQueue, Filter(Table({TechnicalName: "seed", DisplayName: "", Team: "", SubTeam: "", IsLimited: false, Categories: ""}), false));
Set(varQueueRunning, false);
Set(varQueueTotal, 0);
Set(varQueueDone, 0);
Set(varIndexRunError, false);
ClearCollect(colPermissionIndex, 'PermissionIndex');
ClearCollect(colPermissionFiltered, colPermissionIndex);
ClearCollect(colLibraryGroups, Sort(AddColumns(GroupBy(colPermissionFiltered, LibraryUrl, LibraryTitle, GroupedItems), ItemCount, CountRows(GroupedItems)), LibraryTitle, SortOrder.Ascending));
Clear(colPermFlatSource);
Collect(colPermFlatSource, AddColumns(colLibraryGroups, RowType, "Library", SortKey, LibraryUrl, RankInGroup, 0, GroupSortKey, "", DirectGroupCount, With({_lu: LibraryUrl}, CountRows(Filter(colPermissionFiltered, LibraryUrl = _lu && (IsBlank(NestedUnderGroup) || NestedUnderGroup = ""))))));
Collect(colPermFlatSource, AddColumns(Filter(colPermissionFiltered, IsBlank(NestedUnderGroup) || NestedUnderGroup = ""), RowType, "Group", SortKey, LibraryUrl, RankInGroup, 1, GroupSortKey, GroupName, MemberCount, With({_gn: GroupName, _lu: LibraryUrl}, CountRows(Filter(colPermissionFiltered, NestedUnderGroup = _gn && LibraryUrl = _lu)))));
Collect(colPermFlatSource, AddColumns(Filter(colPermissionFiltered, !IsBlank(NestedUnderGroup) && NestedUnderGroup <> ""), RowType, "Member", SortKey, LibraryUrl, RankInGroup, 2, GroupSortKey, NestedUnderGroup));
ClearCollect(colExpandedLibs, Filter(Table({LibraryUrl: "seed"}), false));
ClearCollect(colExpandedGroups, Filter(Table({GroupKey: "seed"}), false));
ClearCollect(colSelectedLibraries, Filter(Table({LibraryUrl: "seed"}), false));
ClearCollect(colManageFiltered, colFilePlanFiltered);
ClearCollect(colEditQueue, Filter(colFilePlanFiltered, false));
ClearCollect(colDeleteSkipped, Filter(Table({LibraryUrl: "seed", Title: "seed", ItemCount: 0, Deleted: false}), false));
Set(varDeleteBusy, false);
Set(varDeleteStatus, "");
Set(varConfirmOverrideVisible, false);
Set(varDeletedCount, 0);
Set(varEditSaving, false);
Set(varEditSaveStatus, "")
EffectiveName is PrettyName when filled in, otherwise falls back to Title — self-heals every library missing a pretty name, no data migration needed. varIsAdmin gates the Admin screen — edit the email list directly in this line if who's authorized ever changes. colPermissionIndex/colPermissionFiltered feed the Group Search screen — make sure you've run Rebuild Permission Index at least once first, or this loads empty.For each: delete the existing screen if you already have one pasted, New screen (blank) → open the link → Copy YAML → paste. Rename the screen to match the name shown.
.Run() call has no true non-blocking mode (a slow response is caught and shown as a friendly message rather than a raw error, but the flow itself always runs to completion on its own regardless). The status line above it shows how long ago the index was last rebuilt, and flags how many Limited libraries have been created since — both computed live from data already loaded, no extra flow or list needed.RowType and a second expand-tracking collection (colExpandedGroups) rather than nested galleries. v3.1 fixed a real sort-order bug where member rows sorted after every group in a library instead of clustering under their own parent group — the sort's column priority had rank ahead of the group-clustering key instead of after it. v3.2 moved the counts directly into each chevron instead of a separate pill: 4 > on a Library row (how many groups directly granted), (2) > on a Group row (how many nested members) — visible whether the row is expanded or collapsed.ThisItem.Title (the library's real current SharePoint Title) when calling the flow, not PrettyName/EffectiveName — SharePoint's getbytitle() needs the actual current Title, and Create Library (App) renames every library's Title to the pretty name as its last real step, so by the time a library shows up here, its Title already is the pretty name.Force: false on the first call — any empty library in your selection is deleted immediately, any non-empty one is skipped and reported back in one round trip, no separate "check" step needed first. If anything was skipped, an override panel lists exactly which ones and their item counts; "Delete Anyway" re-sends only those, this time with Force: true. First-time use of two controls new to this app: CheckBox@0.0.30 (per-row tick, read/write via its Checked property) and ModernButton's BasePaletteColor for the red delete buttons — Modern buttons don't have a direct Fill property, unlike Classic buttons used everywhere else in this app; light seed colours get overridden by Fluent 2's contrast rules, but the dark red used here renders correctly.colEditQueue), shown here as one stacked card per library, all editable at once. Every field edit patches straight into that local collection as you type (no flow, no round trip); "Save All" is the only point anything actually writes to SharePoint, via a single ForAll + Patch. Library URL is deliberately not editable here — it's the join key this entire app relies on to survive renames, so changing it needs to stay a rare, manual, direct-in-SharePoint action, not a button.4 > on a Library row (how many groups directly granted, collapsed), (2) > on a Group row (how many nested members). If you manually added lblGroupMemberCount per an earlier version of this guide, delete it — it's superseded by this and no longer needed.Add this small nav bar to each of the six screens. This is an add-on, not a full screen replacement: select the screen in the Tree view first, then paste. Four of the five buttons (everything except File Plan and Group Search) are gated by varIsAdmin, same check used everywhere else in this app.
Y: 0, Height: 48 — it will visually overlap each screen's existing Title label (which starts around Y: 24). Nudge your screen's existing top controls down by about 50px afterward, or accept the overlap if it doesn't bother you — this wasn't worth reissuing every screen's full YAML just to shift everything down.v4.3 — Updated 13 Aug 2026 10:23 BST
Screens:
scrFilePlan:
Children:
- recNavBarBg:
Control: Classic/Button@2.2.0
Properties:
Text: =""
DisplayMode: =DisplayMode.View
Fill: =RGBA(30, 30, 45, 1)
HoverFill: =RGBA(30, 30, 45, 1)
PressedFill: =RGBA(30, 30, 45, 1)
BorderThickness: =0
X: =0
Y: =0
Width: =1000
Height: =48
- btnNavFilePlan:
Control: ModernButton@1.0.0
Properties:
Text: ="File Plan"
X: =16
Y: =6
Width: =140
Height: =36
OnSelect: |-
=Navigate(scrFilePlan)
- btnNavGroupSearch:
Control: ModernButton@1.0.0
Properties:
Text: ="Group Search"
X: =164
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrGroupSearch)
- btnNavCreateLibrary:
Control: ModernButton@1.0.0
Properties:
Text: ="Create Library"
Visible: =varIsAdmin
X: =332
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrAdminCreateLibrary)
- btnNavManagePermissions:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Permissions"
Visible: =varIsAdmin
X: =500
Y: =6
Width: =190
Height: =36
OnSelect: |-
=Navigate(scrManagePermissions)
- btnNavManageLibraries:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Libraries"
Visible: =varIsAdmin
X: =698
Y: =6
Width: =170
Height: =36
OnSelect: |-
=Navigate(scrManageLibraries)
Screens:
scrGroupSearch:
Children:
- recNavBarBg:
Control: Classic/Button@2.2.0
Properties:
Text: =""
DisplayMode: =DisplayMode.View
Fill: =RGBA(30, 30, 45, 1)
HoverFill: =RGBA(30, 30, 45, 1)
PressedFill: =RGBA(30, 30, 45, 1)
BorderThickness: =0
X: =0
Y: =0
Width: =1000
Height: =48
- btnNavFilePlan:
Control: ModernButton@1.0.0
Properties:
Text: ="File Plan"
X: =16
Y: =6
Width: =140
Height: =36
OnSelect: |-
=Navigate(scrFilePlan)
- btnNavGroupSearch:
Control: ModernButton@1.0.0
Properties:
Text: ="Group Search"
X: =164
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrGroupSearch)
- btnNavCreateLibrary:
Control: ModernButton@1.0.0
Properties:
Text: ="Create Library"
Visible: =varIsAdmin
X: =332
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrAdminCreateLibrary)
- btnNavManagePermissions:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Permissions"
Visible: =varIsAdmin
X: =500
Y: =6
Width: =190
Height: =36
OnSelect: |-
=Navigate(scrManagePermissions)
- btnNavManageLibraries:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Libraries"
Visible: =varIsAdmin
X: =698
Y: =6
Width: =170
Height: =36
OnSelect: |-
=Navigate(scrManageLibraries)
Screens:
scrAdminCreateLibrary:
Children:
- recNavBarBg:
Control: Classic/Button@2.2.0
Properties:
Text: =""
DisplayMode: =DisplayMode.View
Fill: =RGBA(30, 30, 45, 1)
HoverFill: =RGBA(30, 30, 45, 1)
PressedFill: =RGBA(30, 30, 45, 1)
BorderThickness: =0
X: =0
Y: =0
Width: =1000
Height: =48
- btnNavFilePlan:
Control: ModernButton@1.0.0
Properties:
Text: ="File Plan"
X: =16
Y: =6
Width: =140
Height: =36
OnSelect: |-
=Navigate(scrFilePlan)
- btnNavGroupSearch:
Control: ModernButton@1.0.0
Properties:
Text: ="Group Search"
X: =164
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrGroupSearch)
- btnNavCreateLibrary:
Control: ModernButton@1.0.0
Properties:
Text: ="Create Library"
Visible: =varIsAdmin
X: =332
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrAdminCreateLibrary)
- btnNavManagePermissions:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Permissions"
Visible: =varIsAdmin
X: =500
Y: =6
Width: =190
Height: =36
OnSelect: |-
=Navigate(scrManagePermissions)
- btnNavManageLibraries:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Libraries"
Visible: =varIsAdmin
X: =698
Y: =6
Width: =170
Height: =36
OnSelect: |-
=Navigate(scrManageLibraries)
Screens:
scrManagePermissions:
Children:
- recNavBarBg:
Control: Classic/Button@2.2.0
Properties:
Text: =""
DisplayMode: =DisplayMode.View
Fill: =RGBA(30, 30, 45, 1)
HoverFill: =RGBA(30, 30, 45, 1)
PressedFill: =RGBA(30, 30, 45, 1)
BorderThickness: =0
X: =0
Y: =0
Width: =1000
Height: =48
- btnNavFilePlan:
Control: ModernButton@1.0.0
Properties:
Text: ="File Plan"
X: =16
Y: =6
Width: =140
Height: =36
OnSelect: |-
=Navigate(scrFilePlan)
- btnNavGroupSearch:
Control: ModernButton@1.0.0
Properties:
Text: ="Group Search"
X: =164
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrGroupSearch)
- btnNavCreateLibrary:
Control: ModernButton@1.0.0
Properties:
Text: ="Create Library"
Visible: =varIsAdmin
X: =332
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrAdminCreateLibrary)
- btnNavManagePermissions:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Permissions"
Visible: =varIsAdmin
X: =500
Y: =6
Width: =190
Height: =36
OnSelect: |-
=Navigate(scrManagePermissions)
- btnNavManageLibraries:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Libraries"
Visible: =varIsAdmin
X: =698
Y: =6
Width: =170
Height: =36
OnSelect: |-
=Navigate(scrManageLibraries)
Screens:
scrManageLibraries:
Children:
- recNavBarBg:
Control: Classic/Button@2.2.0
Properties:
Text: =""
DisplayMode: =DisplayMode.View
Fill: =RGBA(30, 30, 45, 1)
HoverFill: =RGBA(30, 30, 45, 1)
PressedFill: =RGBA(30, 30, 45, 1)
BorderThickness: =0
X: =0
Y: =0
Width: =1000
Height: =48
- btnNavFilePlan:
Control: ModernButton@1.0.0
Properties:
Text: ="File Plan"
X: =16
Y: =6
Width: =140
Height: =36
OnSelect: |-
=Navigate(scrFilePlan)
- btnNavGroupSearch:
Control: ModernButton@1.0.0
Properties:
Text: ="Group Search"
X: =164
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrGroupSearch)
- btnNavCreateLibrary:
Control: ModernButton@1.0.0
Properties:
Text: ="Create Library"
Visible: =varIsAdmin
X: =332
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrAdminCreateLibrary)
- btnNavManagePermissions:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Permissions"
Visible: =varIsAdmin
X: =500
Y: =6
Width: =190
Height: =36
OnSelect: |-
=Navigate(scrManagePermissions)
- btnNavManageLibraries:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Libraries"
Visible: =varIsAdmin
X: =698
Y: =6
Width: =170
Height: =36
OnSelect: |-
=Navigate(scrManageLibraries)
Screens:
scrEditLibraries:
Children:
- recNavBarBg:
Control: Classic/Button@2.2.0
Properties:
Text: =""
DisplayMode: =DisplayMode.View
Fill: =RGBA(30, 30, 45, 1)
HoverFill: =RGBA(30, 30, 45, 1)
PressedFill: =RGBA(30, 30, 45, 1)
BorderThickness: =0
X: =0
Y: =0
Width: =1000
Height: =48
- btnNavFilePlan:
Control: ModernButton@1.0.0
Properties:
Text: ="File Plan"
X: =16
Y: =6
Width: =140
Height: =36
OnSelect: |-
=Navigate(scrFilePlan)
- btnNavGroupSearch:
Control: ModernButton@1.0.0
Properties:
Text: ="Group Search"
X: =164
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrGroupSearch)
- btnNavCreateLibrary:
Control: ModernButton@1.0.0
Properties:
Text: ="Create Library"
Visible: =varIsAdmin
X: =332
Y: =6
Width: =160
Height: =36
OnSelect: |-
=Navigate(scrAdminCreateLibrary)
- btnNavManagePermissions:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Permissions"
Visible: =varIsAdmin
X: =500
Y: =6
Width: =190
Height: =36
OnSelect: |-
=Navigate(scrManagePermissions)
- btnNavManageLibraries:
Control: ModernButton@1.0.0
Properties:
Text: ="Manage Libraries"
Visible: =varIsAdmin
X: =698
Y: =6
Width: =170
Height: =36
OnSelect: |-
=Navigate(scrManageLibraries)
payaml-validate tool) — clean on all six, including zero unproven-property flags, since every control/property here has already been proven elsewhere in this app. One thing not independently confirmed: that Studio's paste, given a screen selected in the Tree view and a Screens: scrName: Children: [...] fragment on the clipboard, adds those as new children of that screen rather than something else. If it doesn't behave that way, fall back to inserting each control manually (Insert → the control type, then set the properties shown above) — same end result, just click-by-click instead of paste.Found a problem? Report it here.