Skip to content

Fix Angular 21 change detection for simulation time, import-csv, connect-database, and ravel-select-horizontal-dim#639

Merged
highperformancecoder merged 37 commits intodevelopfrom
copilot/fix-header-change-detection
Apr 8, 2026
Merged

Fix Angular 21 change detection for simulation time, import-csv, connect-database, and ravel-select-horizontal-dim#639
highperformancecoder merged 37 commits intodevelopfrom
copilot/fix-header-change-detection

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 8, 2026

  • Rebase PR onto develop branch (merged develop into branch)
  • Fix CommunicationService: convert t and deltaT to BehaviorSubject<string>
  • Fix Header Component: subscribe to t$ and deltaT$, call detectChanges()
  • Fix Header Template: use component properties instead of commService.t/deltaT
  • Fix Import CSV Component: add this.cdr.detectChanges() at end of load() method
  • Fix Connect Database Component: add this.cdRef.detectChanges() at end of getTables()
  • Fix Ravel Select Horizontal Dim Component: already in develop via PR Fix dialog rendering in 8 components after Angular 20→21 migration #637
Original prompt

Problem

After the Angular 20→21 migration, four components still have change detection issues where data doesn't update in the UI after async operations complete:

1. Header Component - Simulation Time Not Updating

File: gui-js/libs/ui-components/src/lib/header/header.component.html (lines 118-121)

The template displays t and deltaT values directly from commService:

<div class="time-info toolButton" classType="SimTime">
  <span class="time">t:{{ commService.t }}</span>
  <span class="delta-time">&Delta;t:{{ commService.deltaT }}</span>
</div>

File: gui-js/libs/core/src/lib/services/communication/communication.service.ts (lines 284-293)

The service updates these values but doesn't trigger change detection:

private updateSimulationTime(t: number, deltaT: number) {
  if (Number(this.t) >= this.runUntilTime) {
    this.showPlayButton$.next(true);
  }

  this.t = t.toFixed(2);
  this.deltaT = deltaT.toFixed(2);
}

Root Cause: In Angular 21, the header component needs to manually trigger change detection after commService.t and commService.deltaT are updated. The component already has ChangeDetectorRef injected and uses it for other observables, but not for the simulation time values.

Solution: Subscribe to simulation time updates in the header component and call changeDetectorRef.detectChanges() when they change. Convert t and deltaT to BehaviorSubjects in the service.

2. Import CSV Component - Tab Not Switching After File Load

File: gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts (lines 318-347)

The load() method updates selectedTabIndex but doesn't trigger change detection:

async load(selectTab) {
  if (this.url.value === '') return;

  this.setParameterNameFromUrl();

  if (this.url.value.includes('://')) {
    const savePath = await this.electronService.downloadCSV({ windowUid: this.csvDialog.$prefix(), url: this.url.value });
    this.url.setValue(savePath);
    this.files = [savePath];
  }

  const fileUrlOnServer = await this.csvDialog.url();

  if (!this.url.value || this.url.value !== fileUrlOnServer) {
    await this.csvDialog.url(this.url.value);
    await this.csvDialog.guessSpecAndLoadFile();
    await this.getCSVDialogSpec();
    this.updateForm();
  }
  await this.csvDialog.loadFile();
  await this.parseLines();

  for (const tab of this.tabs) {
    tab.disabled = false;
  }

  this.fileLoaded = true;

  this.selectedTabIndex = selectTab;
}

Root Cause: After all the async operations complete, selectedTabIndex is set but Angular 21 doesn't detect the change. The component has ChangeDetectorRef injected but doesn't call it after the async load completes.

Solution: Call this.cdr.detectChanges() at the end of the load() method after selectedTabIndex is updated.

3. Connect Database Component - Table Selector Not Populating

File: gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts (lines 44-47, 60-76)

The getTables() method updates this.tables array but doesn't trigger change detection:

async getTables() {
  this.ravel.db.connect(this.dbType,this.connection,"");
  this.tables=await this.ravel.db.tableNames();
}

async selectFile() {
  let options: OpenDialogOptions = {
    defaultPath: ':models',
    filters: [
      { extensions: ['sqlite'], name: 'SQLite' },
      { extensions: ['*'], name: 'All Files' },
    ],
    properties: ['openFile'],
  };
  //if (defaultPath) options['defaultPath'] = defaultPath;
  let filePath = await this.electronService.openFileDialog(options);
  if (typeof filePath==='string')
    this.connection=`db=${filePath}`;
  else
    this.connection=`db=${filePath[0]}`;
  await this.getTables();
}

