Skip to content

Comments

Performance Overlay rework/redesign#3274

Merged
evanpelle merged 4 commits intoopenfrontio:mainfrom
scamiv:perf-overlay-redesign
Feb 23, 2026
Merged

Performance Overlay rework/redesign#3274
evanpelle merged 4 commits intoopenfrontio:mainfrom
scamiv:perf-overlay-redesign

Conversation

@scamiv
Copy link
Contributor

@scamiv scamiv commented Feb 22, 2026

Description:

updates the Performance Overlay to be more usable (draggable/resizable/scrollable), adds tick-level metrics (TPS + per-layer tick timings), and reduces overhead when the overlay is hidden.

UI/UX

  • Overlay layout updated to a fixed, pixel-positioned panel (default near top-left) with a dedicated drag handle.
  • Overlay is touch-draggable (pointer events) and remains usable on small viewports via internal scrolling.
  • Overlay width is resizable with a right-edge handle; width is clamped to viewport bounds.
  • Render/tick layer breakdown sections are collapsible, with headers and “last tick” summaries.

New metrics

  • Adds TPS reporting:
    • Current TPS (ticks in the last 1s).
    • Average TPS over the last ~60s, computed using elapsed time so it’s accurate before a full 60s passes.
  • Adds per-layer tick profiling (“Tick Layers”) alongside render profiling (“Render Layers”).
  • Adds “render-per-tick” metrics so render-layer costs can be understood per simulation tick (frames + per-layer totals).

Performance / overhead

  • Avoids profiling overhead when the overlay is hidden:
    • GameRenderer only calls FrameProfiler.clear()/consume() and per-layer start/end when profiling is enabled.
    • Tick-layer duration tracking is only collected when profiling is enabled.

Settings plumbing

  • UserSettings now dispatches a user-settings-changed CustomEvent on set() / setFloat().
  • The overlay listens for settings.performanceOverlay changes so visibility stays in sync even when toggled outside the overlay.

Implementation notes (by file)

  • src/client/graphics/layers/PerformanceOverlay.ts

    • Adds TPS tracking using a timestamp ring + moving heads (1s / 60s).
    • Adds UI state for collapsibles, drag + resize pointer tracking, and new breakdown models:
      • Render layers: EMA avg/max + per-tick render aggregation.
      • Tick layers: EMA avg/max + last-tick durations.
    • Copy-to-clipboard snapshot now includes TPS, tick layers, and render-per-tick last-tick details.
  • src/client/graphics/GameRenderer.ts

    • Gates render-layer profiling behind FrameProfiler.isEnabled().
    • Accumulates per-render-layer timings across frames and publishes them once per tick via updateRenderPerTickMetrics(...).
    • Measures tick-layer durations (per layer tick() call) and publishes them via updateTickLayerMetrics(...).
  • src/core/game/UserSettings.ts

    • Adds emitChange(key, value) to dispatch user-settings-changed to globalThis (best-effort).
  • resources/lang/en.json

    • Adds/updates performance_overlay.* strings for TPS and the new render/tick layer sections.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

DISCORD_USERNAME

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Walkthrough

Adds gated frame profiling, per-frame and per-tick layer metrics with TPS and 60s average, a draggable/resizable performance overlay showing render/tick breakdowns and copyable snapshots, new localization keys, and emits a user-settings-changed event when settings change.

Changes

Cohort / File(s) Summary
Localization
resources/lang/en.json
Added performance_overlay.tps, performance_overlay.tps_avg_60s, render_layers_table_header, render_layers_summary, tick_layers_header, tick_layers_table_header, tick_layers_summary; changed performance_overlay.layers_header to "Render Layers".
Renderer profiling & aggregation
src/client/graphics/GameRenderer.ts
Gated profiling behind FrameProfiler.isEnabled(), added renderFramesSinceLastTick and renderLayerDurationsSinceLastTick, accumulate per-frame render durations, and flush aggregated metrics to PerformanceOverlay during ticks.
Performance overlay UI & metrics
src/client/graphics/layers/PerformanceOverlay.ts
Added TPS (current + 60s avg), per-layer EMA timings, per-frame and per-tick aggregations, collapsible render/tick sections, draggable positioning, resizable width, copy-to-clipboard snapshot, pointer handlers, extended snapshot payload, and state reset/cleanup.
Settings events
src/core/game/UserSettings.ts
Added private emitChange(key, value) and emit user-settings-changed event after persisting boolean/float settings.
Manifest
package.json
Listed by manifest analyzer (touched).

