refactor: client cleanup, dead code removal, shared exports & Yoga UI#1145
refactor: client cleanup, dead code removal, shared exports & Yoga UI#1145SYMBaiEX wants to merge 25 commits into
Conversation
Delete websocket-manager.ts (~435 LOC) and its barrel export from lib/index.ts. This module was never imported by any consumer — the CharacterSelectScreen uses its own local connection state, and the game client uses Hyperscape's internal world.init() networking.
- Delete types/player.ts — its types were duplicated in game/types/ - Add quantity field to RawEquipmentSlot in game/types/equipment.ts - Create game/types/events.ts as canonical NetworkEvents definition - Update useModalPanels to import from game/types instead of local copy - Re-route types/index.ts barrel to re-export from game/types/ Eliminates type duplication between types/player.ts and game/types/, establishing game/types/ as the single source of truth for client UI type definitions.
Move OSRS-style gold formatting to the canonical location in game/systems/currency/currencyUtils.ts. Remove duplicate definitions from DuelPanel/utils.ts and TradePanel/utils.ts. Update all consumers (StakesScreen, ConfirmScreen, DuelResultModal, StakeGrid, TradePanel) to import from the central currency system.
Resolve naming conflict where SettingsPanel.tsx (1,547 LOC) sat as a sibling file next to the SettingsPanel/ directory containing its sub-components. Move the file into the directory and update index.ts to export from the local file instead of the parent. Fix relative import paths that now need an additional level of traversal.
Remove unused ItemData interface from game/systems/types.ts — it was never imported by any consumer. The client correctly uses getItem() from @hyperscape/shared (152 usages across 58 files) to read from the ITEMS Map populated by DataManager from server manifests. No client-side item definitions found that bypass the manifest system.
Delete useQuestLog.ts (~440 LOC) — it was never imported by any consumer. QuestsPanel manages quest data directly from server events, and the quest selection store (questStore.ts) handles cross-panel quest state. The quest status sync (useQuestStatusSync) correctly bridges server events into the Zustand store for minimap quest pips.
Delete ~950 LOC of unreferenced field editor components. Fields.tsx defined 10 React field components never imported by any consumer. CurvePane.tsx and CurvePreview.tsx were only imported by Fields.tsx.
The exported HintContextType had zero importers. The production HintContext lives in ui/components/Hint.tsx with its own local type.
- Add @internal JSDoc to UI/UIView/UIText node exports and Yoga type exports in shared/index.client.ts — these are engine-internal 3D UI nodes, not for client React/CSS usage - Remove deprecated LRUCache/createMonitoredCache/cacheRegistry barrel aliases from client lib/index.ts (never imported by any consumer)
Remove 389-line file of React prop interfaces (ContextMenuAction, FieldTextProps, WindowProps, etc.) that were never exported from the package or imported by any consumer. The production ContextMenuAction lives in systems/client/interaction/types.ts with the correct shape (handler + priority + styledLabel).
Wire up orphaned shared types that were built for client consumption but never exported from the package. Includes COLORBLIND_PALETTES, COMPLEXITY_MODE_CONFIGS, getCombatLevelColor, and related types.
The ./client export in package.json pointed to framework.d.ts (full server+client types) instead of a client-specific declaration. This meant new exports added to index.client.ts were invisible to client consumers. Fix: build.mjs now copies index.client.d.ts to framework.client.d.ts, and package.json ./client export points to it. Client consumers now see only the types actually exported for client use.
Replace duplicate type definitions in client with re-exports from @hyperscape/shared. Both accessibility.ts (93 LOC) and complexity.ts (127 LOC) were byte-for-byte identical between packages. - Add accessibility and complexity exports to shared/index.ts - Add ComplexityFeatures to shared export lists - Client files now re-export from @hyperscape/shared as single source of truth for COLORBLIND_PALETTES, COMPLEXITY_MODE_CONFIGS, etc.
Create imageCache.ts — module-level singleton that loads and caches HTMLImageElement instances for use with Canvas 2D drawImage(). Used by the upcoming UIImage node to render item icons and game assets. Features: coalesced callbacks per URL, 256-entry LRU eviction, server-safe (typeof window guard), crossOrigin anonymous for CDN.
Create UIImage.ts — renders images in canvas-based UI panels with full Yoga flexbox layout support. Follows UIView/UIText patterns. Features: - Async image loading via imageCache with automatic redraw on load - objectFit modes: fill, contain, cover, none, scale-down - borderRadius clipping via imageRoundRect - Yoga measure function for intrinsic sizing from natural dimensions - All standard layout props: margin, padding, absolute positioning, flexBasis/Grow/Shrink, display toggle The UIImageData type was already defined in types/rendering/nodes.ts — this commit provides the implementation.
Add UIImage + uiimage aliases to nodes/index.ts for factory support
(createNode("uiimage", data)). Export from both index.ts and
index.client.ts entry points. Rebuild generates declarations in
framework.d.ts and framework.client.d.ts.
Extend UIView with overflow clipping and scroll offset properties: - overflow: "visible" | "hidden" | "scroll" (default "visible") - scrollX/scrollY: pixel offsets for scrolled content - Yoga OVERFLOW_HIDDEN prevents parent expansion for clipped content - Canvas ctx.save()/clip()/restore() clips children to view bounds - Scroll offsets subtract from child draw coordinates - roundRect clip path respects borderRadius for rounded scroll areas Enables scrollable lists, inventory grids, and chat panels in the Yoga UI node system.
Create attachScrollHandler() — wires wheel events on a root UI node to a scrollable UIView child. Handles pointer-in-bounds detection, content height calculation from Yoga children, scroll clamping, speed scaling, and optional shift+wheel horizontal scroll. Returns a cleanup function for teardown.
DuelArenaVisualsSystem: import LOBBY_CENTER_X/Z, LOBBY_WIDTH/LENGTH and HOSPITAL_CENTER_X/Z, HOSPITAL_WIDTH/LENGTH from data/arena-layout.ts (constants already existed, just missing import after armor pipeline merge) EquipmentVisualSystem: import getItem from data/items.ts (function already existed, missing import after armor pipeline merge)
PR Review: refactor: client cleanup, dead code removal, shared exports & Yoga UIOverall this is a solid cleanup PR — removing ~2,500 lines of dead code with no broken imports is a win. The new Bug:
|
PR Review: refactor: client cleanup, dead code removal, shared exports & Yoga UIOverall AssessmentSolid cleanup PR that removes ~2,500 lines of dead code and consolidates duplicate types/utilities. The net -1,629 lines is a clear win. The new UIImage node and UIView scroll support are well-structured additions to the engine's Yoga-based UI system. Positive
Issues & Suggestions1.
|
There was a problem hiding this comment.
Pull request overview
This PR performs a broad client/shared cleanup and refactor, consolidates duplicated UI/game types and exports, fixes shared client declaration generation, and extends the Yoga/canvas UI system with image rendering and initial scroll/overflow support.
Changes:
- Remove dead/duplicated client and shared code, and centralize canonical game/UI types.
- Add Yoga UI enhancements:
UIImagenode, shared image cache, andUIViewoverflow/scroll plumbing (+ scroll helper). - Fix
@hyperscape/sharedclienttypesexport to use a client-specific.d.tsoutput.
Reviewed changes
Copilot reviewed 43 out of 43 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/types/rendering/ui.ts | Removed legacy UI-related shared type bundle (dead/duplicated). |
| packages/shared/src/types/rendering/nodes.ts | Adds UIViewData overflow/scroll fields and exports OverflowMode. |
| packages/shared/src/nodes/UIView.ts | Adds overflow clipping + scroll offsets and new proxy/getters/setters. |
| packages/shared/src/nodes/UIImage.ts | Introduces a new Yoga/canvas-backed UI image node with object-fit and async loading. |
| packages/shared/src/nodes/index.ts | Exports UIImage and lowercase alias uiimage for createNode(). |
| packages/shared/src/index.ts | Re-exports UIImage and exposes accessibility/complexity/combat-level utilities from shared. |
| packages/shared/src/index.client.ts | Re-exports UIImage, UI node types, and additional shared utilities; adds internal-notes comments. |
| packages/shared/src/extras/ui/scrollHandler.ts | Adds helper to attach wheel-driven scrolling behavior for UIView overflow scrolling. |
| packages/shared/src/extras/ui/index.ts | Re-exports new UI helpers (imageCache, scrollHandler). |
| packages/shared/src/extras/ui/imageCache.ts | Adds shared image loading/cache layer for canvas UI rendering. |
| packages/shared/scripts/build.mjs | Copies index.client.d.ts → framework.client.d.ts during declaration generation. |
| packages/shared/package.json | Updates exports["./client"].types to ./build/framework.client.d.ts. |
| packages/client/src/ui/types/complexity.ts | Switches to re-export complexity types/constants from @hyperscape/shared. |
| packages/client/src/ui/types/accessibility.ts | Switches to re-export accessibility types/constants from @hyperscape/shared. |
| packages/client/src/ui/components/index.ts | Removes stale comment re: Fields relocation. |
| packages/client/src/types/ui.ts | Removes HintContextType from exported client types. |
| packages/client/src/types/player.ts | Removes legacy player state types/constants (moved to game/types). |
| packages/client/src/types/index.ts | Re-points player state exports to game/types and removes HintContextType. |
| packages/client/src/lib/websocket-manager.ts | Removes deprecated/unreferenced WebSocket manager implementation. |
| packages/client/src/lib/index.ts | Stops exporting the removed WebSocket manager and deprecated cache aliases. |
| packages/client/src/hooks/useModalPanels.ts | Uses canonical NetworkEvents from game/types instead of a local copy. |
| packages/client/src/game/types/index.ts | Adds canonical exports for NetworkEvents + NetworkEventName. |
| packages/client/src/game/types/events.ts | Introduces canonical network event constants + union type. |
| packages/client/src/game/types/equipment.ts | Restores quantity?: number on RawEquipmentSlot in canonical equipment types. |
| packages/client/src/game/systems/types.ts | Removes unused ItemData interface from game systems types. |
| packages/client/src/game/systems/quest/useQuestLog.ts | Removes unused quest log hook implementation. |
| packages/client/src/game/systems/quest/index.ts | Removes exports for the deleted useQuestLog hook. |
| packages/client/src/game/systems/currency/index.ts | Re-exports consolidated formatGoldValue from currency utils. |
| packages/client/src/game/systems/currency/currencyUtils.ts | Adds consolidated formatGoldValue implementation. |
| packages/client/src/game/panels/TradePanel/utils.ts | Removes local formatGoldValue helper (now centralized). |
| packages/client/src/game/panels/TradePanel/TradePanel.tsx | Imports formatGoldValue from the currency system. |
| packages/client/src/game/panels/SettingsPanel/SettingsPanel.tsx | Updates imports after moving SettingsPanel deeper into the directory structure. |
| packages/client/src/game/panels/SettingsPanel/index.ts | Updates re-export to the new local SettingsPanel path. |
| packages/client/src/game/panels/DuelPanel/utils.ts | Removes local formatGoldValue helper (now centralized). |
| packages/client/src/game/panels/DuelPanel/StakesScreen.tsx | Imports formatGoldValue from the currency system. |
| packages/client/src/game/panels/DuelPanel/DuelResultModal.tsx | Imports formatGoldValue from the currency system. |
| packages/client/src/game/panels/DuelPanel/ConfirmScreen.tsx | Imports formatGoldValue from the currency system. |
| packages/client/src/game/panels/DuelPanel/components/StakeGrid.tsx | Imports formatGoldValue from the currency system (path adjusted). |
| packages/client/src/game/Fields.tsx | Removes large, unused Fields UI implementation. |
| packages/client/src/game/CurvePreview.tsx | Removes unused curve preview component. |
| packages/client/src/game/CurvePane.tsx | Removes unused curve editor pane component. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| this.yogaNode.setFlexBasis(this._flexBasis); | ||
| this.yogaNode.setFlexGrow(this._flexGrow); | ||
| this.yogaNode.setFlexShrink(this._flexShrink); | ||
| if (this._overflow === "hidden" || this._overflow === "scroll") { | ||
| this.yogaNode.setOverflow(Yoga.OVERFLOW_HIDDEN); | ||
| } |
There was a problem hiding this comment.
overflow: "scroll" is currently mapped to Yoga.OVERFLOW_HIDDEN, making it indistinguishable from overflow: "hidden" at the Yoga layout level. If scroll is meant to be a distinct mode, set Yoga overflow to OVERFLOW_SCROLL for the "scroll" case and keep OVERFLOW_HIDDEN for "hidden".
| set overflow(value: OverflowMode) { | ||
| if (this._overflow === value) return; | ||
| this._overflow = value; | ||
| if (this.yogaNode) { | ||
| if (value === "hidden" || value === "scroll") { | ||
| this.yogaNode.setOverflow(Yoga.OVERFLOW_HIDDEN); | ||
| } else { | ||
| this.yogaNode.setOverflow(Yoga.OVERFLOW_VISIBLE); | ||
| } |
There was a problem hiding this comment.
The overflow setter also maps both "hidden" and "scroll" to Yoga.OVERFLOW_HIDDEN, so switching to overflow="scroll" won’t update Yoga to a scroll overflow mode. Consider setting Yoga.OVERFLOW_SCROLL when value === "scroll", and OVERFLOW_HIDDEN only for value === "hidden".
| // Sum children's computed heights for total content size | ||
| let contentHeight = 0; | ||
| let contentWidth = 0; | ||
| const childCount = scrollView.yogaNode.getChildCount(); | ||
| for (let i = 0; i < childCount; i++) { | ||
| const child = scrollView.yogaNode.getChild(i); | ||
| contentHeight += | ||
| child.getComputedHeight() + | ||
| child.getComputedMargin(1) + // EDGE_TOP | ||
| child.getComputedMargin(3); // EDGE_BOTTOM | ||
| contentWidth += | ||
| child.getComputedWidth() + | ||
| child.getComputedMargin(0) + // EDGE_LEFT | ||
| child.getComputedMargin(2); // EDGE_RIGHT | ||
| } |
There was a problem hiding this comment.
contentWidth/contentHeight are computed by summing every child’s width/height, which overestimates the scrollable extent for common layouts (e.g., vertical lists will incorrectly accumulate widths). To clamp scrolling correctly, compute the max extent based on each child’s computed position + size (+ margins), rather than summing dimensions.
| // Attach to root UI's onWheel | ||
| const prevHandler = ( | ||
| rootUI as unknown as { _onWheel?: (e: UIWheelEvent) => void } | ||
| )._onWheel; | ||
| (rootUI as unknown as { onWheel: (e: UIWheelEvent) => void }).onWheel = ( | ||
| event: UIWheelEvent, | ||
| ) => { | ||
| prevHandler?.(event); | ||
| handler(event); | ||
| }; | ||
|
|
||
| // Return cleanup function | ||
| return () => { | ||
| ( | ||
| rootUI as unknown as { onWheel: ((e: UIWheelEvent) => void) | undefined } | ||
| ).onWheel = prevHandler; | ||
| }; |
There was a problem hiding this comment.
This chains wheel handlers via a non-existent _onWheel field. UI/Node don’t define _onWheel (or a wheel handler field at all), so prevHandler will always be undefined and cleanup will restore the wrong thing. Use the public onWheel callback (read current rootUI.onWheel, wrap it, then restore it in cleanup), or add a real wheel handler field/API on UI/Node that the input system dispatches to.
| overflow?: "visible" | "hidden" | "scroll"; | ||
| scrollX?: number; | ||
| scrollY?: number; | ||
| } | ||
|
|
||
| export type OverflowMode = "visible" | "hidden" | "scroll"; | ||
|
|
There was a problem hiding this comment.
UIViewData.overflow repeats the string union instead of reusing the exported OverflowMode type right below. To avoid drift, consider changing the property to overflow?: OverflowMode and keeping the union in one place.
| // Re-export commonly used node classes to satisfy API extractor | ||
| // @internal — These are engine-internal 3D UI nodes using Yoga layout. | ||
| // Client UI uses React/CSS, not these canvas-based nodes. | ||
| export { UI } from "./nodes/UI"; | ||
| export { UIView } from "./nodes/UIView"; | ||
| export { UIText } from "./nodes/UIText"; | ||
| export { UIImage } from "./nodes/UIImage"; |
There was a problem hiding this comment.
If the goal is to have API Extractor treat these exports as internal, // @internal line comments typically won’t be recognized. The repo uses JSDoc /** @internal */ elsewhere (e.g. packages/shared/src/utils/rendering/InstancedMeshManager.ts:2845). Consider switching to a JSDoc @internal tag on the export(s) so tooling can exclude them consistently.
| export type { Physics as PhysicsInterface } from "./types/index"; | ||
| // Re-export UI-related types used by UIView/UIText/UI | ||
| // @internal — UI node types for engine-internal 3D UI system (Yoga layout). | ||
| // Client UI uses React/CSS, not these types. | ||
| export type { |
There was a problem hiding this comment.
Same as above: // @internal comments likely won’t be picked up by API Extractor. Consider using a JSDoc /** @internal */ tag on these type exports if they’re intended to be hidden from the public client API surface.
- UIView.copy(): add missing _overflow, _scrollX, _scrollY copies - UIImage setters: guard this.ui before accessing _res to prevent crash when properties are set before mount() - imageCache evictIfNeeded(): replace break with continue + attempt limit to prevent infinite stall when all entries are loading - UIImageData.objectFit: remove `| string` that made the literal union type meaningless - scrollHandler: replace unsafe cast to private _res with public res getter already exposed on UI class - formatGoldValue: restore toFixed(1) for billion tier to preserve decimal display (e.g. "1.5B" instead of "1B")
Code Review: PR #1145Overall AssessmentSolid cleanup PR. Removing ~2,500 lines of verified dead code (WebSocketManager, Fields, CurvePane, CurvePreview, useQuestLog, orphaned ui.ts) is valuable, and the new Yoga UI features (UIImage, scroll, overflow) are well-structured. I confirmed with grep that all deleted modules have zero remaining importers. The type consolidation and client declaration fix are both good catches. A few items below worth addressing before merge: Bugs / Correctness1. When an image fails to load (line 115-118), all coalesced img.addEventListener("error", () => {
cache.set(url, { status: "error" });
for (const cb of entry.callbacks) cb(); // Let callers know *something* happened
entry.callbacks.length = 0;
});Alternatively, add an 2. Line 109-112: If any callback in for (const cb of entry.callbacks) {
try { cb(); } catch (e) { console.error("[imageCache] callback error", e); }
}
entry.callbacks.length = 0;3. Lines 104-111 use Consider either:
4. Lines 136-139: Unlike other properties, Recommend using 5. Line 245: this._margin = isArray(source._margin) ? [...source._margin] : source._margin;Type Safety6. In width?: number | string | null;
height?: number | string | null;But 7. Lines 78-83 use hardcoded numbers (0, 1, 2, 3) for Yoga edge constants instead of importing Performance8. Line 48-52: The eviction loop calls Minor Suggestions (non-blocking)
What Looks Good
Nice work overall. The main items I'd want fixed before merge are #1-5 above (the bugs/correctness section). The rest are improvement suggestions. |
- imageCache: fire callbacks on error (callers can show placeholder), wrap callbacks in try/catch to prevent memory leaks - scrollHandler: replace unsafe `as unknown` casts with public UI.onWheel property, use Yoga.EDGE_* constants instead of magic numbers - UIView: use setters in constructor for overflow/scrollX/scrollY so Yoga overflow mode is applied during construction - UIImage.copy(): deep-copy margin array to prevent shared mutation - UIImageData: remove `| string` from width/height (implementation expects number, string would produce NaN)
PR Review: refactor: client cleanup, dead code removal, shared exports & Yoga UIOverall this is a solid cleanup PR — removing ~2,500 lines of confirmed dead code, consolidating scattered types, and adding useful new Yoga UI primitives. The refactoring commits are well-scoped and the dead code removals are clean (verified zero remaining imports for all deleted files). Below are findings organized by severity. Bugs1. Type mismatch between
onWheel?: (e: { deltaX: number; deltaY: number; shiftKey: boolean }) => void;But
Suggested fix: Change onWheel?: (e: UIWheelEvent) => void;2.
3. Error entries in image cache never expire (low severity)
Performance4. No scroll throttling (medium severity)
5.
6.
Code Quality7. The new currency utilities file (~430 lines) introduces a full multi-currency system ( 8. Both 9.
Security10. No URL validation on image
Test Coverage11. No tests for new features The new
What Looks Good
SummaryThe cleanup and consolidation work is high quality and ready to merge. The new Yoga UI features (UIImage, scroll, image cache) are well-implemented but have a few type mismatches and missing edge-case handling that should be addressed. The |
- UI.onWheel: use UIWheelEvent type instead of inline shape - imageCache: add 30s TTL for error entries so transient CDN failures auto-retry instead of staying broken for the session - scrollHandler: batch wheel deltas with requestAnimationFrame to avoid redundant redraws from high-frequency trackpad events; cancel rAF on cleanup - isEdge: deduplicate into extras/ui/yoga.ts, import from UIView + UIImage - UIImageData: remove | string from width/height (impl expects number)
Code Review: PR #1145 — Client Cleanup, Dead Code Removal, Shared Exports & Yoga UIGood cleanup PR overall — removing ~2,500 lines of dead code and consolidating duplicated types is solid housekeeping. The new UIImage node is substantial and well-structured. A few issues to flag: Bugs / Issues1. Callback accumulation in
Suggestion: guard against duplicate registration, e.g. return early if the entry is already loading and the caller already has a pending callback, or deduplicate callbacks. 2. Scroll handler content size calculation incorrect for row layouts (
3. The old DuelPanel Code Quality4. Unused import in
// UIImage.ts:11
import { every, isArray, isBoolean, isNumber, isString } from "lodash-es";
// ^^^^^ unused5. UIImage doesn't cancel in-flight loads on
6. Scroll handler cleanup has stale-closure risk ( The cleanup function captures Minor / Nits7. Missing The 8. Unlike other setters in the class, the What Looks Good
Nice work on the cleanup. Items 1-3 are worth addressing before merge; the rest are minor. |
1. imageCache: deduplicate callbacks by reference — UIImage now caches its redraw callback as a stable property so includes() prevents accumulation from repeated draw() cycles 2. scrollHandler: compute content size correctly for row layouts — sum widths/max height for row, sum heights/max width for column 3. formatGoldValue: revert to toFixed(1) for all tiers (K/M/B) to match the original DuelPanel behavior exactly 4. UIImage: remove unused `every` lodash import (was for old isEdge) 5. UIImage: clear ui reference on unmount so in-flight imageCache callbacks don't trigger redraws on detached nodes; add _mounted flag 6. scrollHandler: guard cleanup against stale closures — only restore prevHandler if we're still the active handler 7. scrollHandler: normalize deltaMode (lines=16px, pages=viewport) per DOM WheelEvent spec 8. UIView: validate overflow setter input against "visible" | "hidden" | "scroll", warn and default to "visible" for invalid values
Code Review: PR #1145 — Client Cleanup, Dead Code Removal, Shared Exports & Yoga UIOverall this is a well-structured cleanup PR. The dead code removal is thorough and verified — all deleted files have zero remaining references. The type consolidation is correct and maintains API compatibility. The new Yoga UI features (UIImage, scroll, overflow) are well-engineered. A few items worth addressing: Bug: Unused
|
- UIView: remove dead `every` lodash import (moved isEdge to yoga.ts) - UIImage: remove unused `_mounted` field (ui=undefined is the guard) - UIView: overflow setter throws on invalid input (consistent with all other setters in the class) - UIImageData: tighten margin type to [number,number,number,number] tuple (matches isEdge validation) - scrollHandler: remove variable shadowing in deltaMode=2 branch
Code Review: PR #1145 — Client Cleanup, Dead Code Removal, Shared Exports & Yoga UIExcellent cleanup PR. The dead code removal is well-researched (commit messages cite import counts and verify no consumers), the type consolidation is sound, and the new UIImage/scroll features are solid additions. A few findings below, roughly ordered by severity. Issues to Address1. 2. 3. 4. scrollHandler content-size calculation ignores padding 5. scrollHandler recalculates content size on every wheel flush Minor / Suggestions6. imageCache 7. No URL validation in imageCache 8. Missing 9. What Looks Good
Overall this is a well-executed cleanup with good new features. The main actionable item is #1 (measure callback accumulation). Nice work. |
Summary
game/types/formatGoldValueinto currency utilsTest plan
npx tsc --noEmit -p packages/shared/tsconfig.json— 0 errorsnpx tsc --noEmit -p packages/client/tsconfig.json— 0 new errorsnpm run lint— no new errors