Updated 10 Sep 2026 Policy Tracker only 5 fixes

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.

Need to paste a whole screen instead of a small fix? That lives on a separate site: Policy Tracker — Screen YAML →

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.

Each fix below is a targeted property edit on the live app, applied by hand in Power Apps Studio. None of them require a full screen repaste unless stated otherwise.

Fix 1 - Next-Thursday Date Calc Consolidated Into One UDF

App Formulas + Main + ViewItem + Overview · structural refactor · no behaviour change on the 5 consolidated spots

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."

Two similar-looking formulas found and deliberately left untouched — these are not duplicates of this one, they do genuinely different things: 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.
Every code block below is the COMPLETE property — select all in Studio's formula box, delete, paste. No hunting for a specific line. These are built from the last synced 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.
  1. Open the App object (root of the tree view) → Formulas property (Advanced pane).
  2. Add the function below to the end of the existing Formulas block.
App · Formulas — new function v2 · 2026-09-02 BST — corrected, see Open Issue below
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))
)
If you already added the v1 version (a single-line 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.
  1. Open Main. Select Button1_9 (the "+ New Entry" button, top-left header). Select OnSelect, select all, delete, and paste this complete replacement:
Button1_9.OnSelect — complete property, select all & replace v1 · 2026-09-02 BST
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);
OPEN ISSUE, reported 2026-09-02, not yet resolved: pasting the block below broke Main live — all 4 dashboard rows showed "0 items"/"No releases went out that week" and the date labels went blank. Root cause not yet confirmed. If you haven't pasted this yet, skip straight to the "Open Issue" section at the bottom of this fix before continuing — it has a rollback and a diagnostic label to run first.
  1. 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 — complete property, select all & replace v1 · 2026-09-02 BST
// 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
    )
)
  1. Open ViewItem, select the screen itself, then OnVisible. Select all, delete, and paste this complete replacement — two lines changed (the varPlannedPublishDate default, and the colThursdayOptions build further down), everything else is copied through byte-for-byte, including the deep-link handling and all the Reset() calls:
ViewItem.OnVisible — complete property, select all & replace v1 · 2026-09-02 BST
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)
);
Do not touch the "Historic" case above it in Main's HeaderGallery — that's the different, past-facing formula described in the warning above, not a duplicate (it's already unchanged in the complete block you just pasted).
  1. Open Overview, select galOverviewColumns1. Select Items, select all, delete, and paste this complete replacement:
galOverviewColumns1.Items — complete property, select all & replace v1 · 2026-09-02 BST
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)
        )
    )
)
Test: full Preview close/reopen after adding the App Formula (same rule as always for Formulas/OnStart changes). Then check each of the four spots does what it always did — "+ New Entry" still defaults to the same date it used to, Main's dashboard and Overview's 4-week grid still show the same weeks in the same order, ViewItem's Thursday picker still lists 104 weeks starting from the same date. Nothing should look different; only the formula source changed.

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 — ROLLBACK, complete property v2 · 2026-09-02 BST
// 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
    )
)
Paste that in and Main should come back immediately — this is a screen property, no Preview restart needed.

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.

Temporary debug label - Text v1 · 2026-09-02 BST
"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"))
Confirmed 2026-09-02: "Today" and "Inline formula" both printed 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

  1. Go to App → Formulas and replace NextThursday with the v2 version at the top of this fix (the one using With/Date(Year, Month, Day)).
  2. Leave the temporary debug label from Step 2 in place and don't change it — just look at it again.
  3. "Are they equal?" must now say true before doing anything else. If it still says false, stop and report back exactly what all four lines say — don't reapply to HeaderGallery/ViewItem/Overview yet.
  4. Once it says true: reapply the corrected HeaderGallery.Items block from Step 4 above (the one using NextThursday(Value), not the rollback), then continue with the remaining ViewItem/Overview steps in this fix as originally written.
  5. Delete the temporary debug label once everything is confirmed working.

Fix 2 - NewsletterPack Refresh Logic Moved Into One UDF

App Formulas + NewsletterPack + btnRefresh_1 · structural refactor · no behaviour change, removes a recurring bug class

Apply Fix 1 first — this fix's RefreshNewsletterPack() calls NextThursday(1), which only exists once Fix 1's App Formula has been added.