Sequence Diagram

sequenceDiagram
    participant Renderer as GameRenderer
    participant Profiler as FrameProfiler
    participant Overlay as PerformanceOverlay
    participant Settings as UserSettings

    Renderer->>Profiler: isEnabled()?
    alt profiling enabled
        Profiler-->>Renderer: true
        Renderer->>Renderer: accumulate per-frame render durations
        Renderer->>Overlay: flush render metrics at tick start
        Renderer->>Renderer: accumulate per-tick layer durations
        Renderer->>Overlay: update tick-layer metrics after tick
    else profiling disabled
        Profiler-->>Renderer: false
        Renderer->>Renderer: render/tick without profiling
    end

    Settings->>Settings: set(key, value)
    Settings->>Settings: emitChange(key, value)
    Settings->>Overlay: user-settings-changed event
    Overlay->>Overlay: update UI / persisted state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

Layers hum in timed delight,
TPS counts through day and night.
Drag the pane, resize the view,
Metrics sparkle, fresh and new. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a comprehensive rework and redesign of the Performance Overlay component.
Description check ✅ Passed The description is directly related to the changeset, covering UI/UX updates, new metrics, performance improvements, and detailed implementation notes for each modified file.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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.

Copy link
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: 2

🧹 Nitpick comments (2)
src/client/graphics/layers/PerformanceOverlay.ts (2)

380-390: Global event listener is never removed.

The "user-settings-changed" listener added in init() is never cleaned up. If the component is ever disconnected and reconnected, duplicate listeners will stack. Consider storing a reference and removing it in disconnectedCallback():

Sketch
+ private onSettingsChanged = (event: Event) => {
+   const customEvent = event as CustomEvent<{ key?: string; value?: unknown }>;
+   if (customEvent.detail?.key !== "settings.performanceOverlay") return;
+   const nextVisible = customEvent.detail.value === true;
+   if (this.isVisible === nextVisible) return;
+   this.setVisible(nextVisible);
+ };

  init() {
    ...
-   globalThis.addEventListener("user-settings-changed", (event: Event) => {
-     const customEvent = event as CustomEvent<{ key?: string; value?: unknown }>;
-     if (customEvent.detail?.key !== "settings.performanceOverlay") return;
-     const nextVisible = customEvent.detail.value === true;
-     if (this.isVisible === nextVisible) return;
-     this.setVisible(nextVisible);
-   });
+   globalThis.addEventListener("user-settings-changed", this.onSettingsChanged);
  }

