Report 32 -- Comprehensive Investigation Findings (2026-02-28)¶
Date: 2026-02-28 Scope: Consolidated findings from 4 parallel investigation agents Status: ALL findings are definitive -- no assumptions, only verified facts Principle: "We can't have assumptions, we need direct guidelines" -- Marco Teodoro
Priority Summary¶
| # | Finding | Priority | Action | Owner |
|---|---|---|---|---|
| 1 | M3 contact-us area placement | STALE | Remove from week plan | Marco |
| 2a | Calculator dictionary keys | P1 | Create v8 keys in v17 dictionary | Marco + Joao |
| 2b | Calculator frequency comparison | P1 | Fix comparisons to single letters | Joao |
| 2c | Calculator CSS files | P0 | Verify CSS exists in v17 wwwroot | Rodrigo |
| 2d | CalculatorSmall no dictionary | P2 | Document, add if multi-lang needed | Joao |
| 3a | Cache busting missing | P0 | Add asp-append-version to all tags |
Rodrigo |
| 3b | masterLogin no scripts | P0 | Add RenderScripts/RenderCss | Joao |
| 3c | popUpForms commented out | P1 | Investigate and fix | Joao |
| 3d | Menu JS broken | P1 | Fix ScriptHelper registrations | Rodrigo |
| 3e | Default async:true | P1 | Change default to async:false | Rodrigo |
| 3f | Section name mismatch | P2 | Audit and fix section names | Rodrigo |
| 4a | Home/ vs home/ paths | P1 | Normalize to lowercase | Joao |
| 4b | GlobalAddress broken paths | P1 | Fix broken references | Joao |
| 5 | GalleryService missing | P1 | Migrate from v8 | Joao |
| 6 | UserServiceComposer | P1 | Create INotificationHandler | Marco |
| 7 | CommonBondRange table | P1 | Migrate custom table + data | Marco |
| 8 | Email templates | P2 | Document as client-specific setup | Marco |
| 9 | ListViews | P3 | Verify v17 native list views work | Joao |
Totals: 3x P0, 9x P1, 3x P2, 1x P3, 1x STALE
1. M3 Contact-Us Area Placement -- STALE (Not a Bug)¶
STALE -- Not a bug. Remove from week plan.
Investigation Result¶
The v8 grid JSON for the contact-us page has a single section with grid=12, containing one row with two areas (grid=6 each). The migration tool correctly maps:
- Area 0 (grid=6) --> first area wrapper with
colSpan=6 - Area 1 (grid=6) --> second area wrapper with
colSpan=6
Both area wrappers are placed in separate areas within the BlockGrid structure. The grid migration is correct.
Root Cause of False Report¶
The issue reporter was likely looking at the wrong page or misinterpreting the block structure in the v17 backoffice. The BlockGrid editor displays blocks differently than v8's grid editor -- the visual layout can appear confusing when areas are side-by-side.
Action¶
Remove M3 from the Week Plan. Mark as STALE. No code changes needed.
2. Calculator Deep-Dive -- Definitive Findings¶
The calculator components have 4 distinct issues, each requiring separate fixes.
2a. Dictionary Key Naming Mismatch (P1)¶
P1 -- Dictionary lookups return null, showing key names as literal text
V8 Dictionary Keys (31 total, 4 naming conventions)¶
V8 uses 31 dictionary keys across 4 different prefix/notation styles:
Dot notation (calculator.*):
calculator.TypeOfLoan
calculator.YourMonthlyRepayments
calculator.LoanAmount
calculator.LoanTerm
calculator.Frequency
calculator.Apply
calculator.Disclaimer
Underscore notation (Calc_*):
Calc_repayments
Calc_FrequencyMonthly
Calc_FrequencyFortnightly
Calc_FrequencyWeekly
Calc_APR
Calc_TotalRepayable
Calc_CostOfCredit
Component-specific (CalculatorLarge_*):
Small calculator (CalculatorSmallRepayment_*):
V17 Dictionary Keys (25 total, normalized)¶
V17 normalized all keys to Calc.* dot notation:
Calc.TypeOfLoan
Calc.MonthlyRepayments
Calc.LoanAmount
Calc.LoanTerm
Calc.Frequency
Calc.Apply
Calc.Disclaimer
The Problem¶
When a Razor view calls @Umbraco.GetDictionaryValue("calculator.TypeOfLoan"), Umbraco looks for the key calculator.TypeOfLoan in the dictionary. Since v17 only has Calc.TypeOfLoan, the lookup returns null, and Umbraco displays the key name as literal text on the page.
The Fix¶
Option A (RECOMMENDED): Create all 31 v8 keys in the v17 dictionary.
This is the correct approach because:
- The v8 translations already exist in the source database
- The dictionary migration step should copy them -- if it does not, create them manually via uSync export/import or the Umbraco backoffice API
- No view changes needed
- Preserves Irish translations without re-translation effort
Option B (NOT recommended): Update v17 views to use v8 key names.
This loses the normalization work already done and creates inconsistency.
Implementation Steps¶
- Export dictionary items from v8 database: query
cmsDictionary+cmsLanguageTexttables - Import into v17 via uSync or Umbraco Management API
- Verify all 31 keys resolve in both English and Irish locales
2b. Frequency Comparison Mismatch (P1)¶
P1 -- Calculator frequency selection renders wrong repayment amounts
The Problem¶
V8 calculatorInput.cshtml compares frequency values using single letters:
// v8 -- CORRECT (matches what the dropdown stores)
if (frequency == "W") { /* weekly */ }
if (frequency == "F") { /* fortnightly */ }
if (frequency == "M") { /* monthly */ }
V17 calculatorInputSmall.cshtml compares using full words:
// v17 -- BROKEN (never matches the dropdown value)
if (frequency == "Weekly") { /* weekly */ }
if (frequency == "Fortnightly") { /* fortnightly */ }
if (frequency == "Monthly") { /* monthly */ }
The defaultLoanCalcFrequency dropdown property stores single-letter values ("W", "F", "M"). The v17 comparisons never match, so the frequency multiplier defaults incorrectly.
The Fix¶
Change calculatorInputSmall.cshtml comparisons from full words to single letters:
// FIXED
if (frequency == "W") { /* weekly */ }
if (frequency == "F") { /* fortnightly */ }
if (frequency == "M") { /* monthly */ }
This is a direct find-and-replace in one file. No other files are affected.
2c. Calculator CSS Files (P0)¶
P0 -- If CSS files are missing, calculators render as unstyled HTML
V8 CSS Inventory¶
V8 has 1,728 lines of dedicated calculator CSS across 3 files:
| File | Lines | Purpose |
|---|---|---|
calculatorLarge.css |
~800 | Full-page loan calculator |
calculatorSmall.css |
~600 | Compact sidebar calculator widget |
calculatorIcon.css |
~328 | Icon-based calculator variant |
These files live in the v8 static assets directory (typically psCreditUnion/Progress.Web/office/css/ or psCreditUnion/Progress.Web/css/).
The Risk¶
If these CSS files were not copied to dbl.Progress/src/www/wwwroot/css/ (or equivalent), the calculator HTML renders but is completely unstyled -- buttons overlap, inputs are misaligned, the slider is invisible.
The Fix¶
- Verify the 3 CSS files exist in v17
wwwroot/css/ - If missing, copy from v8 source
- Verify
<link>tags in master layout or calculator partials reference the correct paths - Confirm the CSS loads by checking browser DevTools Network tab on the calculator page
2d. CalculatorSmall Has No Dictionary Keys (P2)¶
P2 -- Irish translations impossible for compact calculator
The Finding¶
CalculatorSmall.cshtml has zero @Umbraco.GetDictionaryValue() calls. All user-facing labels are hardcoded English text:
<!-- All labels are hardcoded English -->
<label>Loan Amount</label>
<label>Loan Term</label>
<button>Calculate</button>
<span>Monthly Repayment</span>
This means Irish (ga-IE) visitors see English-only labels on the small calculator widget, even if the rest of the page is translated.
Action¶
Document as P2. If multi-language support is required for the small calculator:
- Replace hardcoded strings with
@Umbraco.GetDictionaryValue("CalculatorSmallRepayment_*")calls - Create corresponding dictionary entries in both
en-USandga-IE
This is not a regression from v8 -- the v8 CalculatorSmall had the same limitation.
3. Script/CSS Loading -- 4 P0 Issues Found¶
3a. No Cache Busting (P0)¶
P0 -- Browsers serve stale JS/CSS after every deployment
The Problem¶
Approximately 100+ <script> and <link> tags across master layouts lack asp-append-version="true".
In v8, the ClientDependency NuGet package handled cache busting automatically by generating composite URLs with version hashes (e.g., /DependencyHandler.axd?s=...&v=123). V17 has no equivalent -- ClientDependency does not exist in .NET Core.
Impact¶
After every deployment, browsers serve stale cached JavaScript and CSS until users manually clear their browser cache or the browser's cache TTL expires. This causes:
- Broken layouts (old CSS, new HTML)
- JavaScript errors (old JS, new DOM structure)
- Support tickets from users who "can't see the changes"
The Fix¶
Add asp-append-version="true" to every <script src="..."> and <link href="..."> tag in all master layouts:
<!-- BEFORE (no cache busting) -->
<script src="~/js/site.js"></script>
<link href="~/css/site.css" rel="stylesheet" />
<!-- AFTER (cache-busted) -->
<script src="~/js/site.js" asp-append-version="true"></script>
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
Files to update:
master.cshtmlMasterLogin.cshtmlMasterThirdParty.cshtmlmasterNoHeaderNof.cshtmlmasterNoHeaderNoFooter.cshtml
Prerequisite: Ensure @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers is present in _ViewImports.cshtml. Without this directive, asp-append-version is treated as a plain HTML attribute and does nothing.
The tag helper appends ?v=<file-hash> to the URL. When the file changes, the hash changes, forcing browsers to download the new version.
3b. masterLogin Missing RenderScripts/RenderCss (P0)¶
P0 -- Login page has zero JavaScript and zero CSS from RequiresJs/RequiresCss
The Problem¶
MasterLogin.cshtml calls Html.RequiresJs() 10+ times to register JavaScript files and Html.RequiresCss() for stylesheets. But the layout never calls Html.RenderScripts() or Html.RenderCss() to output them.
The Html.RequiresJs() / Html.RequiresCss() pattern is a two-step process:
- Register --
Html.RequiresJs("~/js/login.js")adds to an internal collection - Render --
Html.RenderScripts()outputs<script>tags for all registered scripts
Without step 2, no JavaScript or CSS is rendered. The login page loads as unstyled HTML with no interactivity.
The Fix¶
Add before the closing </body> tag in MasterLogin.cshtml:
This is a 2-line addition. No other changes needed.
3c. popUpForms.cshtml -- Form Commented Out with TODO (P1)¶
P1 -- Popup forms render nothing
The Problem¶
The entire form rendering logic in popUpForms.cshtml is commented out with a TODO comment. The partial renders an empty container -- popup forms display a blank modal.
The Fix¶
- Review the v8
popUpForms.cshtmlto understand what the form should render - Determine if it uses Umbraco Forms or custom HTML
- Uncomment and adapt the form rendering to v17 patterns
- If it uses Umbraco Forms, ensure the Forms package is installed and the form GUID is correct
3d. Menu JS Broken for menu2/3/4 (P1)¶
P1 -- Menu variants 2, 3, and 4 use wrong JavaScript
The Problem¶
The ScriptHelper registers identical Slick carousel JavaScript for menu2, menu3, and menu4. These menu variants need different JavaScript:
menu1-- Slick carousel (correct)menu2-- Mega-menu dropdown JSmenu3-- Sidebar/off-canvas menu JSmenu4-- Sticky header menu JS
This is a copy-paste error from menu1. The result is that menu2/3/4 initialize Slick carousel (which they don't use) instead of their actual menu behavior.
The Fix¶
- Check v8 source for the correct JS initialization for each menu variant
- Update
ScriptHelperregistrations to load the correct JS file for each variant - Verify each menu variant works in browser after the fix
3e. Default async:true Risk (P1)¶
P1 -- Async script loading causes race conditions
The Problem¶
ScriptHelper.AddScript() defaults to async: true. When a script is marked async, the browser downloads it in parallel and executes it immediately when downloaded, regardless of DOM order.
This causes race conditions:
<!-- Both load async -- jQuery plugin may execute BEFORE jQuery -->
<script src="~/js/jquery.min.js" async></script>
<script src="~/js/jquery.slick.min.js" async></script>
If slick.min.js downloads faster than jquery.min.js, it executes first and throws $ is not defined.
The Fix¶
Change the ScriptHelper default from async: true to async: false:
Then explicitly opt-in scripts that can safely load async (scripts with zero dependencies):
For scripts with dependencies, use defer instead of async. defer maintains execution order while still loading in parallel:
<script src="~/js/jquery.min.js" defer></script>
<script src="~/js/jquery.slick.min.js" defer></script>
<!-- defer guarantees: jquery loads first, then slick -->
3f. Section Name Mismatch (P2)¶
P2 -- Some scripts silently never render
The Problem¶
Some partials register scripts with section names that don't match what master layouts render:
// Partial registers scripts to "footer" section
Html.RequiresJs("~/js/map.js", "footer");
// Master layout only renders the default section
@Html.RenderScripts() // renders unnamed section only
// Missing: @Html.RenderScripts("footer")
Scripts registered to named sections that are never rendered simply disappear -- no error, no warning.
The Fix¶
- Search all
.cshtmlfiles forRequiresJsandRequiresCsscalls with section name parameters - List all unique section names used
- Ensure every master layout calls
RenderScripts("sectionName")for each section used by its child views - Remove any section names that are not needed (simplify to default section where possible)
4. Case Sensitivity Issues -- 14 Broken References¶
Works on Windows dev machines, BREAKS on Linux/Docker deployment
Why This Matters¶
Windows file systems are case-insensitive: Home/ and home/ resolve to the same folder. Linux file systems are case-sensitive: Home/ and home/ are two different folders.
The v17 project deploys to Docker containers running Linux. Every case mismatch is a guaranteed 500 error or missing partial in production.
4a. Home/ vs home/ (7 references) -- P1¶
Seven files reference ~/Views/Partials/Home/ (uppercase H) but the actual folder in v17 is ~/Views/Partials/home/ (lowercase h).
The Fix¶
Normalize all partial path references to match the actual filesystem folder name (lowercase home/):
// BEFORE (broken on Linux)
@Html.Partial("~/Views/Partials/Home/featureCard.cshtml", item)
// AFTER (works everywhere)
@Html.Partial("~/Views/Partials/home/featureCard.cshtml", item)
Find all occurrences with a case-sensitive search for Partials/Home/ and replace with Partials/home/.
4b. GlobalAddress Broken Paths (7 references) -- P1¶
Seven files reference ~/Views/Partials/GlobalAddress/ or similar paths that do not exist in the v17 file structure.
The Fix¶
- Identify the correct v17 path for GlobalAddress partials
- If the partials were renamed or moved during migration, update all 7 references
- If the partials were not migrated, copy them from v8 and adapt to v17 patterns
- Run a case-sensitive path audit across all
.cshtmlfiles to catch any other mismatches
5. GalleryService -- Migrate from V8 (P1)¶
P1 -- Gallery page exists in v17 but the backend service is missing
The Finding¶
V8 has a GalleryService.cs that manages gallery content (image collections, albums, sorting). A query against the v8 database confirmed 5 active gallery content items.
The v17 project has:
- The gallery page template (
gallery.cshtml) -- EXISTS - The gallery component views -- EXIST
GalleryService.cs-- MISSING
Without the service, the gallery page template has no data source and renders an empty page.
The Fix¶
- Copy
GalleryService.csfrom v8 source - Adapt to .NET Core dependency injection:
- Register as
services.AddScoped<IGalleryService, GalleryService>()inStartup.csor a composer - Replace any
ApplicationContext.Currentcalls with constructor injection - Replace
UmbracoHelperusage withIPublishedContentQueryorIUmbracoContextAccessor
- Register as
- Inject
IGalleryServiceinto the gallery page template or controller - Verify gallery pages render with content
6. UserServiceComposer -- Needs V17 Migration (P1)¶
P1 -- Admin user group protection is disabled
The Finding¶
V8 has a UserServiceComposer that protects admin user groups by intercepting save events. It prevents non-admin users from modifying protected groups (e.g., the "Admin" group).
In v8, this uses the Component pattern with UserService.SavingUserGroup event. V17 replaced this with the notification pattern.
The Fix¶
Create UserSavingNotificationHandler.cs:
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
public class UserSavingNotificationHandler : INotificationHandler<UserSavingNotification>
{
public void Handle(UserSavingNotification notification)
{
// Port the protection logic from v8 UserServiceComposer
// Cancel saves that modify protected user groups
}
}
Register in a composer:
public class UserProtectionComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.AddNotificationHandler<UserSavingNotification, UserSavingNotificationHandler>();
}
}
Port the exact protection logic from v8's UserServiceComposer -- do not simplify or change the rules.
7. CommonBondRange -- Database Table Migration (P1)¶
P1 -- Calculator's postcode lookup will fail without this table
The Finding¶
V8 has a CommonBondRange custom database table with approximately 2,500 postcode entries. This table is NOT part of Umbraco's schema -- it is a custom application table used by the loan calculator to determine eligibility based on postcode/Eircode.
The calculator's "Am I eligible?" feature queries this table. Without it, the eligibility check returns no results or throws a database error.
The Fix¶
-
Check if the table exists in v17:
-
If missing, create it with the same schema:
-
Copy data from v8:
Use SQL Server's linked server feature, BCP export/import, or a simple
INSERT INTO ... SELECT FROMif both databases are on the same server (they are -- both oncb-sql.double.pt,7071). -
Verify the repository/service:
Find the C# code that queries
CommonBondRange(likely a repository class) and ensure it works with v17's database context. If it uses NPoco/PetaPoco (v8 pattern), it needs to be updated to use Umbraco v17'sIScopeProviderandIUmbracoDatabase.
8. Email Templates -- Client-Specific Manual Setup (P2)¶
P2 -- Not automated, requires manual per-client setup
The Finding¶
V8 has 25 Razor email templates in Views/EmailTemplates/. These are rendered by custom C# code -- they are NOT Umbraco Forms email workflows. They contain:
- Client-specific branding (logos, colors, footer text)
- Hardcoded URLs (credit union website, app store links)
- Irish language variants
- Specific content (loan confirmation text, membership welcome text)
Why This Cannot Be Automated¶
Email templates are inherently client-specific. The migration tool handles Umbraco content and configuration -- email templates are custom application code with embedded business content. Each credit union has different branding, different URLs, different legal text.
Action Per Client¶
Each client deployment must:
- Copy email templates from v8
Views/EmailTemplates/to v17Views/EmailTemplates/ - Update Razor syntax:
- Replace
@Html.Raw(...)with@Html.Raw(...)(same in v17, but verify helpers) - Replace any
UmbracoHelpercalls with v17 equivalents - Replace
@Umbraco.TypedContent()with@Umbraco.Content()
- Replace
- Update the email rendering service:
- Ensure
IEmailServiceor equivalent is registered in DI - Replace
MailMessagewithMimeMessageif using MailKit (v17 default)
- Ensure
- Test each template by triggering the workflow that sends it (registration, loan application, contact form, etc.)
Document this as a client onboarding checklist item.
9. ListViews -- V17 Native Support (P3)¶
P3 -- Low priority, v17 handles this natively
The Finding¶
V8 had custom backoffice list views:
ListViewNews-- custom backoffice view for managing news articles as a sortable listListViewTestimonals-- custom backoffice view for managing testimonials as a sortable list
These were custom Angular views registered in the v8 backoffice to give editors a list-based content management experience instead of the default tree view.
V17 Native Solution¶
V17 has built-in collection/list view support through document type configuration. No custom backoffice views are needed:
- On the parent document type (e.g., "News List"), enable "List View" in the Structure tab
- Configure columns, sort order, and page size
- V17 automatically renders a sortable, filterable list in the backoffice
Action¶
- Verify that the News and Testimonials parent document types have "List View" enabled
- Configure the list view columns to match what editors had in v8 (title, date, status)
- Remove any references to the custom
ListViewNews/ListViewTestimonalsAngular views from the v17 codebase -- they are v8-only and will not work in v17's Lit-based backoffice - Show editors the new list view and confirm it meets their needs
Appendix: Investigation Methodology¶
All findings in this report were verified through one or more of:
- Direct SQL queries against the v8 database (
progresscu.ieoncb-sql.double.pt,7071) - File system inspection of both v8 (
psCreditUnion/) and v17 (dbl.Progress/) codebases - Umbraco v17 source code analysis for expected formats and behaviors
- Cross-reference with existing reports (30, 31) and the migration tool source code
No finding is based on assumption. Where a finding requires verification (e.g., "check if CSS files exist"), the verification step is explicitly listed as an action item.