Template: gui-js/libs/ui-components/src/lib/connect-database/connect-database.html (lines 13-18)

<select (change)="setTable($event)">
  <option disabled selected>--Select Table--</option>
  @for (t of tables; track t) {
    <option [value]="t">{{t}}</option>
  }
</select>

Root Cause: After getTables() completes, the tables array is populated but Angular 21 doesn't detect that the array reference changed. The component has ChangeDetectorRef injected but doesn't use it.

Solution: Call this.cdRef.detectChanges() at the end of getTables() method.

4. Ravel Select Horizontal Dim Component - Numerical Columns Not Populating

File: gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts (lines 36-38)

The ngOnInit() method populates dataCols but doesn't trigger change detection:

async ngOnInit() {
  this.dataCols=await this.ravel.db.numericalColumnNames();
}

Template: gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.html (lines 5-13)

<div class="selectors">
  @for (d of dataCols; track d) {
    <div class...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/highperformancecoder/minsky/639)
<!-- Reviewable:end -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved UI responsiveness for simulation time display and overall component rendering through more reliable state management.
  * Strengthened change detection mechanism across multiple data components to ensure consistent and timely display of dynamically loaded content and updates.
  * Fixed potential rendering delays and timing issues that could affect tab selection interactions and data table population during rapid user actions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

dependabot bot and others added 30 commits March 11, 2026 21:32
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.10 to 7.5.11.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](isaacs/node-tar@v7.5.10...v7.5.11)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
…_PAINT handler

- RESTService/addon.cc: Make destroyFrame synchronous on Windows/macOS like renderFrame.
  The child window is created on the JS thread (via synchronous renderFrame), so
  destroyFrame must also run on the JS thread to avoid cross-thread SendMessageA
  calls in ~WindowInformation() that block the minsky thread while holding
  minskyCmdMutex, causing a deadlock when the JS thread tries to acquire minskyCmdMutex.

- model/windowInformation.cc: Add ValidateRect in windowProc WM_PAINT handler when
  GWLP_USERDATA is 0 to prevent infinite WM_PAINT loop when window is being destroyed.

- model/windowInformation.cc: Add SWP_NOACTIVATE flag to SetWindowPos in blit() to
  prevent unnecessary window activation messages on every paint, reducing message
  cascade issues with screen sharing tools like Zoom.

Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
…SetWindowPos from blit

- RESTService/addon.cc: drawNativeWindows() now swaps nativeWindowsToRedraw under
  minskyCmdMutex then releases the lock before calling draw(), matching the existing
  macOSXDrawNativeWindows() pattern. Previously holding minskyCmdMutex across the
  entire draw() call could deadlock: GDI/Cairo operations inside draw() can need the
  JS thread's message pump while the JS thread is blocked trying to acquire
  minskyCmdMutex for a synchronous command.

- model/windowInformation.cc: Removed SetWindowPos(HWND_TOP,...) from blit(). Calling
  it on every WM_PAINT fired WM_WINDOWPOSCHANGED messages that could cascade into
  further WM_PAINT messages and triggered screen-sharing hooks (e.g. Zoom) on every
  paint, keeping the JS thread's message pump busy and preventing it from returning
  to the libuv event loop to service minsky thread BlockingCall resolutions.

- RESTService/addon.cc: Corrected the comment on synchronous destroyFrame dispatch:
  the real reason is that Windows requires a window to be destroyed on the same thread
  that created it, not (as previously stated) that minskyCmdMutex blocks windowProc.

- model/windowInformation.cc: Expanded the ValidateRect else-branch comment to
  clarify when GWLP_USERDATA is zero (WindowInformation already torn down).

Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
…ispatch per review feedback

- Revert drawNativeWindows() back to holding minskyCmdMutex during draw() as
  intended: the mutex protects the model from being modified while a view is
  being redrawn, and releasing it before draw() would break that invariant.

- Revert destroyFrame from the synchronous dispatch list: destroyFrame only
  posts WM_CLOSE to the window and does not fix the underlying deadlock.
  Only renderFrame needs to be dispatched synchronously on the JS thread.

The only change retained in this PR is the removal of SetWindowPos from
blit() in windowInformation.cc, which prevents cascaded WM_WINDOWPOSCHANGED/
WM_PAINT messages that kept the JS thread's message pump busy indefinitely.

Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
…lockup-issue