+ disconnectedCallback() {
+   super.disconnectedCallback();
+   globalThis.removeEventListener("user-settings-changed", this.onSettingsChanged);
+ }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PerformanceOverlay.ts` around lines 380 - 390, The
global "user-settings-changed" listener added in PerformanceOverlay.init() never
gets removed, causing duplicate handlers when the component is reconnected; fix
by capturing the listener callback in a property (e.g.,
this._onUserSettingsChanged or similar) when calling globalThis.addEventListener
in init(), use that stored function instead of an inline lambda, and remove it
in PerformanceOverlay.disconnectedCallback() via
globalThis.removeEventListener("user-settings-changed",
this._onUserSettingsChanged) so the handler is registered once and cleaned up on
disconnect.

649-760: Three copies of the same EMA update pattern.

updateLayerStats, updateRenderPerTickMetrics, and updateTickLayerMetrics all do the same work: iterate entries, compute EMA avg, track running max, accumulate total. A shared helper would reduce duplication:

Sketch of shared helper
private updateEmaMap(
  statsMap: Map<string, { avg: number; max: number; last: number; total: number }>,
  durations: Record<string, number>,
  alpha: number = 0.2,
): { name: string; avg: number; max: number; total: number }[] {
  for (const [name, duration] of Object.entries(durations)) {
    const existing = statsMap.get(name);
    if (!existing) {
      statsMap.set(name, { avg: duration, max: duration, last: duration, total: duration });
    } else {
      const avg = existing.avg + alpha * (duration - existing.avg);
      const max = Math.max(existing.max, duration);
      const total = existing.total + duration;
      statsMap.set(name, { avg, max, last: duration, total });
    }
  }
  return Array.from(statsMap.entries())
    .map(([name, s]) => ({ name, avg: s.avg, max: s.max, total: s.total }))
    .sort((a, b) => b.total - a.total);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PerformanceOverlay.ts` around lines 649 - 760,
Multiple methods duplicate the EMA/max/total update logic; extract a private
helper (e.g., updateEmaMap) that accepts a stats Map (this.layerStats /
this.renderPerTickLayerStats / this.tickLayerStats), a durations Record, and
optional alpha and performs the per-entry upsert (avg EMA, max, last, total) and
returns the sorted breakdown array; then replace the bodies of updateLayerStats,
updateRenderPerTickMetrics, and updateTickLayerMetrics to call updateEmaMap
(assigning its return to this.layerBreakdown or this.tickLayerBreakdown where
those methods previously computed breakdowns) and keep the other per-method
bookkeeping (renderLastTickFrameCount, renderLastTickLayerTotalMs,
tickLayerLastCount, tickLayerLastTotalMs, etc.) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Line 1038: The hardcoded tooltip strings for the collapse/expand button in
PerformanceOverlay.ts need i18n: replace the raw "Collapse" / "Expand" used in
the button title (driven by this.renderLayersExpanded) with calls to the
translation helper (translateText) so the title reads something like
translateText("Collapse") or translateText("Expand") based on
this.renderLayersExpanded; update both occurrences in the PerformanceOverlay
component where the title attribute is set and ensure any nearby tooltip usage
(same property) follows the same pattern.
- Around line 928-941: The render clamping allows clampedY to reach
viewportHeight (pushing the overlay fully off-screen) while the drag handler
caps Y at viewportHeight - 100; fix the clamping in PerformanceOverlay by using
the same upper bound as the drag handler: change the clamp for this.position.y
so it uses Math.min(this.position.y, viewportHeight - 100) instead of
Math.min(this.position.y, viewportHeight), keeping the existing Math.max(margin,
...) logic and referencing this.position.y and clampedY.

---

Nitpick comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Around line 380-390: The global "user-settings-changed" listener added in
PerformanceOverlay.init() never gets removed, causing duplicate handlers when
the component is reconnected; fix by capturing the listener callback in a
property (e.g., this._onUserSettingsChanged or similar) when calling
globalThis.addEventListener in init(), use that stored function instead of an
inline lambda, and remove it in PerformanceOverlay.disconnectedCallback() via
globalThis.removeEventListener("user-settings-changed",
this._onUserSettingsChanged) so the handler is registered once and cleaned up on
disconnect.
- Around line 649-760: Multiple methods duplicate the EMA/max/total update
logic; extract a private helper (e.g., updateEmaMap) that accepts a stats Map
(this.layerStats / this.renderPerTickLayerStats / this.tickLayerStats), a
durations Record, and optional alpha and performs the per-entry upsert (avg EMA,
max, last, total) and returns the sorted breakdown array; then replace the
bodies of updateLayerStats, updateRenderPerTickMetrics, and
updateTickLayerMetrics to call updateEmaMap (assigning its return to
this.layerBreakdown or this.tickLayerBreakdown where those methods previously
computed breakdowns) and keep the other per-method bookkeeping
(renderLastTickFrameCount, renderLastTickLayerTotalMs, tickLayerLastCount,
tickLayerLastTotalMs, etc.) intact.

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Feb 22, 2026
@scamiv scamiv marked this pull request as ready for review February 22, 2026 19:51
@scamiv scamiv requested a review from a team as a code owner February 22, 2026 19:51
@evanpelle evanpelle added this to the v30 milestone Feb 22, 2026
fix the
clamping in PerformanceOverlay by using the same upper bound as the drag
handler

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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

