Skip to content

DynamoDS/DYN-10516: Dynamo Home: refresh Templates and Recent Files from disk dynamically#17150

Closed
eamiri wants to merge 5 commits into
masterfrom
10516-refresh-templates-recent-files
Closed

DynamoDS/DYN-10516: Dynamo Home: refresh Templates and Recent Files from disk dynamically#17150
eamiri wants to merge 5 commits into
masterfrom
10516-refresh-templates-recent-files

Conversation

@eamiri

@eamiri eamiri commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Closes https://autodesk.atlassian.net/browse/DYN-10516

h1. Home — Refresh Templates and Recent Files Implementation Plan

h2. Overview

Make the Dynamo Home screen's Templates and Recent Files lists refresh from disk every time the Home view becomes visible (e.g., after a user closes a workspace), and fix the misleading warning + hang triggered by clicking a tile whose file has been deleted. Pure on-show refresh — no file system watchers, per the agreed scope.

h2. Current State Analysis

h3. Key Discoveries

  • HomePage is created once and toggled via {{Visibility}} bound to {{DynamoViewModel.ShowStartPage}} ({{src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs:1556-1583}}); natural refresh hook is the existing {{HomePage.DynamoViewModel_PropertyChanged}} ({{src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs:127-133}}).
  • {{StartPageViewModel.LoadTemplates()}} ({{StartPage.xaml.cs:226-254}}) does not clear {{templateFiles}} — the refresh method must.
  • {{DynamoViewModel.RecentFiles}} is an ObservableCollection ({{DynamoViewModel.cs:588-595}}); HomePage already subscribes to its {{CollectionChanged}} ({{HomePage.xaml.cs:283-287, 372}}), so mutating it auto-pushes to the WebView. {{TemplateFiles}} lacks such a subscription — add one.
  • {{DynamoViewModel.CanOpen}} ({{DynamoViewModel.cs:2379-2399}}) pops a side-effecting modal dialog with archive-flavored text; from a WebView2 host-object callback that nested pump causes the hang. Pre-validating in the caller skips this path entirely.
  • {{PathHelper.IsValidPath}} ({{src/DynamoUtilities/PathHelper.cs:64-67}}) is the canonical check — reuse it.
  • Legacy {{StartPageView}} is dead-in-production ({{DynamoView.IsNewAppHomeEnabled}} always true at {{DynamoView.xaml.cs:2948-2954}}) but the same bug lives in {{StartPage.HandleFilePath}} — fix in parallel.

h2. Desired End State

  • Closing a workspace and returning to Home re-reads {{PathManager.TemplatesDirectory}}; new templates appear, deleted templates disappear.
  • Same trip prunes {{DynamoViewModel.RecentFiles}} of entries whose file no longer exists; the Home view updates in the same frame via the existing {{CollectionChanged}} push.
  • Clicking a missing-file tile shows "This file no longer exists at the following location and will be removed from your list." (no archive language), removes the offending entry, re-renders Home, and does not hang.
  • Manual repro from the ticket no longer reproduces; new NUnit tests cover the refresh/prune/click paths.

h2. What We're NOT Doing

  • No live {{FileSystemWatcher}} (explicitly out of scope).
  • No general refactor of {{CanOpen}} to remove its side-effecting dialog — scoped fix at the Home-screen callers only.
  • No templates-folder schema/location changes; no sub-folder template support (current code is root-only).
  • No HomePage instantiation re-architecture — it stays toggle-visibility.
  • No JS / DynamoHome frontend changes — reuse existing {{window.receiveTemplatesDataFromDotNet}} / {{window.receiveGraphDataFromDotNet}} channels.
  • No new analytics events.

h2. Implementation Approach

Three small layered changes:

Refresh APIs + resource strings: add {{RefreshTemplates()}} on {{StartPageViewModel}}, {{PruneInvalidRecentFiles()}} + {{TryRemoveRecentFile()}} on {{DynamoViewModel}}, and new "File Not Found" resx entries.

Auto-push wiring: subscribe to {{TemplateFiles.CollectionChanged}} in {{HomePage}} (mirroring the RecentFiles wiring) so collection changes flow to the WebView automatically.

Trigger + click fix: in {{HomePage.DynamoViewModel_PropertyChanged}}, call the refresh APIs when {{ShowStartPage}} flips to true; in {{HomePage.OpenFile}} and {{StartPage.HandleFilePath}}, pre-validate the path — on miss show the new dialog, drop the stale entry, return early (avoids the {{CanOpen}} side-effect and removes the hang).