Added 2026-09-02. Before this reset, every single newsletter-related fix on this screen carried the same warning: NewsletterPack.OnVisible and btnRefresh_1.OnSelect held two separate, hand-copied pastes of the exact same formula, and forgetting to update both is what caused two real bugs before this reset (a fix applied to one copy but not the other). This fix removes the duplication at the source instead of continuing to patch both copies every time: the whole block (target-date calc, colNewsletterPack build, batch-group/membership collections, subject and body variables) moves into one new function, RefreshNewsletterPack(), added to App → Formulas — the same place GetFileExt/BuildLinksForSource/RefreshActiveCache already live, so this is the established pattern for this app, not a new one. Both triggers become a single-line call. Nothing about what the app does changes — this only changes where the logic lives.

Bigger than the usual one-line fix: this touches App Formulas, which (like OnStart) only takes effect after a full Preview close/reopen, not a Refresh click. Apply this in a quiet moment, not mid-Thursday-send, and do a full close/reopen before testing.
  1. Open the App object (root of the tree view) → Formulas property (Advanced pane).
  2. Add the function below to the end of the existing Formulas block — don't remove GetFileExt, BuildLinksForSource, or RefreshActiveCache.
App · Formulas — new function v1 · 2026-09-02 BST
RefreshNewsletterPack(): Boolean = {
    Set(varNewsletterTargetDate, NextThursday(1));
    Refresh('Policy Proof Tracker');
    ClearCollect(
        colNewsletterPack,
        Sort(
            AddColumns(
                Filter(
                    'Policy Proof Tracker',
                    'Planned Publish Date' >= varNewsletterTargetDate &&
                    'Planned Publish Date' < DateAdd(varNewsletterTargetDate, 1, "Days") &&
                    (
                        'Approved For Release By'.Value = "XO" ||
                        'Approved For Release By'.Value = "ACOS" ||
                        'Approved For Release By'.Value = "DCOMD" ||
                        'Approved For Release By'.Value = "D Commander"
                    ) &&
                    Published = false &&
                    Coalesce('External consultation required', false) = false
                ),
                SortKey,
                If(
                    'Top Item',
                    -1000,
                    Coalesce(LookUp(colChoiceColors, ChoiceValue = 'Document Type'.Value, SortOrder), 99)
                ),
                IsFinalApproved,
                Or(
                    'Approved For Release By'.Value = "XO",
                    'Approved For Release By'.Value = "ACOS",
                    'Approved For Release By'.Value = "DCOMD",
                    'Approved For Release By'.Value = "D Commander"
                ),
                PackTitle,
                Coalesce('Title for Newsletter', Title),
                PackAudience,
                Coalesce(Audience, ""),
                PackSummary,
                Coalesce('Item summary', "")
            ),
            SortKey,
            SortOrder.Ascending
        )
    );
    ClearCollect(colBatchGroups, AddColumns(Sort('BM Groups', SortOrder, SortOrder.Ascending), Ticked, false));
    ClearCollect(colBatchMemberships, Filter('BM Memberships', false));
    ForAll(colBatchGroups As grp, Collect(colBatchMemberships, Filter('BM Memberships', GroupName = grp.Title)));
    Set(varNewsletterSubject, Text(Today(), "yyyymmdd") & " - Cadets Branch weekly policy updates");
    Set(
        varNewsletterBodyLead,
        "Good afternoon all,<br><br>" &
        "Please find a link to this week's releases here - <a href=""https://www.google.com"">Cadets Branch Weekly Policy Updates - " & Text(varNewsletterTargetDate, "dd mmm yyyy") & "</a><br>" &
        "Below is a summary of this week's releases from Cadets Branch:<br><br>"
    );
    Set(
        varNewsletterBodyEmpty,
        "Good afternoon all,<br><br>" &
        "There are no policy releases this week.<br><br>"
    );
    Set(
        varNewsletterBodyStatic,
        "<br><b>Previous Newsletters:</b><br>" &
        "All the previous weekly policy updates are available via the links below<br>" &
        "MODNet users: <a href=""https://modgovuk.sharepoint.com/teams/1598/SitePages/Weekly-Policy-Updates---Archive.aspx"">Weekly Policy Updates (sharepoint.com)</a><br>" &
        "Resource Centre users: Navigate to: Westminster → Resource Centre → Organisation → Cadets Branch Weekly Policy Updates<br><br>" &
        "<b>About the Newsletter:</b><br>" &
        "This newsletter works on both MODNet and personal systems, and all policies included will also be uploaded on to both MODNet and the Resource Centre for routine accessing.<br>" &
        "Each week we will send out a version of this email, to the usual recipients, providing a link to the latest policy newsletter and a summary of what items have been included.<br>" &
        "This email has been sent to the groups listed below but please feel free to forward on as you deem necessary, emailed recipients:<br><br>" &
        "ACF CEOs (ex-HQ SE)<br>" &
        "JMC SO2's<br>" &
        "JMC OC CTT's<br>" &
        "---<br>" &
        "JMC Cadets Branch 0mailboxes<br>" &
        "JMC CTT 0mailboxes<br>" &
        "JMC Col Cadets (including Col Ashley Fulford)<br>" &
        "JMC RAL members<br>" &
        "JMC SCEOs<br>" &
        "Cadets Branch<br>" &
        "CTC Frimley<br>" &
        "CCAT<br>" &
        "CRFCA (Sam Plant and Simon Estick)<br>" &
        "ACCT UK (Murdo Urquhart and Richard Walton)<br>" &
        "CCRS (Shooting Manager - Daf Marston)<br>" &
        "CCF RAF &amp; RN leads"
    );
    true
}
  1. Open NewsletterPack, select the screen itself (root node), then OnVisible. Select all, delete, and replace with just RefreshNewsletterPack();
  2. Select btnRefresh_1, OnSelect. Select all, delete, and replace with the identical single line, RefreshNewsletterPack();
  3. Leave btnDraftBatch1_1/btnDraftBatch2_1 exactly as they are — they already just read varNewsletterSubject/varNewsletterBodyLead/varNewsletterBodyEmpty/varNewsletterBodyStatic, which the UDF still sets, so nothing there needs to change.