♻️ Duplicate comments (1)
src/client/graphics/layers/PerformanceOverlay.ts (1)

1038-1038: ⚠️ Potential issue | 🟡 Minor

Hardcoded "Collapse" / "Expand" tooltip strings still bypass i18n.

Despite the prior fix confirmation, both the render-layers collapse button (Line 1038) and the tick-layers collapse button (Line 1100) still use raw English literals instead of translateText. Both need the same treatment:

🌐 Proposed fix
-                    title=${this.renderLayersExpanded ? "Collapse" : "Expand"}
+                    title=${this.renderLayersExpanded
+                      ? translateText("performance_overlay.collapse")
+                      : translateText("performance_overlay.expand")}
-                    title=${this.tickLayersExpanded ? "Collapse" : "Expand"}
+                    title=${this.tickLayersExpanded
+                      ? translateText("performance_overlay.collapse")
+                      : translateText("performance_overlay.expand")}

Also applies to: 1100-1100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PerformanceOverlay.ts` at line 1038, The
collapse/expand button tooltips in PerformanceOverlay.ts are using hardcoded
English strings; update both occurrences (the render-layers button using
this.renderLayersExpanded and the tick-layers button using the similar expanded
flag) to call translateText and pass the appropriate key/text (e.g.,
translateText("Collapse") or translateText("Expand")) instead of raw literals so
the title attribute becomes title={this.renderLayersExpanded ?
translateText("Collapse") : translateText("Expand")} (and the analogous
expression for the tick-layers control).
🧹 Nitpick comments (1)
src/client/graphics/layers/PerformanceOverlay.ts (1)

649-760: Identical EMA update pattern duplicated across three methods.

updateLayerStats, updateRenderPerTickMetrics, and updateTickLayerMetrics all share the same Map-based EMA logic (init on first entry, then avg = prev + α*(new - prev), running max and total). Extracting a small helper keeps the smoothing factor and logic in one place:

♻️ Proposed refactor
+  private static updateEmaMap(
+    map: Map<string, { avg: number; max: number; last: number; total: number }>,
+    durations: Record<string, number>,
+    alpha = 0.2,
+  ): void {
+    for (const [name, duration] of Object.entries(durations)) {
+      const e = map.get(name);
+      if (!e) {
+        map.set(name, { avg: duration, max: duration, last: duration, total: duration });
+      } else {
+        map.set(name, {
+          avg: e.avg + alpha * (duration - e.avg),
+          max: Math.max(e.max, duration),
+          last: duration,
+          total: e.total + duration,
+        });
+      }
+    }
+  }

Then each method becomes a single call:

-      Object.entries(layerDurations).forEach(([name, duration]) => {
-        const existing = this.layerStats.get(name);
-        if (!existing) { ... } else { ... }
-      });
+      PerformanceOverlay.updateEmaMap(this.layerStats, layerDurations);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PerformanceOverlay.ts` around lines 649 - 760, The
EMA/map-update logic is duplicated in updateLayerStats,
updateRenderPerTickMetrics, and updateTickLayerMetrics; extract a single helper
(e.g., updateStatsEntry or applyEmaToMap) that accepts the target Map
(layerStats, renderPerTickLayerStats, tickLayerStats), the entry name, duration,
and alpha and performs the init-or-update (avg, max, last, total) logic, then
replace the repeated blocks in those three methods with calls to that helper to
keep the smoothing factor and update behavior centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Around line 380-390: The global "user-settings-changed" listener added inside
PerformanceOverlay.init() is created as an anonymous function and never removed,
causing leaks and duplicate listeners on repeated init(); fix by defining the
handler as a class field (e.g., this._onUserSettingsChanged = (event: Event) =>
{ ... } or a bound method) and use that field when calling
globalThis.addEventListener in init(); then remove it in
PerformanceOverlay.disconnectedCallback() via
globalThis.removeEventListener("user-settings-changed",
this._onUserSettingsChanged), and ensure init() avoids re-registering the same
handler (e.g., check if this._onUserSettingsChanged is already set or remove
before add).

