Skip to content

Cross-tab state sync#59

Merged
Demonstrandum merged 15 commits into
cursor/run-selection-persistence-348ffrom
cursor/cross-tab-state-sync-0c38
Feb 20, 2026
Merged

Cross-tab state sync#59
Demonstrandum merged 15 commits into
cursor/run-selection-persistence-348ffrom
cursor/cross-tab-state-sync-0c38

Conversation

@Demonstrandum

Copy link
Copy Markdown
Owner

Motivation for features / changes

This PR addresses several long-standing issues related to state synchronization and persistence across different dashboards (time-series vs. legacy Polymer plugins like Scalars, Images, Text):

  1. Inconsistent Run Selection: Run selections were independent between old-style plugin dashboards and lost on page reload.
  2. Inconsistent Run Colors: Run colors in old-style dashboards did not match the time-series tab, especially after user overrides or dark mode changes.
  3. Inconsistent Axis Scales: Axis scale settings (e.g., log scale) were not shared between the time-series and Scalars tabs and did not persist on reload in the Scalars tab.
  4. Inconsistent Panel Expansion: Expanded/collapsed states of tag groups were not synchronized or persisted across tabs.
  5. Squashed Scalar Plots: Scalar plots would sometimes render with incorrect, squashed dimensions after tab switches or refreshes.

The core motivation is to establish a single source of truth for these settings, ensuring consistency and persistence across the entire application, and to eliminate silent failures and duplicated logic.

Technical description of changes

This PR refactors state management for run selection, colors, axis scales, and panel expansion to use a single, shared source of truth (localStorage keys and window.__tbRunColorMap for colors) directly, eliminating intermediate formats and synchronization issues.

  1. Unified Run Selection:
    • tf-runs-selector (Polymer) now directly reads from and writes to localStorage['_tb_run_selection.v1'], the same key used by NgRx.
    • A new NgRx effect listens for tb-run-selection-changed custom events from Polymer components and dispatches runSelectionStateLoaded to keep the NgRx store in sync.
  2. Unified Run Colors:
    • ColorScale (Polymer) now primarily reads from window.__tbRunColorMap, which is kept live-updated by an NgRx subscription to getRunColorMap.
    • persistRunColorSettings$ effect now exports colors scoped to active route experiments to prevent cross-experiment conflicts.
    • Strict erroring is enforced: if window.__tbRunColorMap is missing or a run's color is not found, the application will crash (no more silent fallbacks).
    • ColorScale.setDomain() now returns early for empty domains to prevent crashes during initial module startup.
  3. Unified Axis Scales:
    • tf-scalar-card (Polymer) now reads from and writes to localStorage['_tb_axis_scales.v1'] directly.
    • Initialization now uses a _tagChanged observer to ensure this.tag is defined when reading stored scales.
  4. Unified Panel Expansion:
    • tf-category-paginated-view (Polymer) now reads from and writes to localStorage['_tb_tag_group_expansion.v1'] directly.
  5. Live Updates Across Tabs:
    • Custom events (tb-run-selection-changed, tb-tag-group-expansion-changed, tb-run-color-map-changed) are dispatched on state changes and listened for by other Polymer instances to trigger immediate re-reads and UI updates.
  6. Fix for Squashed Scalar Plots:
    • tf-line-chart-data-loader now uses a ResizeObserver to monitor its container's dimensions. It enqueues a chart redraw when the container size becomes valid after being hidden or having zero size, preventing charts from rendering squashed.

Screenshots of UI changes (or N/A)

N/A. This PR focuses on fixing existing behavior and consistency, not introducing new UI elements.

Detailed steps to verify changes work correctly (as executed by you)

  1. Run Selection Persistence & Sync:
    • Navigate to the Time-Series tab. Select/deselect several runs.
    • Switch to the Scalars tab. Verify the same runs are selected/deselected.
    • Switch to the Images tab. Verify the same runs are selected/deselected.
    • In the Images tab, change the run selection. Switch back to Scalars, then to Time-Series. Verify selections are synchronized live.
    • Refresh the page on any tab. Verify run selections persist.
  2. Run Color Consistency & Persistence:
    • Navigate to the Time-Series tab. Observe run colors.
    • Switch to the Scalars tab. Verify run colors exactly match the Time-Series tab.
    • (If possible) Change a run's color in the Time-Series tab. Switch to Scalars. Verify the color updates live.
    • Refresh the page on any tab. Verify run colors remain consistent across all tabs.
    • Verify that if multiple experiments have runs with the same name, colors are assigned deterministically without crashing.
  3. Axis Scale Persistence & Sync:
    • Navigate to the Time-Series tab. Select a scalar tag and change its Y-axis scale (e.g., to log).
    • Switch to the Scalars tab. Verify the corresponding scalar plot uses the same Y-axis scale.
    • In the Scalars tab, change a plot's Y-axis scale. Refresh the page. Verify the scale persists.
  4. Panel Expansion Persistence & Sync:
    • Navigate to the Time-Series tab. Expand/collapse several tag groups.
    • Switch to the Scalars tab. Verify the same tag groups are expanded/collapsed.
    • Refresh the page on any tab. Verify panel expansion states persist.
  5. Squashed Scalar Plots Fix:
    • Navigate to the Scalars tab.
    • Repeatedly switch between the Scalars tab and other tabs (e.g., Time-Series, Images, Text).
    • Refresh the page frequently while switching tabs.
    • Verify that scalar plots never appear "squashed" or with incorrect dimensions.