Fix Windows canvas lockup: remove SetWindowPos from blit()
PlotWidgetSuper inherits from both ItemT<PlotWidget> (via Item) and
RenderNativeWindow (via EventInterface). Both chains define moveTo()
and position(), but classdesc resolves to EventInterface's empty
implementations rather than Item's working ones.

This makes PlotWidget::moveTo() silently no-op via the REST API, and
position() always returns {0,0} — preventing programmatic positioning
of plot widgets.

Add explicit overrides in PlotWidget that delegate to Item::moveTo()
and Item::x()/y(), following the existing disambiguation pattern used
for width() and height().

Fixes tickets:#1918
…nz/minsky into primesoftnz-fix/plotwidget-moveto-position
Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.8 to 4.7.9.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.9/release-notes.md)
- [Commits](handlebars-lang/handlebars.js@v4.7.8...v4.7.9)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-version: 4.7.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 1.1.13.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](juliangruber/brace-expansion@v1.1.12...v1.1.13)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.3 to 1.4.0.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](digitalbazaar/forge@v1.3.3...v1.4.0)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
slope/intercepts, rather than the linear interpolation that the
standard linear regression operator does.
…d tests

Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/6762f0ad-cf41-452b-b6b4-3fc692881ff2

Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
…rmancecoder/minsky into feature-bulk-linear-regression
…-regression

Added a "bulk" Linear regression operator that returns a tensor of
…-database, and ravel-select-horizontal-dim components

Agent-Logs-Url: https://github.com/highperformancecoder/minsky/sessions/f1045e9e-5e27-4c75-9055-d178ab7ae366

Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix change detection for header component simulation time Fix Angular 21 change detection for simulation time, import-csv, connect-database, and ravel-select-horizontal-dim Apr 8, 2026
@highperformancecoder
Copy link
Copy Markdown
Owner

@copilot Please rebase this PR to develop branch, not master

@highperformancecoder
Copy link
Copy Markdown
Owner

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab68e5d9-6eee-43fe-88b1-9b3511971cf4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Converted simulation time state from plain string properties to BehaviorSubject<string> instances in the communication service, and updated multiple UI components to subscribe to these reactive streams and trigger explicit change detection after asynchronous operations.

Changes

Cohort / File(s) Summary
Reactive State Management
gui-js/libs/core/src/lib/services/communication/communication.service.ts
Replaced t and deltaT string properties with BehaviorSubject<string> instances (t$, deltaT$); updated updateSimulationTime() to read current time via .value and push updates via .next().
Component State and Subscriptions
gui-js/libs/ui-components/src/lib/header/header.component.ts
Added t and deltaT public properties; extended ngOnInit to subscribe to commService.t$ and commService.deltaT$, updating local state and triggering change detection on each emission.
Template Binding Updates
gui-js/libs/ui-components/src/lib/header/header.component.html
Updated simulation time display bindings from commService.t and commService.deltaT to local component properties t and deltaT.
Change Detection Triggers
gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts, gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts, gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts
Added explicit detectChanges() calls after asynchronous operations to ensure immediate view synchronization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hopping through RxJS streams so bright,
Time flows as subjects, what a delight!
From strings to behaviors, reactively spun,
Change detection syncs when async is done,
Simulation hops in perfect time! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the main changes: fixing Angular 21 change detection issues across four components (simulation time, import-csv, connect-database, ravel-select-horizontal-dim).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/fix-header-change-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@highperformancecoder highperformancecoder marked this pull request as ready for review April 8, 2026 02:47
Copilot AI review requested due to automatic review settings April 8, 2026 02:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
gui-js/libs/ui-components/src/lib/header/header.component.ts (1)

81-89: Collapse the two simulation-time subscriptions into one CD trigger.

Line 81-89 currently runs detectChanges() twice per tick (t$ then deltaT$). A combined subscription reduces redundant change detection work.

