Skip to content

Commit 4176fcd

Browse files
fix: restore live dashboard graph physics
1 parent e10e1b4 commit 4176fcd

8 files changed

Lines changed: 86 additions & 72 deletions

File tree

engraphis/classic_assets/dashboard.js

Lines changed: 10 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

engraphis/dashboard_assets/engraphis-graph.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,9 @@
912912
});
913913
}
914914

915-
/* The dashboard already honours `prefers-reduced-motion` for the classic renderer; this
916-
engine must not quietly reintroduce perpetual motion for the same user. */
915+
/* Reduced motion still controls cosmetic animation and camera transitions. Physics is
916+
deliberately controlled by the visible Freeze switch instead: otherwise the switch can
917+
say "off" while an OS preference silently leaves every graph static. */
917918
function reduced() {
918919
if (typeof opts.reducedMotion === 'function') return !!opts.reducedMotion();
919920
try {
@@ -1387,7 +1388,8 @@
13871388
pendingRender = pendingRender ? [pendingRender[0] || fit, pendingRender[1] || reheat] : [fit, reheat];
13881389
return;
13891390
}
1390-
const motion = !reduced();
1391+
const motion = !state.settings.frozen;
1392+
const reducedMotion = reduced();
13911393
const next = visible();
13921394
/* Reuse the arrays force-graph already holds when the view is unchanged: the sizing and
13931395
colouring pass below must write onto the objects the vendor is painting from, and the
@@ -1446,6 +1448,7 @@
14461448
const flowing = !fullGraph
14471449
&& state.settings.flow !== false
14481450
&& motion
1451+
&& !reducedMotion
14491452
&& data.links.length <= PARTICLE_LINK_LIMIT;
14501453
const particles = !flowing
14511454
? 0
@@ -1781,7 +1784,7 @@
17811784
};
17821785
api.fit = () => { if (!destroyed) fg.zoomToFit(reduced() ? 0 : 500, 40); };
17831786
api.reheat = () => {
1784-
if (destroyed || reduced() || staticFullLayout) return;
1787+
if (destroyed || state.settings.frozen || staticFullLayout) return;
17851788
raw.nodes.forEach(n => { n.fx = undefined; n.fy = undefined; });
17861789
if (fg.d3ReheatSimulation) { fg.d3AlphaDecay(alphaDecay()); fg.d3ReheatSimulation(); }
17871790
};
@@ -1798,7 +1801,6 @@
17981801
if (staticFullLayout) return;
17991802
raw.nodes.forEach(n => { n.fx = undefined; n.fy = undefined; });
18001803
applyForces();
1801-
if (reduced()) return;
18021804
fg.d3AlphaDecay(alphaDecay());
18031805
if (fg.d3ReheatSimulation) fg.d3ReheatSimulation();
18041806
};

engraphis/dashboard_assets/ledger.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,6 @@
15961596
palette: byId('graph-palette').value,
15971597
flow: byId('graph-flow').getAttribute('aria-checked') === 'true',
15981598
labels: byId('graph-labels').getAttribute('aria-checked') === 'true',
1599-
frozen: state.graphFrozen,
16001599
tuning: graphTuningSettings(),
16011600
minDegree: number(byId('graph-min-degree').value),
16021601
depth: number(byId('graph-depth').value),
@@ -1654,7 +1653,9 @@
16541653
byId('graph-ghosts').checked = graphPreference('ghosts', byId('graph-ghosts').checked) !== false;
16551654
byId('graph-size').value = graphPreference('size', byId('graph-size').value,
16561655
['degree', 'betweenness']);
1657-
state.graphFrozen = graphPreference('frozen', false) === true;
1656+
// Freeze is deliberately session-only. A previously frozen arrangement must not make a
1657+
// freshly opened graph look broken; physics starts live until the person clicks Freeze.
1658+
state.graphFrozen = false;
16581659
setGraphSwitch('graph-freeze', state.graphFrozen);
16591660
setGraphSwitch('graph-flow', graphPreference('flow', true) !== false);
16601661
setGraphSwitch('graph-labels', graphPreference('labels', false) === true);
@@ -1700,7 +1701,6 @@
17001701
? view.repoFilter.slice(0, 200) : byId('graph-repo-filter').value;
17011702
state.graphIncludeCode = typeof view.includeCode === 'boolean'
17021703
? view.includeCode : state.graphIncludeCode;
1703-
state.graphFrozen = typeof view.frozen === 'boolean' ? view.frozen : state.graphFrozen;
17041704
byId('graph-preset').value = preset;
17051705
byId('graph-style').value = style;
17061706
byId('graph-color').value = color;

engraphis/static/dashboard.js

Lines changed: 10 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/e2e/commercial.spec.js

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ async function openView(page, name) {
267267
await expect(page.locator(`#view-${name}`)).toHaveClass(/\bactive\b/);
268268
}
269269

270-
test('local dashboard exposes hosted Pro and Team CTAs without local commercial controls', async ({ page }) => {
270+
test('local dashboard keeps generic Pro and Team CTAs out of settings', async ({ page }) => {
271271
const errors = recordBrowserErrors(page);
272272
const calls = await mockLocalClient(page);
273273
const response = await page.goto('/classic');
@@ -280,12 +280,9 @@ test('local dashboard exposes hosted Pro and Team CTAs without local commercial
280280
await openView(page, 'settings');
281281
const licensePanel = page.locator('.settings-license-panel');
282282
await expect(licensePanel.getByText('LOCAL CORE', { exact: true })).toBeVisible();
283-
await expect(licensePanel.getByRole('link', { name: 'Start 3-day Pro trial' })).toBeVisible();
284-
await expect(licensePanel.getByRole('link', { name: 'Start 3-day Team trial' })).toBeVisible();
285-
await expect(licensePanel).toContainText(
286-
'The email-confirmed, no-card trial lasts exactly 3 active days; '
287-
+ 'private-service account grace is separate, capped at 24 hours, and never extends cloud access or restricts local MCP and dashboard use.',
288-
);
283+
await expect(licensePanel.getByRole('link', { name: 'Start 3-day Pro trial' })).toHaveCount(0);
284+
await expect(licensePanel.getByRole('link', { name: 'Start 3-day Team trial' })).toHaveCount(0);
285+
await expect(licensePanel).not.toContainText('Support continued Engraphis development with Pro.');
289286

290287
await openView(page, 'team');
291288
const team = page.locator('#team-body');
@@ -467,17 +464,9 @@ test('a spent trial says so, and is never offered another one', async ({ page })
467464
await expect(licensePanel).toContainText('Your free trial has ended on 2025-06-28');
468465
await expect(licensePanel).toContainText('still in your local database');
469466
await expect(licensePanel).toContainText('cannot be started again');
470-
// Buyable, not trialable.
471-
await expect(licensePanel.getByRole('link', { name: 'Subscribe to Pro' }))
472-
.toHaveAttribute(
473-
'href',
474-
'https://cloud.engraphis.test/pro?plan=pro&interval=monthly&utm_source=engraphis&utm_medium=product&utm_campaign=pro_conversion&utm_content=license#billing',
475-
);
476-
await expect(licensePanel.getByRole('link', { name: 'Subscribe to Team' }))
477-
.toHaveAttribute(
478-
'href',
479-
'https://cloud.engraphis.test/team?plan=team&interval=monthly&utm_source=engraphis&utm_medium=product&utm_campaign=pro_conversion&utm_content=license_team#billing',
480-
);
467+
// Upgrade CTAs belong with individual locked features, not the general settings panel.
468+
await expect(licensePanel.getByRole('link', { name: 'Subscribe to Pro' })).toHaveCount(0);
469+
await expect(licensePanel.getByRole('link', { name: 'Subscribe to Team' })).toHaveCount(0);
481470
await expect(licensePanel.getByRole('link', { name: 'Start 3-day Pro trial' }))
482471
.toHaveCount(0);
483472
expect(errors).toEqual([]);

0 commit comments

Comments
 (0)