h3. TASK 1: Add refresh APIs and missing-file resource strings [HIGH PRIORITY]

Status: DONE
Milestone: Pure-logic refresh + prune methods exist, are unit-tested; new localized message strings are available.

  • Factor {{StartPageViewModel.LoadTemplates()}} body into a reusable helper that takes a {{clearFirst}} flag (ctor passes {{false}}, {{RefreshTemplates}} passes {{true}}).
  • Add {{internal void RefreshTemplates()}} on {{StartPageViewModel}}.
  • Add {{internal void PruneInvalidRecentFiles()}} on {{DynamoViewModel}}: {{RecentFiles.Where(p => !PathHelper.IsValidPath(p)).ToList()}} then {{Remove}} each (existing {{CollectionChanged}} listeners propagate).
  • Add {{internal bool TryRemoveRecentFile(string path)}} for surgical removal in the click handler.
  • Add resource entries in {{src/DynamoCoreWpf/Properties/Resources.en-US.resx}}:
    ** {{HomeFileNoLongerExistsAtPath}} = "This file no longer exists at the following location and will be removed from your list.\n{0}"
    ** {{HomeFileNoLongerExistsAtPathTitle}} = "File Not Found"
  • Keep new APIs {{internal}} (no {{PublicAPI.Unshipped.txt}} change). If any go public, add entries.

Validation: Tests in Task 4 pass; {{DynamoCoreWpf}} builds with the new resx entries.

Requirements from spec:

  • "Refresh the Home Templates list when the Home view (re)instantiates..."
  • "Same treatment for Recent Files."
  • "Fix the hang when a missing file/template is clicked, and improve the warning message." (resource side; wiring in Task 3.)

Files to Modify:

  • {{src/DynamoCoreWpf/Controls/StartPage.xaml.cs}}
  • {{src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs}}
  • {{src/DynamoCoreWpf/Properties/Resources.en-US.resx}} (Designer.cs regenerates)

Implementation Details:

  • {{LoadTemplates()}} currently does not clear {{templateFiles}}; refactor as {{private void LoadTemplatesFromDisk(bool clearFirst)}}.
  • {{PruneInvalidRecentFiles}} must run on the UI thread (called from a WebView2 callback) — listeners run synchronously on the calling thread.
  • Reuse {{DynamoUtilities.PathHelper.IsValidPath}}.

h3. TASK 2: Wire HomePage to auto-push template-collection changes [HIGH PRIORITY]

Status: DONE
Milestone: Mutating {{StartPageViewModel.TemplateFiles}} updates the WebView's templates list without an explicit caller-side {{SendTemplateData()}}.

  • In {{HomePage.SendTemplateData()}} (or a helper called from {{LoadingDone}}), subscribe {{startPage.TemplateFiles.CollectionChanged += TemplateFiles_CollectionChanged;}} after the initial push (mirror RecentFiles at {{HomePage.xaml.cs:370-373}}).
  • Add {{private void TemplateFiles_CollectionChanged(...)}} that calls {{_ = LoadTemplates(startPage.TemplateFiles?.DistinctBy(x => x.ContextData).ToList())}}.
  • Unsubscribe in {{HomePage.Dispose(bool)}} (lines 747-782).
  • Guard against double-subscription with a {{bool templateSubscribed}} flag in case {{LoadingDone}} runs more than once.

Validation: Manual breakpoint confirms WebView receives updates on collection mutation; {{dotnet test src/DynamoCoreWpfTests/DynamoCoreWpfTests.csproj --filter "Name~HomePage"}} passes.

Requirements from spec: Required scaffolding for Task 3.

Files to Modify: {{src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs}}

Implementation Details: Follow exact shape of {{RecentFiles_CollectionChanged}} (line 283) and its subscribe/unsubscribe sites for consistency. Handler ignores {{e.Action}}, rebuilds whole list.


h3. TASK 3: Refresh on Home-view show + fix missing-file click [HIGH PRIORITY]

