In June, I wrote about building a data-driven vinyl collection app with Microsoft Power Apps. That article focused on the user experience: searching the collection, filtering records by genre, visualizing albums by decade, tracking replacement cost, and picking a random album to play.
At the time, I understood the app primarily through what I had built in Power Apps Studio.
I can now see it differently.
By examining the underlying technical blueprint, including the app’s source structure and Power Fx formulas, I can trace how the application grew from a standard generated app into a more customized solution. I can also see where some of the original assumptions were incomplete.
The resulting picture is more useful than a simple success story. It shows what happens when a personal low-code project evolves organically: generated components remain in place, new screens are added, formulas become more sophisticated, and design shortcuts that work at one scale begin to create risks at the next.
The App as It Exists Today
Vinyl Collection App V4 is a phone-layout canvas app backed by a single SharePoint list. It contains seven screens and catalogs 263 records.
The app began with the familiar Power Apps “create from data” pattern:
- Browse screen
- Detail screen
- Edit screen
I then extended it with four custom screens:
- Category analytics
- Investment analytics
- Random album selection
- Category-based browsing
The final architecture remains intentionally simple. Every screen reads from or writes directly to the same SharePoint list. There is no intermediate database, custom API, or separate reporting layer.
| Layer | Current implementation |
|---|---|
| User experience | Power Apps canvas app |
| Data storage | One SharePoint list |
| Application logic | Power Fx formulas on controls |
| Screens | Seven |
| Data sources | One |
| Records | 263 |
| App-level formulas | None |
| Collections | None |
| Flows referenced by the app package | None |
Power Automate populates the RandSearch field because the SharePoint Row ID cannot be used directly in the supporting calculated-column expression. The flow supplies the key used by the random album feature. Although the current app package does not reference that flow, the automation remains part of the solution.
The Counter field is handled differently: its SharePoint column default is set to 1, so each new item receives that value when it is created. It does not depend on the flow that populates RandSearch.
The technical inventory therefore needs to include both the canvas app and the Power Automate flow that maintains RandSearch. Reviewing the app package alone does not capture every dependency in the solution.
What the Blueprint Revealed
A canvas app can look straightforward in Power Apps Studio while still containing a sizable amount of configuration.
V4 includes:
- 83 labels
- 18 icons
- Nine buttons
- Seven text inputs
- Six images
- Three galleries
- Two pie charts and their legends
- Two combo boxes
- One dropdown
- One date picker
- One add-media control
- One edit form
- One display form
All of the application logic lives in individual control properties. App.OnStart and App.Formulas are empty, and no collections are loaded when the app starts.
That is not inherently wrong. For a personal solution with 263 records, it keeps the architecture understandable. However, it also means that shared behavior, colors, selected records, and reusable calculations are distributed across individual screens and controls.
As the application grew, that decentralized model made inconsistencies easier to introduce.
Microsoft documents that the source for canvas apps is represented in human-readable Power Fx and YAML. The .pa.yaml files contained within an extracted .msapp can be used to review how screens, controls, and formulas are defined. Microsoft cautions that generated YAML is read-only and that changes should continue to be made through supported Power Apps tooling.
For this project, the blueprint turned the app from something I could operate into something I could inspect.
Browse Remains the Application Hub
The generated browse screen remains the center of the application. Its primary gallery searches the SharePoint list by album title or artist and then sorts the results by title:
SortByColumns(
Search(
'Vinyl Collection',
TextSearchBox1.Text,
Title,
Artist
),
"Title",
If(
SortDescending1,
SortOrder.Descending,
SortOrder.Ascending
)
)
From that screen, I can:
- Search the collection
- Change the sort direction
- Refresh the data
- Add a record
- Open an album
- Navigate to any of the four custom screens
This hub-and-spoke design has worked well because users always have a predictable place to begin. The generated detail and edit screens use Back(), while the custom screens provide a Home button that returns to Browse.
The navigation model is simple and consistent from the user’s perspective. Underneath it, however, the app is not always consistent about which gallery owns the selected record.
The Most Important Bug: A Selected Record Is Not Global State
The category-search screen contains a second gallery. Selecting an album there opens the shared detail screen.
The problem is that the detail form, edit form, and delete action are still tied to:
BrowseGallery1.Selected
That reference belongs to the primary Browse screen. If I choose an album from the category-filtered gallery, the shared detail screen can display whatever record was last selected in the main Browse gallery. The same incorrect reference can also affect editing or deletion.
This is a classic example of a generated pattern working correctly until the application is extended.
The better design is to capture the selected record before leaving either gallery:
Set(varSelectedAlbum, ThisItem);
Navigate(DetailScreen1, ScreenTransition.None)
The shared forms can then use:
varSelectedAlbum
The delete action should use the same variable:
Remove(
'Vinyl Collection',
varSelectedAlbum
)
This separates navigation state from a specific gallery control.
.Selected property.That lesson applies far beyond a vinyl collection. The same issue can appear in asset-management apps, request-tracking solutions, employee directories, inventory tools, and any other canvas app that introduces multiple entry points to a common record screen.
The Random Album Feature Works, but the Data Has Outgrown the Formula
The random album screen was one of the more entertaining additions to the app. It chooses an album number and displays the corresponding record.
The current process is:
Refresh('Vinyl Collection');
Set(
varRandomIndex,
RandBetween(
1,
CountRows('Vinyl Collection')
)
)
That formula runs only when the button is pressed, so the screen is blank until the first click. The blank first visit is a side effect of iteration: as the list’s schema changed, I reworked the formula, and the selection ended up living only on the button.
A text control converts that number into a key:
"Num_" & varRandomIndex
The gallery then searches RandSearch for that key. This works only when every number the formula can generate has exactly one matching key, and every record has a key the formula can reach.
Neither is true. The flow builds each key from the item’s SharePoint ID, and the list’s IDs run from 1 through 265 because items 188 and 189 were deleted. The collection holds 263 records, so the formula picks a number from 1 to 263. That creates two defects:
- When the formula picks 188 or 189, no record has that key and the screen comes up blank. That happens roughly once in every 130 picks.
- The albums with IDs 264 and 265 can never be chosen, because the formula never generates a number that high.
Every future deletion makes both problems worse: more numbers that return nothing, and more albums stranded above the range. The odds of a blank screen are still modest, but the design problem matters more than the percentage. A SharePoint ID is an identifier, not a guaranteed sequential row number.
A more resilient pattern is to choose a record from the available rows rather than inventing an ID:
ClearCollect(
colAlbums,
'Vinyl Collection'
);
Set(
varRandomAlbum,
Index(
colAlbums,
RandBetween(
1,
CountRows(colAlbums)
)
)
)
The screen can display varRandomAlbum directly. This also removes the need for the RandSearch workaround. Running the same formula from the screen’s OnVisible property, as well as from the button, means an album is already showing the first time the screen opens.
For the existing collection size, First(Shuffle(...)) could provide another straightforward approach. The larger architectural point is to randomize the records that exist, not the identifier range in which records might exist.
One caveat: ClearCollect against SharePoint is not delegable either, so the collection holds only as many rows as the app’s data-row limit allows (500 by default, 2,000 at most). At 263 records that is not a constraint. If the collection ever outgrows the limit, albums beyond it could never be chosen, which is the same boundary the next section covers.
Delegation Is the Next Scaling Boundary
At 263 records, the app still operates below its configured 500-row data limit. That makes several nondelegable formulas appear safe. They will not remain safe indefinitely.
The blueprint identified nondelegable operations in areas such as:
- Browse search
- Record counts
- Replacement-value calculations
- Grouping
- Chart aggregation
- Random selection
Microsoft explains that delegation allows Power Apps to send a query to the data source for processing. When an expression cannot be delegated, Power Apps retrieves only the number of records permitted by the app’s data-row setting and performs the operation locally. Microsoft identifies 500 as the default limit and warns that nondelegable operations can return incomplete or incorrect results once the data exceeds that configured boundary.
That does not mean the app needs Dataverse immediately. It means V5 should make scaling an intentional decision.
- Increase the data-row limit as a short-term safeguard.
- Replace substring search with a delegable
StartsWithpattern where the user experience permits it. - Review every blue delegation warning in Power Apps Studio.
- Separate operations that must be exact from features where a local collection is acceptable.
- Reassess the data platform if the collection or application requirements move significantly beyond the capabilities of the SharePoint design.
The collection is not approaching thousands of records today. The valuable part is recognizing the boundary before it becomes a production defect.
The Charts Show Both the Strength and Cost of Incremental Design
The category and investment screens use similar Power Fx patterns. The category chart groups records by release decade and then totals an always-1 column named Counter:
AddColumns(
GroupBy(
'Vinyl Collection',
'Decade Released',
DATA
),
DecadeTotal,
Sum(DATA, Counter)
)
The investment chart groups the same records but sums replacement cost:
AddColumns(
GroupBy(
'Vinyl Collection',
'Decade Released',
DATA
),
DecadeTotal,
Sum(
DATA,
'Cost to Replace'
)
)
These formulas demonstrate how a SharePoint list can support lightweight analytics directly inside a canvas app. They also reveal where the data model was changed to accommodate a presentation requirement.
The Counter column supports chart aggregation through a default value of 1 assigned when each SharePoint item is created. No Power Automate step is needed to initialize it. A simpler expression can calculate the number of grouped records directly:
AddColumns(
GroupBy(
'Vinyl Collection',
'Decade Released',
DATA
),
DecadeTotal,
CountRows(DATA)
)
Similarly, the category totals are currently represented by individual labels with hard-coded genre names. That design omits Pop and Alternative, meaning the visible genre counts add up to 260 rather than the full collection of 263.
A dynamic gallery generated from grouped category data would:
- Include every existing category
- Automatically show new categories
- Eliminate repeated formulas
- Reduce maintenance
- Prevent the visible total from drifting away from the actual record count
This is a good example of the difference between making a feature work and turning it into a reusable pattern.
The Data Model Needs One More Cleanup Pass
The SharePoint list includes both a free-text release-date field and a date-formatted calendar field.
The free-text values contain several formats, including complete dates, month-and-year values, and years by themselves. That makes the field readable but difficult to sort, filter, or use consistently in calculations. Meanwhile, the structured calendar field is not used by the app and contains blanks.
V5 should establish a single authoritative release-date strategy:
- Backfill the structured date where a precise date is known.
- Decide how to represent albums for which only a year or month is known.
- Format the structured value for display.
- Derive the release decade from the structured date where possible.
- Retire or clearly redefine the older free-text field.
The same cleanup pass can address the ten records without cover images and verify whether the two entries named The Very Best of Death Row are a legitimate pair or an unintended duplicate.
None of those issues prevents the app from operating. Together, however, they show how quickly data quality becomes part of application quality.
Accessibility Cannot Be Left for the End
The blueprint also identified controls without accessible labels and galleries without an appropriate keyboard-navigation configuration.
Microsoft’s Accessibility Checker can identify potential screen-reader and keyboard issues and classify them by severity. Missing accessible labels can prevent users who rely on screen readers from understanding interactive controls.
Microsoft’s canvas-app accessibility guidance recommends assigning meaningful AccessibleLabel values to input controls and configuring keyboard navigation through logical control structure and TabIndex behavior. It also recommends sufficient color contrast and accessible behavior for images and icons used as controls.
For V5, the accessibility pass should include:
- Accessible labels for search and form inputs
- Descriptive labels for interactive icons
- Logical keyboard navigation
- Appropriate gallery
TabIndexvalues - Review of the hand-selected header, accent, and chart colors
- Testing with the Power Apps Accessibility Checker
A personal app is still a useful place to practice accessible design. Accessibility habits become much easier to carry into client solutions when they are part of the normal build process rather than a final compliance exercise.
What V5 Should Become
V5 does not need to be a complete rewrite. The strongest next version would preserve the architecture that already works while correcting the assumptions exposed by the blueprint.
| Priority | Improvement | Why it matters |
|---|---|---|
| 1 | Introduce varSelectedAlbum and rebind details, edits, and deletion. | Prevents shared screens from acting on the wrong gallery selection. |
| 2 | Replace ID-based random selection with row-based selection and run it when the screen opens. | Eliminates blank results, makes every album selectable, and removes the blank first visit. |
| 3 | Retire RandSearch and Counter dependencies where practical. | Simplifies the data model and reduces maintenance. |
| 4 | Replace hard-coded genre labels with a dynamic gallery. | Keeps totals complete as categories change. |
| 5 | Review delegation warnings and centralize reusable formulas and colors. | Improves scaling and consistency. |
| 6 | Normalize release dates and complete the accessibility pass. | Improves data quality, reporting, keyboard access, and screen-reader support. |
| 7 | Add cover art for the ten records missing images and confirm whether the duplicate The Very Best of Death Row entries should both remain. | Improves collection quality and keeps the browse and detail screens complete. |
The current app proves that a generated solution can grow into something genuinely useful. The V5 work will be about making that growth intentional.
What This Project Taught Me
The first version of this story was about using Power Apps, SharePoint, Power Fx, and automation to turn a personal collection into a usable application.
The follow-up lesson is more technical.
Low-code does not eliminate architecture. It changes where architecture appears.
- The gallery selected by a form
- The way state moves between screens
- Whether an identifier is mistaken for a sequence
- Whether a query delegates
- Whether a chart requires an artificial column
- Whether colors are copied or centralized
- Whether fields are structured for future analysis
- Whether a control can be understood by someone using a screen reader
None of those decisions required traditional software development. All of them affected the reliability and maintainability of the app.
The collection itself has gradually moved from accumulation to curation. The application now appears to be entering the same phase.
I am no longer just adding screens and features.
I am curating the architecture behind them.
Related Reading
- Building a Data-Driven Vinyl Collection App with Power Apps
- From Crate Filling to Careful Curation: What My Vinyl Collection Says About How I Collect