Alternate designs / implementations considered (or N/A)

Previous attempts involved maintaining separate localStorage keys or duplicating color computation logic, leading to synchronization issues and inconsistent behavior. This PR explicitly rejects those approaches in favor of a single source of truth for all shared state, with strict erroring to prevent silent failures.


Open in Web Open in Cursor 

cursoragent and others added 9 commits February 19, 2026 13:13
The superimposed cards grid had its column width hardcoded via a SCSS
variable ($metrics-min-card-width: 335px) and never read the dynamic
cardMinWidth setting from the store.

Regular card grids (CardGridComponent) receive cardMinWidth as an input
and apply it via [style.grid-template-columns], which overrides the
SCSS default. Superimposed cards were missing this entirely.

Changes:
- SuperimposedCardsViewContainer: select getMetricsCardMinWidth from the
  store and pass it as [cardMinWidth] to the presentation component.
- SuperimposedCardsViewComponent: accept cardMinWidth input, compute
  gridTemplateColumn in ngOnChanges (same logic as CardGridComponent),
  and bind it via [style.grid-template-columns] on the grid div.

Fixes #55

Co-authored-by: Samuel <samuel@knutsen.co>
Smoothing was still being written to the URL query string whenever
the user changed the setting, causing unwanted URL pollution.

Remove the smoothing serialization from DashboardDeepLinkProvider's
serializeStateToQueryParams. Smoothing is already persisted in
localStorage, so it does not need to live in the URL.

Deserialization is kept for backwards compatibility with old
bookmarked URLs that may contain a smoothing parameter.

Fixes #54

Co-authored-by: Samuel <samuel@knutsen.co>
When a line chart has disableUpdate=true (card not yet visible via
intersection observer), the container may be resized by persistResize
restoring a saved height, or by a full-width class being applied
asynchronously from the store.  The ResizeDetectorDirective's skip(1)
can batch this resize with the initial ResizeObserver event, causing
the only notification to be swallowed.  The chart then holds stale
dimensions from ngAfterViewInit, so the renderer draws curves at one
scale while the interactive overlay positions tooltip dots at another.

Fix: when disableUpdate transitions from true to false, re-read the
DOM dimensions and resize the chart renderer so both the canvas and
the coordinate mapping match the current layout.

Co-authored-by: Samuel <samuel@knutsen.co>
Co-authored-by: Samuel <samuel@knutsen.co>
…card-width-9016

Super-imposed plots card width
The syncPolymerRunColorMap$ effect (added in PR #58) uses
getRunColorMap which transitively depends on the settings feature
selector via getColorPalette. In the karma bundle test, when the
settings feature state is not registered in a particular test's
MockStore, selectSettingsState returns undefined, causing
'TypeError: Cannot read property settings of undefined' in the
memoized selector chain.

Fix 1 - settings_selectors.ts: Wrap the bare createFeatureSelector
with a createSelector that falls back to initialState when the
feature state is undefined. This prevents crashes in any test that
evaluates settings-dependent selectors without registering the
settings feature.

Fix 2 - colorScale.ts: The readColorMap() function threw when
window.__tbRunColorMap was not set. During tests and before the
first NgRx effect fires, this map is absent. Changed to return
null gracefully, skip domain population when the map is absent,
and return a neutral fallback color (#808080) from getColor
instead of throwing when a run is not in the domain.

Co-authored-by: Samuel <samuel@knutsen.co>
Revert the overly-defensive approach. When window.__tbRunColorMap is
not yet seeded (race between runsStore listener and the NgRx effect),
fall back to the original static palette assignment so the domain is
always fully populated and getColor throws for actual programming
errors. When the shared color map IS available but a run is missing,
log console.error so the problem is visible.

Co-authored-by: Samuel <samuel@knutsen.co>
@cursor

cursor Bot commented Feb 20, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

When the shared color map exists but is missing a specific run,
fall back to the palette color for that entry (and log the error).
Previously the identifier was set to undefined, which would cause
getColor to return undefined instead of a hex string.

Co-authored-by: Samuel <samuel@knutsen.co>
@github-actions

github-actions Bot commented Feb 20, 2026

Copy link
Copy Markdown

Preview Deployment

Status ✅ Running
Live Preview https://Demonstrandum-tensorbored-pr-59.hf.space
Space https://huggingface.co/spaces/Demonstrandum/tensorbored-pr-59
Details
  • Wheel: tensorbored_nightly-2.21.0a20260220-py3-none-any.whl
  • Commit: b17a90f
  • Build status: success

cursoragent and others added 4 commits February 20, 2026 18:40
Co-authored-by: Samuel <samuel@knutsen.co>
Co-authored-by: Samuel <samuel@knutsen.co>
Co-authored-by: Samuel <samuel@knutsen.co>
@cursor cursor Bot force-pushed the cursor/cross-tab-state-sync-0c38 branch from f84be53 to 262ba4d Compare February 20, 2026 18:45
@Demonstrandum Demonstrandum marked this pull request as ready for review February 20, 2026 18:46
Co-authored-by: Samuel <samuel@knutsen.co>
@Demonstrandum Demonstrandum merged commit 9859579 into cursor/run-selection-persistence-348f Feb 20, 2026
13 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.

2 participants