---

Duplicate comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Line 1038: The collapse/expand button tooltips in PerformanceOverlay.ts are
using hardcoded English strings; update both occurrences (the render-layers
button using this.renderLayersExpanded and the tick-layers button using the
similar expanded flag) to call translateText and pass the appropriate key/text
(e.g., translateText("Collapse") or translateText("Expand")) instead of raw
literals so the title attribute becomes title={this.renderLayersExpanded ?
translateText("Collapse") : translateText("Expand")} (and the analogous
expression for the tick-layers control).

---

Nitpick comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Around line 649-760: The EMA/map-update logic is duplicated in
updateLayerStats, updateRenderPerTickMetrics, and updateTickLayerMetrics;
extract a single helper (e.g., updateStatsEntry or applyEmaToMap) that accepts
the target Map (layerStats, renderPerTickLayerStats, tickLayerStats), the entry
name, duration, and alpha and performs the init-or-update (avg, max, last,
total) logic, then replace the repeated blocks in those three methods with calls
to that helper to keep the smoothing factor and update behavior centralized.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2d5164 and 4f361c0.

📒 Files selected for processing (1)
  • src/client/graphics/layers/PerformanceOverlay.ts

Copy link
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

♻️ Duplicate comments (1)
src/client/graphics/layers/PerformanceOverlay.ts (1)

1108-1115: Hardcoded "Collapse" / "Expand" tooltip strings still present.

Both collapse buttons still use raw English strings for their title attributes despite a previous confirmed fix. These should use translateText(...).

Also applies to: 1170-1177

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PerformanceOverlay.ts` around lines 1108 - 1115,
Replace the hardcoded English tooltip strings used in the collapse buttons with
the translation helper: update the title binding on the button that uses
renderLayersExpanded and toggleRenderLayersExpanded so it uses
translateText("Collapse") when this.renderLayersExpanded is true and
translateText("Expand") when false (same change for the duplicate button at the
other occurrence); locate the button element (class "collapse-button", click
handler toggleRenderLayersExpanded, and property renderLayersExpanded) and swap
the raw "Collapse"/"Expand" strings for translateText(...) accordingly.
🧹 Nitpick comments (1)
src/client/graphics/layers/PerformanceOverlay.ts (1)

550-580: Direct DOM mutation of --overlay-width is unsafe when position changes mid-resize.

Lit compares against the previously bound value, not the live DOM value — so "if the DOM value changes, but the value set through lit-html bindings hasn't, lit-html won't know to update the DOM value and will leave it alone."

That means style.setProperty("--overlay-width", …) survives Lit re-renders only while this.position is unchanged. If a simultaneous touch-drag fires and updates this.position, Lit commits a new style string (with empty overlayWidthStyle because overlayWidthPx is still null) via setAttribute, wiping the CSS variable and snapping the overlay back to its default width mid-resize.

Prefer updating this.overlayWidthPx directly and drop the direct DOM write:

♻️ Proposed refactor
  private onResizePointerMove = (e: PointerEvent) => {
    if (!this.resizeState || e.pointerId !== this.resizeState.pointerId) return;

    const margin = 8;
    const viewportWidth = window.innerWidth;
    const left = Math.max(margin, Math.min(this.position.x, viewportWidth));
    const maxWidthPx = Math.max(120, viewportWidth - left - margin);
    const minWidthPx = Math.min(260, maxWidthPx);

    const delta = e.clientX - this.resizeState.startClientX;
    const nextWidth = this.resizeState.startWidthPx + delta;
    const clamped = Math.max(minWidthPx, Math.min(maxWidthPx, nextWidth));
    this.resizeState.pendingWidthPx = clamped;

-    const overlay = this.renderRoot.querySelector<HTMLElement>(
-      ".performance-overlay",
-    );
-    overlay?.style.setProperty("--overlay-width", `${clamped}px`);
+    this.overlayWidthPx = clamped;
  };

  private onResizePointerUp = (e: PointerEvent) => {
    if (!this.resizeState || e.pointerId !== this.resizeState.pointerId) return;

    globalThis.removeEventListener("pointermove", this.onResizePointerMove);
    globalThis.removeEventListener("pointerup", this.onResizePointerUp);
    globalThis.removeEventListener("pointercancel", this.onResizePointerUp);

-    this.overlayWidthPx = this.resizeState.pendingWidthPx;
    this.resizeState = null;
    this.requestUpdate();
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/PerformanceOverlay.ts` around lines 550 - 580, The
direct DOM write in onResizePointerMove
(overlay?.style.setProperty("--overlay-width", …)) is unsafe; instead of
mutating the element, update the component state so Lit's bindings stay
authoritative: set this.overlayWidthPx = clamped (in addition to
this.resizeState.pendingWidthPx) and call this.requestUpdate() so the bound
overlayWidthStyle is updated during the drag; remove the
renderRoot.querySelector style.setProperty call entirely so onResizePointerMove
and onResizePointerUp consistently rely on overlayWidthPx and resizeState.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Around line 1078-1083: The color calculation uses 1000 / this.frameTime which
is Infinity when frameTime === 0, causing getPerformanceColor(Infinity) to
return "performance-good"; update the render logic in PerformanceOverlay so you
compute a safe fps value (e.g. const fps = this.frameTime > 0 ? 1000 /
this.frameTime : 0) or choose a neutral class when this.frameTime === 0, then
pass that safe value to getPerformanceColor and render the ms label unchanged
(this.frameTime) so the initial "0ms" shows a neutral style instead of green;
reference getPerformanceColor and the frameTime property in the component.

---

Duplicate comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Around line 1108-1115: Replace the hardcoded English tooltip strings used in
the collapse buttons with the translation helper: update the title binding on
the button that uses renderLayersExpanded and toggleRenderLayersExpanded so it
uses translateText("Collapse") when this.renderLayersExpanded is true and
translateText("Expand") when false (same change for the duplicate button at the
other occurrence); locate the button element (class "collapse-button", click
handler toggleRenderLayersExpanded, and property renderLayersExpanded) and swap
the raw "Collapse"/"Expand" strings for translateText(...) accordingly.

---

Nitpick comments:
In `@src/client/graphics/layers/PerformanceOverlay.ts`:
- Around line 550-580: The direct DOM write in onResizePointerMove
(overlay?.style.setProperty("--overlay-width", …)) is unsafe; instead of
mutating the element, update the component state so Lit's bindings stay
authoritative: set this.overlayWidthPx = clamped (in addition to
this.resizeState.pendingWidthPx) and call this.requestUpdate() so the bound
overlayWidthStyle is updated during the drag; remove the
renderRoot.querySelector style.setProperty call entirely so onResizePointerMove
and onResizePointerUp consistently rely on overlayWidthPx and resizeState.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f361c0 and b62aad2.

📒 Files selected for processing (1)
  • src/client/graphics/layers/PerformanceOverlay.ts

@github-project-automation github-project-automation bot moved this from Development to Final Review in OpenFront Release Management Feb 23, 2026
@evanpelle evanpelle merged commit 4b917c4 into openfrontio:main Feb 23, 2026
6 of 7 checks passed
@github-project-automation github-project-automation bot moved this from Final Review to Complete in OpenFront Release Management Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

3 participants