Status: DONE
Milestone: Ticket repro no longer reproduces. Templates/Recent Files reflect on-disk state after returning to Home. Clicking a missing tile shows a clear message, removes the item, and does not hang Dynamo.

  • Extend {{HomePage.DynamoViewModel_PropertyChanged}} ({{HomePage.xaml.cs:127-133}}): when {{e.PropertyName == nameof(ShowStartPage)}} AND the new value is {{true}}, call {{startPage.RefreshTemplates()}} and {{startPage.DynamoViewModel.PruneInvalidRecentFiles()}}. Keep the existing {{setShowStartPageChanged}} JS call.
  • Track previous {{ShowStartPage}} in a private field to avoid redundant refreshes if the setter fires when the value is unchanged.
  • In {{HomePage.OpenFile}} ({{HomePage.xaml.cs:536-553}}), before computing {{openArg}}, add a pre-validate block: if {{!PathHelper.IsValidPath(path)}}, drop the stale entry ({{RefreshTemplates()}} if it's a template, else {{TryRemoveRecentFile(path)}}), then {{DynamoMessageBox.Show(startPage.DynamoViewModel.Owner, string.Format(WpfResources.HomeFileNoLongerExistsAtPath, path), WpfResources.HomeFileNoLongerExistsAtPathTitle, MessageBoxButton.OK, MessageBoxImage.Warning)}} and {{return}}. Apply this before the {{IsTestMode}} early return so tests can exercise it (or extract to an internal helper invoked from tests directly).
  • Make the same pre-validate change in {{StartPage.HandleFilePath}} ({{StartPage.xaml.cs:654-666}}) for legacy parity.
  • Default to no batching; if {{Clear()}} + bulk {{Add()}} causes WebView flicker, batch by temporarily detaching {{TemplateFiles_CollectionChanged}}, refreshing, reattaching, and firing one final push.

Validation:

  • Manual: follow the ticket repro — delete a template via Explorer, click the now-missing tile. Expected: clear "File Not Found" dialog, no archive language, no hang, tile disappears.
  • Manual: open a workspace, close it back to Home; newly-added .dyn appears.
  • Manual: open + save a workspace, delete the file via Explorer, close back to Home; Recent Files no longer lists it.
  • Automated tests added in Task 4 pass.

Requirements from spec: All three bullets under "Scope".

Files to Modify:

  • {{src/DynamoCoreWpf/Views/HomePage/HomePage.xaml.cs}}
  • {{src/DynamoCoreWpf/Controls/StartPage.xaml.cs}}

Implementation Details:

  • Pre-validate skips {{OpenCommand.CanExecute}}, so the side-effecting modal in {{CanOpen}} is never invoked from the WebView2 host-object thread — removes the hang. Do not modify {{CanOpen}} itself; other call sites (File menu, ShowOpenDialog) keep current behavior.
  • Use {{DynamoMessageBox.Show(Owner, ...)}} (same pattern as {{CanOpen}}).
  • The {{ShowStartPage = true}} → refresh trigger covers both repro scenarios (workspace close, Home button click) because all paths flow through that setter.

h3. TASK 4: Tests [MEDIUM PRIORITY]

Status: DONE
Milestone: Test coverage protects refresh/prune/click behaviors against regression.

  • In {{test/DynamoCoreWpf3Tests/WorkspaceOpeningTests.cs}}:
    ** {{PruneInvalidRecentFilesRemovesMissingPathsWhenInvoked}}
    ** {{PruneInvalidRecentFilesPreservesValidPathsWhenAllValid}}
    ** {{TryRemoveRecentFileReturnsTrueWhenEntryExists}}
    ** {{TryRemoveRecentFileReturnsFalseWhenEntryMissing}}
    ** {{RefreshTemplatesPicksUpNewlyAddedTemplateWhenInvokedAfterAdd}}
    ** {{RefreshTemplatesDropsDeletedTemplateWhenInvokedAfterDelete}}
    ** Template tests use {{Model.UpdatePreferenceItemLocation(PathManager.PreferenceItem.Templates, ...)}} to redirect to an isolated {{TempFolder}} subdirectory, then write/delete {{.dyn}} files there.
  • In {{test/DynamoCoreWpfTests/HomePageTests.cs}}:
    ** {{OpenFileWithMissingPathDoesNotInvokeTestHook}} — assert {{TestHook}} (proxy for {{OpenCommand}}) is not invoked when the path is missing.
    ** {{OpenFileWithMissingRecentFilePathRemovesItFromRecentFiles}} — assert the stale recent-file entry is pruned via {{TryRemoveRecentFile}}.
    ** {{HandleMissingFileWithListedTemplateRefreshesTemplatesInsteadOfRecentFiles}} — assert the template branch refreshes templates and leaves {{RecentFiles}} untouched.
  • NUnit conventions: {{WhenConditionThenExpectedBehavior}} naming, AAA structure, one behavior per test, no {{[Ignore]}}/{{[Explicit]}}.

Validation:

  • {{dotnet test src/DynamoCoreWpfTests/DynamoCoreWpfTests.csproj --filter "Name~HomePage"}} passes.
  • {{dotnet test src/DynamoCoreWpf3Tests/DynamoCoreWpfTests3.csproj --filter "NameRefreshTemplates|NamePruneInvalidRecentFiles"}} passes.

Files to Modify:

  • {{test/DynamoCoreWpf3Tests/WorkspaceOpeningTests.cs}}
  • {{test/DynamoCoreWpfTests/HomePageTests.cs}}

Implementation Details: The pre-validate in {{HomePage.OpenFile}} runs before the {{IsTestMode}} short-circuit, so {{TestHook}} not being invoked is the observable signal of the missing-path branch. Template-branch coverage drives {{HandleMissingFile}} directly with an injected fake {{TemplateFiles}} entry. The {{DynamoViewModel_PropertyChanged}} flow is guarded by {{dynWebView?.CoreWebView2 != null}} and is therefore covered by the existing manual-test plan rather than a unit test (the underlying {{RefreshTemplates}}/{{PruneInvalidRecentFiles}} methods are independently covered in {{WorkspaceOpeningTests}}).


h2. Testing Strategy

h3. Unit Tests

  • {{PruneInvalidRecentFiles}} with mixed valid/invalid inputs.
  • {{RefreshTemplates}} against a temp templates directory (add/remove scenarios).
  • {{PathHelper.IsValidPath}} already covered — no duplication.

h3. Integration Tests

  • Existing {{WorkspaceOpeningTests}} exercising {{OpenCommand}} continue to pass (the change is at the caller, not {{OpenCommand}}).
  • Existing {{HomePageTests.ShowTemplateCommandFromHomePage}} ({{HomePageTests.cs:486}}) continues to pass.

h3. Manual Testing Steps

Build ({{msbuild src/Dynamo.All.sln /p:Configuration=Release}}), launch sandbox.

Verify Home → Templates section is populated.

With Dynamo running, drop a new {{.dyn}} into the templates folder; open a workspace; close it. New template appears on Home.

With Dynamo running, delete a template via Explorer; open a workspace; close it. Deleted template disappears.

Repeat (4) but click the tile BEFORE refresh: expect "File Not Found" dialog, tile auto-removed, no hang.

Save a graph, open it, close it; delete the file via Explorer; navigate back to Home; confirm Recent Files no longer lists it.

Repeat (6) click flow before refresh: expect "File Not Found" dialog, auto-removal, no hang.

Confirm no regression on the trust-warning path for valid template opens.

h2. Performance Considerations

  • {{RefreshTemplates}} re-reads {{TemplatesDirectory}} and deserializes each {{.dyn}} via {{GetFileProperties}} ({{StartPage.xaml.cs:684-703}}). Template count is small (single digits); negligible UI-thread cost.
  • {{PruneInvalidRecentFiles}} is O(n) with {{n ≤ MaxNumRecentFiles}} (default 10).
  • {{Clear()}} + bulk {{Add()}} on {{TemplateFiles}} fires multiple {{CollectionChanged}} events; for ≤10 templates the resulting ≤11 WebView pushes are invisible. Add batching only if observable flicker shows up.

h2. Migration Notes

  • No persisted-data schema changes; {{PreferenceSettings.RecentFiles}} and templates folder location unchanged.
  • New resource strings flow through the standard {{master-localization}} translation pipeline; implementation does not require translations to land first.
  • No public-API additions if new methods stay {{internal}}. If any are made public, append to {{src/DynamoCoreWpf/PublicAPI.Unshipped.txt}}.

@eamiri eamiri added the kiln Created by kiln label Jun 8, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-10516

eamiri and others added 4 commits June 8, 2026 18:35
Refactor StartPageViewModel.LoadTemplates into LoadTemplatesFromDisk(bool
clearFirst) and expose internal RefreshTemplates so the Home view can
re-read the templates directory on demand.

Add DynamoViewModel.PruneInvalidRecentFiles and TryRemoveRecentFile for
removing recent-file entries whose underlying file has been deleted on
disk; the existing CollectionChanged subscribers propagate the change to
the WebView automatically.

Add HomeFileNoLongerExistsAtPath / HomeFileNoLongerExistsAtPathTitle
resources used by the Home-screen click handlers (wiring lands in
follow-up tasks).
Subscribe HomePage to StartPageViewModel.TemplateFiles.CollectionChanged so that mutations to the template list flow to the WebView automatically (mirroring the existing RecentFiles wiring). Adds a templateSubscribed guard against double subscription and unsubscribes in Dispose(bool).
- HomePage.DynamoViewModel_PropertyChanged: when ShowStartPage flips to
  true, call StartPageViewModel.RefreshTemplates() and
  DynamoViewModel.PruneInvalidRecentFiles(). Track last value to skip
  redundant refreshes when the property fires unchanged.
- HomePage.OpenFile: pre-validate the path with PathHelper.IsValidPath.
  On miss, drop the stale entry (template via RefreshTemplates, recent
  file via TryRemoveRecentFile) and show the new File Not Found dialog.
  Pre-validate runs before the IsTestMode short-circuit so tests can
  exercise it. Skips OpenCommand.CanExecute, which avoids the modal
  inside CanOpen that hangs the WebView2 host-object callback.
- StartPage.HandleFilePath: same pre-validate for legacy-view parity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add NUnit coverage for the Home Templates and Recent Files refresh
flow added in this branch:

- WorkspaceOpeningTests: Prune/TryRemove on RecentFiles and
  RefreshTemplates against a redirected templates directory.
- HomePageTests: pre-validate branch of OpenFile and
  HandleMissingFile differentiating template vs recent-file paths.
@eamiri eamiri marked this pull request as ready for review June 8, 2026 23:11
@eamiri eamiri added the pr_ready PR is ready for review label Jun 8, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jun 8, 2026

Copy link
Copy Markdown

var homePage = new HomePage { DataContext = startPage };
var missingPath = Path.Combine(Path.GetTempPath(), "stale-recent-" + Guid.NewGuid().ToString("N") + ".dyn");

vm.RecentFiles.Clear();

// Inject a fake template entry so the missing path is treated as a template, not a recent file.
startPage.TemplateFiles.Add(new StartPageListItem("Ghost Template") { ContextData = ghostTemplate });
vm.RecentFiles.Clear();
public void PruneInvalidRecentFilesRemovesMissingPathsWhenInvoked()
{
// Arrange
var validPath = Path.Combine(SampleDirectory, @"en-US\Basics\Basics_Basic01.dyn");
{
// Arrange
var validPath = Path.Combine(SampleDirectory, @"en-US\Basics\Basics_Basic01.dyn");
var missingPath = Path.Combine(TempFolder, "definitely-not-on-disk.dyn");
public void PruneInvalidRecentFilesPreservesValidPathsWhenAllValid()
{
// Arrange
var first = Path.Combine(SampleDirectory, @"en-US\Basics\Basics_Basic01.dyn");
{
// Arrange
var first = Path.Combine(SampleDirectory, @"en-US\Basics\Basics_Basic01.dyn");
var second = Path.Combine(SampleDirectory, @"en-US\Basics\Basics_Basic02.dyn");
public void TryRemoveRecentFileReturnsTrueWhenEntryExists()
{
// Arrange
var path = Path.Combine(TempFolder, "anything.dyn");
ViewModel.RecentFiles.Clear();

// Act
var removed = ViewModel.TryRemoveRecentFile(Path.Combine(TempFolder, "missing.dyn"));
public void RefreshTemplatesPicksUpNewlyAddedTemplateWhenInvokedAfterAdd()
{
// Arrange
var templatesDir = Path.Combine(TempFolder, "templates-add-" + Guid.NewGuid().ToString("N"));
var startPage = new StartPageViewModel(ViewModel, isFirstRun: true);
Assert.AreEqual(0, startPage.TemplateFiles.Count, "Sanity: empty templates dir starts with zero items.");

var newTemplate = Path.Combine(templatesDir, "NewTemplate.dyn");
@jasonstratton

Copy link
Copy Markdown
Contributor

Closing in favor of #17161 (Chloe's implementation). Per discussion with Erfan — we'll continue with the community PR.

@eamiri eamiri deleted the 10516-refresh-templates-recent-files branch June 26, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kiln Created by kiln pr_ready PR is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants