Policy Tracker Fix Guide
One page, every confirmed fix kept in it, oldest first. Each entry has the exact control/property, the replacement formula, and a test to confirm it worked. Superseded by nothing — this page keeps growing instead of being replaced fix-by-fix.
What This Page Is
This is the working fix log for the Policy Tracker canvas app (repo PhadeDev/policy-tracker). Reset to empty on 2026-09-02 — the previous entries (publish/consultation gate fixes, the deadline-banner flash fix, and the Thursday Batch email-drafting feature with its zero-release-week and link fixes) had all been confirmed applied in Studio and were no longer needed as a reference. Same pattern as the previous reset on 2026-08-13.
Fix 1 - Next-Thursday Date Calc Consolidated Into One UDF
Added 2026-09-02. The exact same "which future Thursday" formula, DateAdd(Today(), Mod(5 - Weekday(Today()) + 7, 7) [+ (Value - 1) * 7], "Days"), is hand-typed five separate times across three screens: once in Main (the "+ New Entry" button), once in Main's dashboard gallery (the "Current" tab case), twice in ViewItem (the default publish date, and the 104-week Thursday-picker list), and once in Overview's 4-week gallery. Same duplication problem as Fix 2, different formula. Consolidated into one App Formulas function, NextThursday(WeeksAhead: Number): Date — call it with 1 for "the next Thursday" or a gallery's Value (1, 2, 3…) for "the Nth future Thursday."
Main's Historic tab case calculates days since the previous Thursday (past-facing, for grouping already-released items) using a different sign convention entirely. ViewItem's varDaysToNextThursday (used for the screen's 52-week forward Thursday list) explicitly forces today to count as "7 days away" instead of "0" when today is a Thursday — i.e. it always skips today, where NextThursday(1) below does not. If you want NextThursday to also cover that case, it needs a second parameter or function; not built here since that would be a real behaviour change, not just a refactor, and wasn't asked for.
yaml/ mirror (2026-09-02) rather than a fresh Studio export; if you've changed any of these four properties directly in Studio since then without it reaching the mirror, a full paste-over will lose that change — skim each block once before pasting if you're not sure.
- Open the App object (root of the tree view) →
Formulasproperty (Advanced pane). - Add the function below to the end of the existing Formulas block.
NextThursday(WeeksAhead: Number): Date = With(
{_d: DateAdd(Today(), Mod(5 - Weekday(Today()) + 7, 7) + (WeeksAhead - 1) * 7, "Days")},
Date(Year(_d), Month(_d), Day(_d))
)
DateAdd(...) with no With/Date() wrapper), replace it with this v2 version now — see the confirmed root cause in the Open Issue section at the bottom of this fix before doing anything else with NextThursday.
- Open Main. Select Button1_9 (the "+ New Entry" button, top-left header). Select
OnSelect, select all, delete, and paste this complete replacement:
Set(varSelectedItem, Blank());
Set(varSelectedRelease, Blank());
Set(varSelectPolicy, "Empty");
Set(varOwnerDefault, Table({DisplayName: Office365Users.MyProfileV2().displayName, Mail: User().Email}));
Set(varPlannedPublishDate, NextThursday(1));
Clear(colLinks);
Navigate(ViewItem, ScreenTransition.Fade);
Set(varParentSaved, false);
- Still in Main, select HeaderGallery (the dashboard's date-grouped release list). Select
Items, select all, delete, and paste this complete replacement — only the"Current"case changed, the"Historic"/"Future"/"UnapprovedHistoric"cases are copied through byte-for-byte:
// HeaderGallery.Items — full replacement
Switch(
varSelectedState,
"Historic",
Sort(
AddColumns(
AddColumns(
ForAll(
Sequence(4),
With(
{
_daysSincePreviousThursday:
If(
Mod(Weekday(Today()) - 5 + 7, 7) = 0,
7,
Mod(Weekday(Today()) - 5 + 7, 7)
)
},
{PlannedDate: DateAdd(Today(), -_daysSincePreviousThursday - ((Value - 1) * 7), "Days")}
)
),
ReleaseDateText,
Text(PlannedDate, "dddd, dd mmmm yyyy"),
GroupedItems,
With(
{_targetDate: PlannedDate},
Filter(
'Policy Proof Tracker',
'Planned Publish Date' = _targetDate &&
(Published = true || 'External consultation required' = true) &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
)
)
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Descending
),
"Future",
Sort(
AddColumns(
GroupBy(
AddColumns(
Filter(
'Policy Proof Tracker',
'Planned Publish Date' > DateAdd(Today(), 28, "Days") &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
),
PlannedDate, 'Planned Publish Date',
ReleaseDateText, Text('Planned Publish Date', "dddd, dd mmmm yyyy")
),
PlannedDate,
GroupedItems
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Ascending
),
"Current",
Sort(
AddColumns(
AddColumns(
ForAll(
Sequence(4),
{PlannedDate: NextThursday(Value)}
),
ReleaseDateText,
Text(PlannedDate, "dddd, dd mmmm yyyy"),
GroupedItems,
With(
{_targetDate: PlannedDate},
Filter(
colActiveItems,
'Planned Publish Date' = _targetDate &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
)
)
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Ascending
),
"UnapprovedHistoric",
Sort(
AddColumns(
GroupBy(
AddColumns(
Filter(
'Policy Proof Tracker',
'Planned Publish Date' < Today() &&
Published = false &&
'External consultation required' = false &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
),
PlannedDate, 'Planned Publish Date',
ReleaseDateText, Text('Planned Publish Date', "dddd, dd mmmm yyyy")
),
PlannedDate,
GroupedItems
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Descending
)
)
- Open ViewItem, select the screen itself, then
OnVisible. Select all, delete, and paste this complete replacement — two lines changed (thevarPlannedPublishDatedefault, and thecolThursdayOptionsbuild further down), everything else is copied through byte-for-byte, including the deep-link handling and all theReset()calls:
Set(varLoadingLinks, varLinksSourceID <> varSelectedRelease.ID || IsBlank(varSelectedRelease.ID));
Set(varClosingViewItem, false);
Set(varSaving, false);
Set(varShowLinkModal, false);
Set(varSelectedLink, Blank());
Set(varErrTitle, false);
Set(varErrOwner, false);
Set(varErrDocType, false);
Set(varErrPublishDate, false);
Set(
varOwnerDefault,
If(
IsBlank(varSelectedRelease) || IsBlank(varSelectedRelease.Owner.Email),
Table({DisplayName: User().FullName, Mail: User().Email}),
Table({DisplayName: varSelectedRelease.Owner.DisplayName, Mail: varSelectedRelease.Owner.Email})
)
);
Set(
varPlannedPublishDate,
If(
IsBlank(varSelectedRelease) || IsBlank(varSelectedRelease.'Planned Publish Date'),
NextThursday(1),
varSelectedRelease.'Planned Publish Date'
)
);
If(
!varDeepLinkHandled && !IsBlank(Param("ID")),
Set(varDeepLinkHandled, true);
Set(varItemID, Param("ID"));
Set(varLoading, true);
Refresh('Policy Proof Tracker');
Set(varDeepLinkItem, LookUp('Policy Proof Tracker', ID = Value(varItemID)));
If(
!IsBlank(varDeepLinkItem),
Set(varSelectedRelease, varDeepLinkItem);
Set(varSelectPolicy, "Filled");
Set(varParentSaved, true);
Set(varSelectedItem, varDeepLinkItem);
Set(varSourceScreen, "Main");
Set(varSelectedPerson, varDeepLinkItem.Owner);
Refresh(PolicyLinks);
BuildLinksForSource(varDeepLinkItem.ID);
Set(varOwnerDefault, Table({DisplayName: varDeepLinkItem.Owner.DisplayName, Mail: varDeepLinkItem.Owner.Email}));
Set(varPlannedPublishDate, varDeepLinkItem.'Planned Publish Date'),
Notify("This item could not be found. It may have been deleted or the link may be incorrect.", NotificationType.Error);
Set(varDeepLinkNotFound, true)
);
Set(varLoading, false)
);
Reset(txtTitle1);
Reset(drpDocType1);
Reset(dpkPublishDate1);
Reset(cmb_Owner1);
Reset(ckbHighPriority1);
Reset(txtReasonHighPri1);
Reset(ckbExtConsult1);
Reset(txtExtConsultDetails1);
Reset(ckbTopItem1);
Reset(txtNewsletterTitle1);
Reset(txtAudience1);
Reset(txtSummary1);
Reset(txtRemarks1);
Reset(ckbPillarLead1);
Reset(radApproval1);
Reset(ckbPublished1);
Reset(dpkPublishedDate1);
Set(varShowThursdayPicker, false);
ClearCollect(
colThursdayItemCounts,
AddColumns(
GroupBy(
AddColumns(
Filter(
'Policy Proof Tracker',
'Planned Publish Date' >= Today() &&
'Planned Publish Date' <= DateAdd(Today(), 104 * 7, "Days")
),
GroupDate, 'Planned Publish Date'
),
GroupDate,
GroupedItems
),
DayItemCount,
CountRows(GroupedItems)
)
);
ClearCollect(
colThursdayOptions,
ForAll(
Sequence(104),
With(
{_thu: NextThursday(Value)},
{
ThursdayDate: _thu,
ItemCount: Coalesce(LookUp(colThursdayItemCounts, GroupDate = _thu, DayItemCount), 0)
}
)
)
);
If(
!IsBlank(varSelectedRelease.ID),
If(
!IsBlank(LookUp(colActiveItems, ID = varSelectedRelease.ID)),
ClearCollect(colLinks, Filter(colActiveLinks, PolicyTrackerSource.Id = varSelectedRelease.ID));
Set(varLinksSourceID, varSelectedRelease.ID),
If(
varLinksSourceID <> varSelectedRelease.ID,
BuildLinksForSource(varSelectedRelease.ID)
)
);
Set(varLoadingLinks, false),
Clear(colLinks);
Set(varLoadingLinks, false)
);
- Open Overview, select galOverviewColumns1. Select
Items, select all, delete, and paste this complete replacement:
AddColumns(
ForAll(
Sequence(4),
{
PublishDate: NextThursday(Value)
}
),
ItemsForDate,
With(
{_targetDate: PublishDate},
Filter(
'Policy Proof Tracker',
Year('Planned Publish Date') = Year(_targetDate) &&
Month('Planned Publish Date') = Month(_targetDate) &&
Day('Planned Publish Date') = Day(_targetDate)
)
)
)
Open Issue — ROOT CAUSE CONFIRMED, fix above, re-verify before reapplying (2026-09-02)
After pasting the original HeaderGallery.Items block, all 4 rows on Main's "Due Next 4 Weeks" dashboard showed 0 items and blank date labels, even though the top pill still correctly showed 13 due items (the underlying data was fine). Diagnosed via the debug label below: NextThursday(1) and the equivalent inline DateAdd(...) formula printed identical text ("03 Sep 2026 00:00:00") but NextThursday(1) = DateAdd(...) returned false. That rules out a calculation error — the two values are numerically/visually the same but not the same underlying Power Fx type, so every = _targetDate equality check against 'Planned Publish Date' silently matched nothing. A UDF's explicit : Date return type can hand back a value that isn't type-identical to a bare inline expression even when it displays the same. Fixed by rebuilding the value through Date(Year(_d), Month(_d), Day(_d)) inside the function (the v2 App Formula above) — this is a standard way to force a clean, canonical Date value in Power Fx regardless of what produced the input.
Step 1 — roll back to restore Main immediately
Select HeaderGallery, Items, select all, delete, paste this — identical to the block above except the "Current" case's PlannedDate is back to the original inline formula:
// HeaderGallery.Items — full replacement
Switch(
varSelectedState,
"Historic",
Sort(
AddColumns(
AddColumns(
ForAll(
Sequence(4),
With(
{
_daysSincePreviousThursday:
If(
Mod(Weekday(Today()) - 5 + 7, 7) = 0,
7,
Mod(Weekday(Today()) - 5 + 7, 7)
)
},
{PlannedDate: DateAdd(Today(), -_daysSincePreviousThursday - ((Value - 1) * 7), "Days")}
)
),
ReleaseDateText,
Text(PlannedDate, "dddd, dd mmmm yyyy"),
GroupedItems,
With(
{_targetDate: PlannedDate},
Filter(
'Policy Proof Tracker',
'Planned Publish Date' = _targetDate &&
(Published = true || 'External consultation required' = true) &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
)
)
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Descending
),
"Future",
Sort(
AddColumns(
GroupBy(
AddColumns(
Filter(
'Policy Proof Tracker',
'Planned Publish Date' > DateAdd(Today(), 28, "Days") &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
),
PlannedDate, 'Planned Publish Date',
ReleaseDateText, Text('Planned Publish Date', "dddd, dd mmmm yyyy")
),
PlannedDate,
GroupedItems
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Ascending
),
"Current",
Sort(
AddColumns(
AddColumns(
ForAll(
Sequence(4),
{PlannedDate: DateAdd(Today(), Mod(5 - Weekday(Today()) + 7, 7) + (Value - 1) * 7, "Days")}
),
ReleaseDateText,
Text(PlannedDate, "dddd, dd mmmm yyyy"),
GroupedItems,
With(
{_targetDate: PlannedDate},
Filter(
colActiveItems,
'Planned Publish Date' = _targetDate &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
)
)
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Ascending
),
"UnapprovedHistoric",
Sort(
AddColumns(
GroupBy(
AddColumns(
Filter(
'Policy Proof Tracker',
'Planned Publish Date' < Today() &&
Published = false &&
'External consultation required' = false &&
(varFilterOwner = false || Owner.Email = User().Email) &&
(IsBlank(varTypeFilter) || varTypeFilter = "" || 'Document Type'.Value = varTypeFilter)
),
PlannedDate, 'Planned Publish Date',
ReleaseDateText, Text('Planned Publish Date', "dddd, dd mmmm yyyy")
),
PlannedDate,
GroupedItems
),
ItemCount, CountRows(GroupedItems)
),
PlannedDate,
SortOrder.Descending
)
)
Step 2 — diagnose with a temporary label (once Main is confirmed working again)
Add a temporary Label anywhere on Main, set AutoHeight to true, and set its Text to this formula. This does not change any data.
"Today = " & Text(Today(), "dd mmm yyyy") & Char(10) & "NextThursday(1) = " & Text(NextThursday(1), "dd mmm yyyy hh:mm:ss") & Char(10) & "Inline formula = " & Text(DateAdd(Today(), Mod(5 - Weekday(Today()) + 7, 7), "Days"), "dd mmm yyyy hh:mm:ss") & Char(10) & "Are they equal? = " & Text(NextThursday(1) = DateAdd(Today(), Mod(5 - Weekday(Today()) + 7, 7), "Days"))
03 Sep 2026 00:00:00, NextThursday(1) also printed the identical text, but "Are they equal?" was false — confirming the type-mismatch root cause above, not a calculation error.
Step 3 — apply the corrected function, then re-verify with the SAME label before touching any screen
- Go to App → Formulas and replace
NextThursdaywith the v2 version at the top of this fix (the one usingWith/Date(Year, Month, Day)). - Leave the temporary debug label from Step 2 in place and don't change it — just look at it again.
- "Are they equal?" must now say
truebefore doing anything else. If it still saysfalse, stop and report back exactly what all four lines say — don't reapply to HeaderGallery/ViewItem/Overview yet. - Once it says
true: reapply the correctedHeaderGallery.Itemsblock from Step 4 above (the one usingNextThursday(Value), not the rollback), then continue with the remaining ViewItem/Overview steps in this fix as originally written. - Delete the temporary debug label once everything is confirmed working.