Test: full Preview close/reopen first. Then confirm NewsletterPack loads exactly as before (same items, same count), click Refresh and confirm it still works, then click both Thursday Batch buttons on a normal week and (if you can fake it) a zero-release week and confirm both draft correctly — behaviour should be identical to before this fix, only the plumbing changed.
Going forward: if NewsletterPack's rules ever need to change again (a new approval role, a different date window, a new footer line), there is now exactly one place to edit — RefreshNewsletterPack() in App Formulas — instead of two screens to remember. This is the pattern worth reusing for Main/ViewItem/Overview's own duplicated "next Thursday" calculations too, offered as a separate follow-on fix rather than bundled into this one.

Fix 3 - Thursday Batch Buttons Fail If Clicked Before NewsletterPack Finishes Loading

App Formulas + NewsletterPack · adds a readiness flag · fixes a real intermittent failure, no other behaviour change

Reported 2026-09-03: a user clicked "Thursday Batch 1 Email" on landing on the screen and got Office365Outlook.DraftEmail failed: ... invalid value for parameter 'Subject' - a blank value was passed to it. Clicking again a short while later worked fine, both batches. Root cause: RefreshNewsletterPack() does several ClearCollect/ForAll steps before it reaches Set(varNewsletterSubject, ...). If either Thursday Batch button is clicked while OnVisible is still partway through that sequence, varNewsletterSubject (and the body variables) are still blank — a genuine race condition, not bad luck, and worse on a slower connection or a data source with more rows to refresh. Same pattern this app already uses on StartScreen (varActiveCacheReady): add a readiness flag, set it false at the start of the refresh and true at the end, and gate both buttons on it.

  1. Open App → Formulas. Select all of RefreshNewsletterPack only (leave GetFileExt/BuildLinksForSource/RefreshActiveCache/NextThursday untouched) and replace it with this complete version — two new lines added, everything else unchanged:
App · Formulas — RefreshNewsletterPack, complete replacement v3 · 2026-09-03 BST
RefreshNewsletterPack(): Boolean = {
    Set(varNewsletterReady, false);
    Set(varNewsletterTargetDate, NextThursday(1));
    Refresh('Policy Proof Tracker');
    ClearCollect(
        colNewsletterPack,
        Sort(
            AddColumns(
                Filter(
                    'Policy Proof Tracker',
                    'Planned Publish Date' >= varNewsletterTargetDate &&
                    'Planned Publish Date' < DateAdd(varNewsletterTargetDate, 1, "Days") &&
                    (
                        'Approved For Release By'.Value = "XO" ||
                        'Approved For Release By'.Value = "ACOS" ||
                        'Approved For Release By'.Value = "DCOMD" ||
                        'Approved For Release By'.Value = "D Commander"
                    ) &&
                    Published = false &&
                    Coalesce('External consultation required', false) = false
                ),
                SortKey,
                If(
                    'Top Item',
                    -1000,
                    Coalesce(LookUp(colChoiceColors, ChoiceValue = 'Document Type'.Value, SortOrder), 99)
                ),
                IsFinalApproved,
                Or(
                    'Approved For Release By'.Value = "XO",
                    'Approved For Release By'.Value = "ACOS",
                    'Approved For Release By'.Value = "DCOMD",
                    'Approved For Release By'.Value = "D Commander"
                ),
                PackTitle,
                Coalesce('Title for Newsletter', Title),
                PackAudience,
                Coalesce(Audience, ""),
                PackSummary,
                Coalesce('Item summary', "")
            ),
            SortKey,
            SortOrder.Ascending
        )
    );
    ClearCollect(colBatchGroups, AddColumns(Sort('BM Groups', SortOrder, SortOrder.Ascending), Ticked, false));
    ClearCollect(colBatchMemberships, Filter('BM Memberships', false));
    ForAll(colBatchGroups As grp, Collect(colBatchMemberships, Filter('BM Memberships', GroupName = grp.Title)));
    Set(varNewsletterSubject, Text(Today(), "yyyymmdd") & " - Cadets Branch weekly policy updates");
    Set(
        varNewsletterBodyLead,
        "Good afternoon all,<br><br>" &
        "Please find a link to this week's releases here - <a href=""https://www.google.com"">Cadets Branch Weekly Policy Updates - " & Text(varNewsletterTargetDate, "dd mmm yyyy") & "</a><br>" &
        "Below is a summary of this week's releases from Cadets Branch:<br><br>"
    );
    Set(
        varNewsletterBodyEmpty,
        "Good afternoon all,<br><br>" &
        "There are no policy releases this week.<br><br>"
    );
    Set(
        varNewsletterBodyStatic,
        "<br><b>Previous Newsletters:</b><br>" &
        "All the previous weekly policy updates are available via the links below<br>" &
        "MODNet users: <a href=""https://modgovuk.sharepoint.com/teams/1598/SitePages/Weekly-Policy-Updates---Archive.aspx"">Weekly Policy Updates (sharepoint.com)</a><br>" &
        "Resource Centre users: Navigate to: Westminster → Resource Centre → Organisation → Cadets Branch Weekly Policy Updates<br><br>" &
        "<b>About the Newsletter:</b><br>" &
        "This newsletter works on both MODNet and personal systems, and all policies included will also be uploaded on to both MODNet and the Resource Centre for routine accessing.<br>" &
        "Each week we will send out a version of this email, to the usual recipients, providing a link to the latest policy newsletter and a summary of what items have been included.<br>" &
        "This email has been sent to the groups listed below but please feel free to forward on as you deem necessary, emailed recipients:<br><br>" &
        "ACF CEOs (ex-HQ SE)<br>" &
        "JMC SO2's<br>" &
        "JMC OC CTT's<br>" &
        "---<br>" &
        "JMC Cadets Branch 0mailboxes<br>" &
        "JMC CTT 0mailboxes<br>" &
        "JMC Col Cadets (including Col Ashley Fulford)<br>" &
        "JMC RAL members<br>" &
        "JMC SCEOs<br>" &
        "Cadets Branch<br>" &
        "CTC Frimley<br>" &
        "CCAT<br>" &
        "CRFCA (Sam Plant and Simon Estick)<br>" &
        "ACCT UK (Murdo Urquhart and Richard Walton)<br>" &
        "CCRS (Shooting Manager - Daf Marston)<br>" &
        "CCF RAF &amp; RN leads"
    );
    Set(varNewsletterReady, true);
    true
}
  1. Select btnDraftBatch1_1. Select DisplayMode, select all, and replace with:
btnDraftBatch1_1.DisplayMode — complete property v1 · 2026-09-03 BST
If(Coalesce(varNewsletterReady, false), DisplayMode.Edit, DisplayMode.Disabled)
  1. Still on btnDraftBatch1_1, select OnSelect, select all, and replace with this complete version — one new guard added at the top, the rest is unchanged:
btnDraftBatch1_1.OnSelect — complete property v4 · 2026-09-03 BST
If(
    !Coalesce(varNewsletterReady, false) || IsBlank(varNewsletterSubject),
    Notify("Newsletter data is still loading - wait a moment and try again.", NotificationType.Warning),
    Set(varBatch1Recipients, Distinct(Filter(colBatchMemberships As mem, CountRows(Filter(colBatchGroups As grp, Lower(Trim(grp.ThursdayBatch.Value)) = "batch 1" && Lower(Trim(grp.Title)) = Lower(Trim(mem.GroupName)))) > 0), Email));
    Set(varBatch1Bcc, Concat(varBatch1Recipients, Value, "; "));
    If(
        CountRows(varBatch1Recipients) = 0,
        Notify("No recipients found for Thursday Batch 1.", NotificationType.Warning),
        Office365Outlook.DraftEmail(
            "",
            varNewsletterSubject,
            If(
                CountRows(colNewsletterPack) = 0,
                varNewsletterBodyEmpty,
                varNewsletterBodyLead & Concat(colNewsletterPack, "• <b>" & PackTitle & "</b><br>&nbsp;&nbsp;&nbsp;&nbsp;◦ Intended audience: " & PackAudience & "<br><br>")
            ) & varNewsletterBodyStatic,
            {Bcc: varBatch1Bcc}
        );
        Notify("Thursday Batch 1 draft created - check Outlook Drafts.", NotificationType.Success)
    )
)
  1. Select btnDraftBatch2_1. Select DisplayMode, select all, and replace with the identical formula as step 2 above (If(Coalesce(varNewsletterReady, false), DisplayMode.Edit, DisplayMode.Disabled)).
  2. Still on btnDraftBatch2_1, select OnSelect, select all, and replace with this complete version:
btnDraftBatch2_1.OnSelect — complete property v4 · 2026-09-03 BST
If(
    !Coalesce(varNewsletterReady, false) || IsBlank(varNewsletterSubject),
    Notify("Newsletter data is still loading - wait a moment and try again.", NotificationType.Warning),
    Set(varBatch2Recipients, Distinct(Filter(colBatchMemberships As mem, CountRows(Filter(colBatchGroups As grp, Lower(Trim(grp.ThursdayBatch.Value)) = "batch 2" && Lower(Trim(grp.Title)) = Lower(Trim(mem.GroupName)))) > 0), Email));
    Set(varBatch2Bcc, Concat(varBatch2Recipients, Value, "; "));
    If(
        CountRows(varBatch2Recipients) = 0,
        Notify("No recipients found for Thursday Batch 2.", NotificationType.Warning),
        Office365Outlook.DraftEmail(
            "",
            varNewsletterSubject,
            If(
                CountRows(colNewsletterPack) = 0,
                varNewsletterBodyEmpty,
                varNewsletterBodyLead & Concat(colNewsletterPack, "• <b>" & PackTitle & "</b><br>&nbsp;&nbsp;&nbsp;&nbsp;◦ Intended audience: " & PackAudience & "<br><br>")
            ) & varNewsletterBodyStatic,
            {Bcc: varBatch2Bcc}
        );
        Notify("Thursday Batch 2 draft created - check Outlook Drafts.", NotificationType.Success)
    )
)
Note for the "invalid value" error message itself: Studio may still show a red "1 of 1 problem" mark on the formula bar referencing the old error after you paste the fix — if so, this app has a known stale-validation quirk (see Known Bad Patterns → Studio Editor Quirks): click to the end of the formula, delete the last character, retype it, and the phantom error clears.
Test: full Preview close/reopen after the App Formulas change. Open NewsletterPack and try clicking a Thursday Batch button as fast as possible right after the screen appears — it should now either be visibly disabled (greyed out) for a moment, or show the "still loading" warning if clicked in that split second, never the raw Office365Outlook error. Once loaded, both buttons should work exactly as before.

Fix 5 - Log Newsletter Release To The Weekly Updates Archive List

NewsletterPack · new SharePoint data source + new button · requires Fix 1-4 applied first

Added 2026-09-10. Adds a button next to the release link field — click it once the newsletter's ready and it logs this week's release straight into the Weekly Policy Updates Archive SharePoint list (the same list already linked in the batch emails' "Previous Newsletters" section), instead of you adding that row by hand.

You need to fill in three column names before pasting the last block below. Open the Weekly Policy Updates Archive list in SharePoint and note the exact column header text for the date column, the URL column, and the description column. Wherever you see <<DATE COLUMN>>, <<URL COLUMN>>, <<DESCRIPTION COLUMN>> below, replace it with the real header text (single-quoted if it contains a space, same style as 'Planned Publish Date' elsewhere in this app).
What it sends: Date — this Thursday's target date (varNewsletterTargetDate, same value used in the subject line). URL — whatever's typed in txtReleaseLink. Description — one bullet per qualifying item's title only, no audience line (colNewsletterPack's PackTitle, same source as the email body, just without the "Intended audience" sub-line). The button is blocked (with a warning, not a silent no-op) if there are no qualifying items, or if the release link is still blank or the inert # fallback.
  1. In Studio, go to Data (left rail) → Add data → SharePoint. If you already have a connection to the modgovuk.sharepoint.com/teams/1598 site (the same one Policy Proof Tracker uses), pick it from your existing connections rather than adding a new one. Tick Weekly Policy Updates Archive and add it.
  2. Open NewsletterPack. In the "Email-ready format" panel, add a new Button control (Insert → Button). Rename it btnLogToArchive_1 (right-click → Rename in the tree view). Position it directly under txtReleaseLink, full width — if it overlaps the Thursday Batch buttons or the preview gallery below, just drag those down a little, position isn't functionally important.
btnLogToArchive_1 — new control properties v1 · 2026-09-10 BST
Text: ="Log to Weekly Updates Archive"
X: =20
Y: =61
Width: =Parent.Width - 40
Height: =36
DisplayMode: =If(Coalesce(varNewsletterReady, false), DisplayMode.Edit, DisplayMode.Disabled)
  1. With btnLogToArchive_1 still selected, set OnSelect to this — remember to swap in your three real column names first:
btnLogToArchive_1.OnSelect — complete property v1 · 2026-09-10 BST
If(
    !Coalesce(varNewsletterReady, false) || IsBlank(varNewsletterSubject),
    Notify("Newsletter data is still loading - wait a moment and try again.", NotificationType.Warning),
    CountRows(colNewsletterPack) = 0,
    Notify("No qualifying newsletter items for this Thursday - nothing to log.", NotificationType.Warning),
    Coalesce(Trim(txtReleaseLink.Text), "#") = "#",
    Notify("Add the real release link above before logging this week's release.", NotificationType.Warning),
    Patch(
        'Weekly Policy Updates Archive',
        Defaults('Weekly Policy Updates Archive'),
        {
            <<DATE COLUMN>>: varNewsletterTargetDate,
            <<URL COLUMN>>: { Value: Trim(txtReleaseLink.Text), DisplayText: "Cadets Branch Weekly Policy Updates - " & Text(varNewsletterTargetDate, "dd mmm yyyy") },
            <<DESCRIPTION COLUMN>>: Concat(colNewsletterPack, "• " & PackTitle & Char(10))
        }
    );
    Notify("Added to the Weekly Updates Archive list.", NotificationType.Success)
)
If your URL column is a Hyperlink or Picture type (right-click the column in SharePoint → Edit → check its type), the { Value: ..., DisplayText: ... } record above is required — SharePoint won't accept a plain string. If it's actually a plain Single line of text column instead, replace that whole record with just Trim(txtReleaseLink.Text).
Test: open NewsletterPack, wait for it to finish loading (button goes from greyed-out to coloured), leave the release link blank and click the button — should warn and refuse. Paste a real link, click again — should succeed, and a new row should appear in Weekly Policy Updates Archive with this Thursday's date, your link, and one bullet per qualifying item's title.

Leave a note for Claude — Policy Tracker

Saved into the shared locked storage bucket only Claude can read — not public. Auto-deletes after 14 days.