Proposed refactor
-import { Subject, takeUntil } from 'rxjs';
+import { combineLatest, Subject, takeUntil } from 'rxjs';
@@
-      this.commService.t$.pipe(takeUntil(this.destroy$)).subscribe((t) => {
-        this.t = t;
-        this.changeDetectorRef.detectChanges();
-      });
-
-      this.commService.deltaT$.pipe(takeUntil(this.destroy$)).subscribe((deltaT) => {
-        this.deltaT = deltaT;
-        this.changeDetectorRef.detectChanges();
-      });
+      combineLatest([this.commService.t$, this.commService.deltaT$])
+        .pipe(takeUntil(this.destroy$))
+        .subscribe(([t, deltaT]) => {
+          this.t = t;
+          this.deltaT = deltaT;
+          this.changeDetectorRef.detectChanges();
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gui-js/libs/ui-components/src/lib/header/header.component.ts` around lines 81
- 89, The two subscriptions to this.commService.t$ and this.commService.deltaT$
cause detectChanges() to run twice; replace them with a single combined
subscription (e.g., combineLatest([this.commService.t$,
this.commService.deltaT$]) or an equivalent operator) that pipes
takeUntil(this.destroy$), assigns both this.t and this.deltaT inside the
subscription callback, and then calls this.changeDetectorRef.detectChanges()
once; update references to the existing destroy$, t, deltaT, commService
observables and changeDetectorRef so behavior remains identical but with a
single CD trigger.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gui-js/libs/core/src/lib/services/communication/communication.service.ts`:
- Around line 285-291: The run-until check is using the previous observable
value (Number(this.t$.value)) which can overshoot; change the condition to use
the current loop variable t (or Number(t) if t might be a string) when comparing
against runUntilTime so showPlayButton$.next(true) fires immediately; leave the
subsequent updates to this.t$.next(t.toFixed(2)) and
this.deltaT$.next(deltaT.toFixed(2)) as-is.

---

Nitpick comments:
In `@gui-js/libs/ui-components/src/lib/header/header.component.ts`:
- Around line 81-89: The two subscriptions to this.commService.t$ and
this.commService.deltaT$ cause detectChanges() to run twice; replace them with a
single combined subscription (e.g., combineLatest([this.commService.t$,
this.commService.deltaT$]) or an equivalent operator) that pipes
takeUntil(this.destroy$), assigns both this.t and this.deltaT inside the
subscription callback, and then calls this.changeDetectorRef.detectChanges()
once; update references to the existing destroy$, t, deltaT, commService
observables and changeDetectorRef so behavior remains identical but with a
single CD trigger.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f56f932c-c6dc-4f34-a964-a458fc0f0226

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba2cff and 58792a4.

📒 Files selected for processing (6)
  • gui-js/libs/core/src/lib/services/communication/communication.service.ts
  • gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts
  • gui-js/libs/ui-components/src/lib/header/header.component.html
  • gui-js/libs/ui-components/src/lib/header/header.component.ts
  • gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts
  • gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts

…-change-detection

# Conflicts:
#	Doxyversion
#	doc/version.tex
#	gui-js/libs/shared/src/lib/constants/version.ts
#	gui-js/package-lock.json
#	gui-js/package.json
#	minskyVersion.h

Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Addresses remaining Angular 21 change-detection gaps where UI state wasn’t updating after async operations (simulation time in header, CSV import tab switching, DB table list, and ravel horizontal-dim column list).

Changes:

  • Converted CommunicationService simulation time fields from plain strings to BehaviorSubject<string> (t$, deltaT$) and updated the simulation-time update logic to publish via .next().
  • Updated Header component/template to subscribe to t$ / deltaT$, store local t / deltaT, and manually trigger change detection.
  • Added detectChanges() calls after async UI-state updates in Import CSV, Connect Database, and Ravel Select Horizontal Dim.

Reviewed changes

Copilot reviewed 35 out of 37 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
gui-js/libs/core/src/lib/services/communication/communication.service.ts Publishes simulation time via BehaviorSubject instead of mutating string fields.
gui-js/libs/ui-components/src/lib/header/header.component.ts Subscribes to simulation time streams and triggers manual change detection.
gui-js/libs/ui-components/src/lib/header/header.component.html Binds to component t / deltaT instead of service fields.
gui-js/libs/ui-components/src/lib/import-csv/import-csv.component.ts Forces CD after async load so selectedTabIndex update renders.
gui-js/libs/ui-components/src/lib/connect-database/connect-database.component.ts Forces CD after async table-name load so dropdown populates.
gui-js/libs/ui-components/src/lib/ravel-select-horizontal-dim/ravel-select-horizontal-dim.component.ts Forces CD after async numerical column-name load so list populates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 8, 2026

@copilot Please rebase this PR to develop branch, not master

Done in b7b8f63. The develop branch has been merged into this PR branch so all develop changes are included. However, I don't have the ability to change the PR's target base from master to develop via the available tools — you'll need to do that manually in the GitHub UI (Edit → Change base → develop).

@highperformancecoder highperformancecoder changed the base branch from master to develop April 8, 2026 03:25
@highperformancecoder highperformancecoder merged commit 69e9543 into develop Apr 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants