@almadar/ui 5.12.0 → 5.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8627,7 +8627,7 @@ var init_MapView = __esm({
8627
8627
  shadowSize: [41, 41]
8628
8628
  });
8629
8629
  L.Marker.prototype.options.icon = defaultIcon;
8630
- const { useEffect: useEffect72, useRef: useRef66, useCallback: useCallback128, useState: useState110 } = React80__default;
8630
+ const { useEffect: useEffect72, useRef: useRef66, useCallback: useCallback128, useState: useState111 } = React80__default;
8631
8631
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
8632
8632
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
8633
8633
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -8672,7 +8672,7 @@ var init_MapView = __esm({
8672
8672
  showAttribution = true
8673
8673
  }) {
8674
8674
  const eventBus = useEventBus2();
8675
- const [clickedPosition, setClickedPosition] = useState110(null);
8675
+ const [clickedPosition, setClickedPosition] = useState111(null);
8676
8676
  const handleMapClick = useCallback128((lat, lng) => {
8677
8677
  if (showClickedPin) {
8678
8678
  setClickedPosition({ lat, lng });
@@ -16378,6 +16378,7 @@ function CalendarGrid({
16378
16378
  swipeRightEvent,
16379
16379
  dayWindow = "auto"
16380
16380
  }) {
16381
+ const evs = Array.isArray(events2) ? events2 : events2 ? [events2] : [];
16381
16382
  const eventBus = useEventBus();
16382
16383
  const longPressTimer = useRef(null);
16383
16384
  const resolvedWeekStart = useMemo(
@@ -16426,7 +16427,7 @@ function CalendarGrid({
16426
16427
  [onEventClick]
16427
16428
  );
16428
16429
  const eventsForDayCount = useCallback(
16429
- (day) => events2.filter(
16430
+ (day) => evs.filter(
16430
16431
  (ev) => new Date(ev.startTime).toDateString() === day.toDateString()
16431
16432
  ).length,
16432
16433
  [events2]
@@ -16541,7 +16542,7 @@ function CalendarGrid({
16541
16542
  }
16542
16543
  ) }),
16543
16544
  visibleDays.map((day) => {
16544
- const slotEvents = events2.filter(
16545
+ const slotEvents = evs.filter(
16545
16546
  (ev) => eventInSlot(ev, day, time)
16546
16547
  );
16547
16548
  const isToday = day.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
@@ -25777,8 +25778,16 @@ function GameCanvas2D({
25777
25778
  drawEventRef.current = drawEvent;
25778
25779
  const emitRef = React80.useRef(emit);
25779
25780
  emitRef.current = emit;
25781
+ const assetBaseUrlRef = React80.useRef(assetBaseUrl);
25782
+ assetBaseUrlRef.current = assetBaseUrl;
25783
+ const backgroundImageRef = React80.useRef(backgroundImage);
25784
+ backgroundImageRef.current = backgroundImage;
25785
+ const widthRef = React80.useRef(width);
25786
+ widthRef.current = width;
25787
+ const heightRef = React80.useRef(height);
25788
+ heightRef.current = height;
25780
25789
  const loadImage = React80.useCallback((url) => {
25781
- const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
25790
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrlRef.current}${url}`;
25782
25791
  const cached = imageCache.current.get(fullUrl);
25783
25792
  if (cached?.complete && cached.naturalWidth > 0) return cached;
25784
25793
  if (!cached) {
@@ -25788,7 +25797,7 @@ function GameCanvas2D({
25788
25797
  imageCache.current.set(fullUrl, img);
25789
25798
  }
25790
25799
  return null;
25791
- }, [assetBaseUrl]);
25800
+ }, []);
25792
25801
  React80.useEffect(() => {
25793
25802
  const canvas = canvasRef.current;
25794
25803
  if (!canvas) return;
@@ -25810,10 +25819,10 @@ function GameCanvas2D({
25810
25819
  if (tickEventRef.current) {
25811
25820
  emitRef.current(tickEventRef.current, { dt, frame });
25812
25821
  }
25813
- if (backgroundImage) {
25814
- const bgImg = loadImage(backgroundImage);
25822
+ if (backgroundImageRef.current) {
25823
+ const bgImg = loadImage(backgroundImageRef.current);
25815
25824
  if (bgImg) {
25816
- ctx.drawImage(bgImg, 0, 0, width, height);
25825
+ ctx.drawImage(bgImg, 0, 0, widthRef.current, heightRef.current);
25817
25826
  }
25818
25827
  }
25819
25828
  onDrawRef.current?.(ctx, frame);
@@ -25829,7 +25838,7 @@ function GameCanvas2D({
25829
25838
  running = false;
25830
25839
  cancelAnimationFrame(rafRef.current);
25831
25840
  };
25832
- }, [fps]);
25841
+ }, [fps, loadImage]);
25833
25842
  return /* @__PURE__ */ jsx(Box, { className: cn("inline-block", className), children: /* @__PURE__ */ jsx(
25834
25843
  "canvas",
25835
25844
  {
@@ -27023,15 +27032,22 @@ function PlatformerCanvas({
27023
27032
  const eventBus = useEventBus();
27024
27033
  const keysRef = useRef(/* @__PURE__ */ new Set());
27025
27034
  const imageCache = useRef(/* @__PURE__ */ new Map());
27035
+ const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
27026
27036
  const loadImage = useCallback((url) => {
27027
27037
  const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
27028
27038
  const cached = imageCache.current.get(fullUrl);
27029
- if (cached?.complete && cached.naturalWidth > 0) return cached;
27039
+ if (cached?.complete && cached.naturalWidth > 0) {
27040
+ if (!loadedImages.has(fullUrl)) {
27041
+ setLoadedImages((prev) => new Set(prev).add(fullUrl));
27042
+ }
27043
+ return cached;
27044
+ }
27030
27045
  if (!cached) {
27031
27046
  const img = new Image();
27032
27047
  img.crossOrigin = "anonymous";
27033
27048
  img.src = fullUrl;
27034
27049
  img.onload = () => {
27050
+ setLoadedImages((prev) => new Set(prev).add(fullUrl));
27035
27051
  updateAssetStatus(fullUrl, "loaded");
27036
27052
  };
27037
27053
  img.onerror = () => {
@@ -27041,7 +27057,7 @@ function PlatformerCanvas({
27041
27057
  updateAssetStatus(fullUrl, "pending");
27042
27058
  }
27043
27059
  return null;
27044
- }, [assetBaseUrl]);
27060
+ }, [assetBaseUrl, loadedImages]);
27045
27061
  useEffect(() => {
27046
27062
  if (typeof window === "undefined") return;
27047
27063
  const canvas = canvasRef.current;
@@ -27223,7 +27239,7 @@ function PlatformerCanvas({
27223
27239
  ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
27224
27240
  ctx.fill();
27225
27241
  }
27226
- });
27242
+ }, [player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, loadedImages]);
27227
27243
  return /* @__PURE__ */ jsx(
27228
27244
  "canvas",
27229
27245
  {
@@ -29274,7 +29290,7 @@ function useSafeEventBus10() {
29274
29290
  }
29275
29291
  }
29276
29292
  function SortableListInner({
29277
- items: initialItems = EMPTY_ITEMS,
29293
+ items: initialItemsProp = EMPTY_ITEMS,
29278
29294
  renderItem,
29279
29295
  reorderEvent,
29280
29296
  reorderPayload,
@@ -29282,6 +29298,7 @@ function SortableListInner({
29282
29298
  className
29283
29299
  }) {
29284
29300
  const eventBus = useSafeEventBus10();
29301
+ const initialItems = Array.isArray(initialItemsProp) ? initialItemsProp : initialItemsProp ? [initialItemsProp] : [];
29285
29302
  const handleReorder = useCallback(
29286
29303
  (fromIndex, toIndex, item) => {
29287
29304
  eventBus.emit(`UI:${reorderEvent}`, {
@@ -32778,9 +32795,10 @@ var init_ReplyTree = __esm({
32778
32795
  showActions = true,
32779
32796
  className
32780
32797
  }) => {
32798
+ const nodeList = Array.isArray(nodes) ? nodes : nodes ? [nodes] : [];
32781
32799
  const [collapsedSet, setCollapsedSet] = useState(() => {
32782
32800
  const acc = /* @__PURE__ */ new Set();
32783
- collectInitiallyCollapsed(nodes, acc);
32801
+ collectInitiallyCollapsed(nodeList, acc);
32784
32802
  return acc;
32785
32803
  });
32786
32804
  const toggleCollapse = useCallback((id) => {
@@ -32794,10 +32812,10 @@ var init_ReplyTree = __esm({
32794
32812
  return next;
32795
32813
  });
32796
32814
  }, []);
32797
- if (nodes.length === 0) {
32815
+ if (nodeList.length === 0) {
32798
32816
  return /* @__PURE__ */ jsx(Box, { className: cn("text-sm text-muted-foreground", className), children: "No replies yet." });
32799
32817
  }
32800
- return /* @__PURE__ */ jsx(Box, { className: cn("flex flex-col gap-2 min-w-0", className), children: nodes.map((node) => /* @__PURE__ */ jsx(
32818
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex flex-col gap-2 min-w-0", className), children: nodeList.map((node) => /* @__PURE__ */ jsx(
32801
32819
  ReplyTreeNode,
32802
32820
  {
32803
32821
  node,
@@ -6,7 +6,7 @@
6
6
  * Composes DayCell and TimeSlotCell atoms into a 7-day grid.
7
7
  */
8
8
  import React from "react";
9
- import type { EventEmit, EventPayload } from "@almadar/core";
9
+ import type { EventEmit, EventPayload, EntityCollection } from "@almadar/core";
10
10
  export interface CalendarEvent {
11
11
  id: string;
12
12
  title: string;
@@ -26,7 +26,7 @@ export interface CalendarGridProps {
26
26
  /** Time slot labels (defaults to 09:00-17:00) */
27
27
  timeSlots?: string[];
28
28
  /** Events to display on the grid */
29
- events?: CalendarEvent[];
29
+ events?: EntityCollection<CalendarEvent>;
30
30
  /** Called when a time slot is clicked */
31
31
  onSlotClick?: (day: Date, time: string) => void;
32
32
  /** Called when a day header is clicked */
@@ -11,7 +11,7 @@
11
11
  * Uses atoms only internally: Box, VStack, HStack, Typography, Badge, Button, Icon.
12
12
  */
13
13
  import React from 'react';
14
- import type { EntityRow, EventKey } from '@almadar/core';
14
+ import type { EntityRow, EventKey, EntityCollection } from '@almadar/core';
15
15
  import { type DataDndProps } from './useDataDnd';
16
16
  export interface DataGridField {
17
17
  /** Entity field name (dot-notation supported) */
@@ -54,8 +54,14 @@ export interface DataGridProps<T extends EntityRow = EntityRow> extends DataDndP
54
54
  * without widening. The generic `T` lets consumers pass a narrower
55
55
  * entity (e.g. `CartItem`) and have the `children` render function
56
56
  * receive cards typed to that exact shape.
57
+ *
58
+ * Declared as the pattern's data INLET via `EntityCollection<T>` (the inlet
59
+ * half of the circuit, symmetric with the `EventKey` outlet props below):
60
+ * pattern-sync tags it `kind:"entity", cardinality:"collection"` so consumers
61
+ * bind the domain entity without name-matching the prop. Structurally still
62
+ * `T | readonly T[]` — see the brand's doc.
57
63
  */
58
- entity: T | readonly T[];
64
+ entity: EntityCollection<T>;
59
65
  /**
60
66
  * Field definitions for rendering each card. The pattern contract in
61
67
  * `@almadar/patterns` documents `columns` as the wire-format alias the
@@ -11,7 +11,7 @@
11
11
  * Uses atoms only internally: Box, VStack, HStack, Typography, Badge, Button, Icon.
12
12
  */
13
13
  import React from 'react';
14
- import type { EntityRow, EventKey } from "@almadar/core";
14
+ import type { EntityRow, EntityCollection, EventKey } from "@almadar/core";
15
15
  import { type DataDndProps } from './useDataDnd';
16
16
  export interface DataListField {
17
17
  /** Entity field name (dot-notation supported) */
@@ -45,7 +45,7 @@ export interface DataListProps<T extends EntityRow = EntityRow> extends DataDndP
45
45
  * entity (e.g. `CartItem`) and have the `children` render function
46
46
  * receive items of that exact shape.
47
47
  */
48
- entity: T | readonly T[];
48
+ entity: EntityCollection<T>;
49
49
  /**
50
50
  * Field definitions for rendering each row. The pattern contract in
51
51
  * `@almadar/patterns` documents `columns` as the wire-format alias the
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import type { EventEmit, EventPayloadValue } from "@almadar/core";
2
+ import type { EventEmit, EntityCollection } from "@almadar/core";
3
3
  export type CanvasItemStatus = 'empty' | 'seated' | 'ordered' | 'awaiting-bill' | 'cleaning';
4
4
  export type CanvasItemShape = 'round' | 'rectangle' | 'square';
5
5
  export interface CanvasItem {
@@ -20,7 +20,7 @@ export interface PositionedCanvasProps {
20
20
  * molecule narrows non-array values to `[]` and validates element shape at
21
21
  * render time via the `id` / `x` / `y` guards.
22
22
  */
23
- items: readonly CanvasItem[] | EventPayloadValue;
23
+ items: EntityCollection<CanvasItem>;
24
24
  width?: number;
25
25
  height?: number;
26
26
  selectedId?: string | null;
@@ -5,7 +5,7 @@
5
5
  * Composes the VoteStack molecule + Avatar/Typography/Button atoms.
6
6
  */
7
7
  import React from "react";
8
- import type { EventEmit } from "@almadar/core";
8
+ import type { EventEmit, EntityCollection } from "@almadar/core";
9
9
  import { type VoteValue } from "../molecules/VoteStack";
10
10
  export interface ReplyNode {
11
11
  id: string;
@@ -19,7 +19,7 @@ export interface ReplyNode {
19
19
  collapsed?: boolean;
20
20
  }
21
21
  export interface ReplyTreeProps {
22
- nodes: ReplyNode[];
22
+ nodes: EntityCollection<ReplyNode>;
23
23
  maxDepth?: number;
24
24
  onVote?: (nodeId: string, vote: VoteValue) => void;
25
25
  onReply?: (parentNodeId: string) => void;
@@ -6,9 +6,9 @@
6
6
  * Shows a drop indicator line at the target position during drag.
7
7
  */
8
8
  import React from 'react';
9
- import type { EventKey, EventPayload, EventPayloadValue } from "@almadar/core";
9
+ import type { EntityCollection, EventKey, EventPayload, EventPayloadValue } from "@almadar/core";
10
10
  export interface SortableListProps<T extends EventPayloadValue = EventPayload> {
11
- items: T[];
11
+ items: EntityCollection<T>;
12
12
  renderItem: (item: T, index: number) => React.ReactNode;
13
13
  reorderEvent: EventKey;
14
14
  reorderPayload?: EventPayload;
@@ -12,7 +12,7 @@
12
12
  * Icon, Checkbox, Divider.
13
13
  */
14
14
  import React from 'react';
15
- import type { EntityRow, EventKey } from '@almadar/core';
15
+ import type { EntityRow, EventKey, EntityCollection } from '@almadar/core';
16
16
  import { type DataDndProps } from './useDataDnd';
17
17
  export interface TableViewColumn {
18
18
  /** Stable column key (React key + default value lookup). */
@@ -52,7 +52,7 @@ export interface TableViewItemAction {
52
52
  }
53
53
  export interface TableViewProps<T extends EntityRow = EntityRow> extends DataDndProps {
54
54
  /** Schema entity data — single record or collection. */
55
- entity: T | readonly T[];
55
+ entity: EntityCollection<T>;
56
56
  /** Column definitions. The compiler emits `columns`; `fields` is the alias. */
57
57
  columns?: readonly TableViewColumn[];
58
58
  /** Alias for `columns`. */
@@ -7,6 +7,7 @@
7
7
  * See EntityDisplayProps in ./types.ts for base prop contract.
8
8
  */
9
9
  import React from "react";
10
+ import type { EntityRecord, EntityRow } from "@almadar/core";
10
11
  import type { LucideIcon } from "lucide-react";
11
12
  import type { EntityDisplayProps } from "./types";
12
13
  export interface DetailField {
@@ -44,6 +45,8 @@ export type FieldDef = string | {
44
45
  type: string;
45
46
  };
46
47
  export interface DetailPanelProps extends EntityDisplayProps {
48
+ /** RECORD-cardinality override: renders ONE record (see body collapse below). */
49
+ entity?: EntityRecord<EntityRow>;
47
50
  title?: string;
48
51
  subtitle?: string;
49
52
  status?: {
@@ -10,9 +10,11 @@
10
10
  * - Never listens to events
11
11
  */
12
12
  import React from 'react';
13
+ import type { EntityRecord } from '@almadar/core';
13
14
  import type { EntityDisplayProps } from './types';
14
15
  import type { HeroEntity } from './marketing-types';
15
16
  export interface HeroOrganismProps extends EntityDisplayProps<HeroEntity> {
17
+ entity?: EntityRecord<HeroEntity>;
16
18
  children?: React.ReactNode;
17
19
  }
18
20
  export declare const HeroOrganism: React.FC<HeroOrganismProps>;
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import React from "react";
14
14
  import type { EventKey } from "@almadar/core";
15
- import type { EntityRow } from "@almadar/core";
15
+ import type { EntityCollection, EntityRow } from "@almadar/core";
16
16
  import type { LucideIcon } from "lucide-react";
17
17
  export type TimelineItemStatus = "complete" | "active" | "pending" | "error";
18
18
  /**
@@ -55,7 +55,7 @@ export interface TimelineProps {
55
55
  * TimelineItem fields (`icon`, callbacks) cannot round-trip through the
56
56
  * event bus, so decorative stories that need them pass `items` directly.
57
57
  */
58
- entity?: EntityRow | readonly EntityRow[];
58
+ entity?: EntityCollection<EntityRow>;
59
59
  /** Timeline title */
60
60
  title?: string;
61
61
  /** Timeline items */
@@ -17,9 +17,12 @@
17
17
  * - Listens: UI:BOOK_START, UI:BOOK_NAVIGATE, UI:BOOK_PAGE_PREV/NEXT, UI:BOOK_PRINT, UI:BOOK_SHOW_TOC
18
18
  */
19
19
  import React from 'react';
20
+ import type { EntityRecord, EntityRow } from '@almadar/core';
20
21
  import type { EntityDisplayProps } from '../types';
21
22
  import type { BookFieldMap } from './types';
22
23
  export interface BookViewerProps extends EntityDisplayProps {
24
+ /** Renders ONE record (the book), not a collection */
25
+ entity?: EntityRecord<EntityRow>;
23
26
  /** Initial page index (default: 0 = cover) */
24
27
  initialPage?: number;
25
28
  /** Field name translation map — a BookFieldMap object or locale key ("ar") */
@@ -7,7 +7,7 @@
7
7
  * Exception: Form manages local `formData` state for field input tracking.
8
8
  * This is the ONE allowed exception — documented here.
9
9
  */
10
- import type { EntityRow } from '@almadar/core';
10
+ import type { EntityRow, EntityInlet } from '@almadar/core';
11
11
  export declare const EntityDisplayEvents: {
12
12
  readonly SORT: "SORT";
13
13
  readonly PAGINATE: "PAGINATE";
@@ -61,7 +61,7 @@ export interface EntityDisplayProps<T extends EntityRow = EntityRow> {
61
61
  * extend `EntityRow`. They surface `EntityDisplayProps<T>` constraint errors.
62
62
  * Tracked as a Phase 7 follow-up in `docs/Almadar_Entity_V2_Plan.md` §10.
63
63
  */
64
- entity?: T | readonly T[];
64
+ entity?: EntityInlet<T>;
65
65
  /** Additional CSS classes */
66
66
  className?: string;
67
67
  /** Loading state indicator */
@@ -318,7 +318,7 @@ interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "
318
318
  /** onChange handler - accepts events from input, select, or textarea */
319
319
  onChange?: React.ChangeEventHandler<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
320
320
  }
321
- declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>>;
321
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement>>;
322
322
 
323
323
  /**
324
324
  * DocSidebar Molecule Component
@@ -10244,7 +10244,7 @@ var init_MapView = __esm({
10244
10244
  shadowSize: [41, 41]
10245
10245
  });
10246
10246
  L.Marker.prototype.options.icon = defaultIcon;
10247
- const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback114, useState: useState99 } = React86__namespace.default;
10247
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback114, useState: useState100 } = React86__namespace.default;
10248
10248
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10249
10249
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10250
10250
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10289,7 +10289,7 @@ var init_MapView = __esm({
10289
10289
  showAttribution = true
10290
10290
  }) {
10291
10291
  const eventBus = useEventBus2();
10292
- const [clickedPosition, setClickedPosition] = useState99(null);
10292
+ const [clickedPosition, setClickedPosition] = useState100(null);
10293
10293
  const handleMapClick = useCallback114((lat, lng) => {
10294
10294
  if (showClickedPin) {
10295
10295
  setClickedPosition({ lat, lng });
@@ -17903,6 +17903,7 @@ function CalendarGrid({
17903
17903
  swipeRightEvent,
17904
17904
  dayWindow = "auto"
17905
17905
  }) {
17906
+ const evs = Array.isArray(events2) ? events2 : events2 ? [events2] : [];
17906
17907
  const eventBus = useEventBus();
17907
17908
  const longPressTimer = React86.useRef(null);
17908
17909
  const resolvedWeekStart = React86.useMemo(
@@ -17951,7 +17952,7 @@ function CalendarGrid({
17951
17952
  [onEventClick]
17952
17953
  );
17953
17954
  const eventsForDayCount = React86.useCallback(
17954
- (day) => events2.filter(
17955
+ (day) => evs.filter(
17955
17956
  (ev) => new Date(ev.startTime).toDateString() === day.toDateString()
17956
17957
  ).length,
17957
17958
  [events2]
@@ -18066,7 +18067,7 @@ function CalendarGrid({
18066
18067
  }
18067
18068
  ) }),
18068
18069
  visibleDays.map((day) => {
18069
- const slotEvents = events2.filter(
18070
+ const slotEvents = evs.filter(
18070
18071
  (ev) => eventInSlot(ev, day, time)
18071
18072
  );
18072
18073
  const isToday = day.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
@@ -26853,8 +26854,16 @@ function GameCanvas2D({
26853
26854
  drawEventRef.current = drawEvent;
26854
26855
  const emitRef = React86__namespace.useRef(emit);
26855
26856
  emitRef.current = emit;
26857
+ const assetBaseUrlRef = React86__namespace.useRef(assetBaseUrl);
26858
+ assetBaseUrlRef.current = assetBaseUrl;
26859
+ const backgroundImageRef = React86__namespace.useRef(backgroundImage);
26860
+ backgroundImageRef.current = backgroundImage;
26861
+ const widthRef = React86__namespace.useRef(width);
26862
+ widthRef.current = width;
26863
+ const heightRef = React86__namespace.useRef(height);
26864
+ heightRef.current = height;
26856
26865
  const loadImage = React86__namespace.useCallback((url) => {
26857
- const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
26866
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrlRef.current}${url}`;
26858
26867
  const cached = imageCache.current.get(fullUrl);
26859
26868
  if (cached?.complete && cached.naturalWidth > 0) return cached;
26860
26869
  if (!cached) {
@@ -26864,7 +26873,7 @@ function GameCanvas2D({
26864
26873
  imageCache.current.set(fullUrl, img);
26865
26874
  }
26866
26875
  return null;
26867
- }, [assetBaseUrl]);
26876
+ }, []);
26868
26877
  React86__namespace.useEffect(() => {
26869
26878
  const canvas = canvasRef.current;
26870
26879
  if (!canvas) return;
@@ -26886,10 +26895,10 @@ function GameCanvas2D({
26886
26895
  if (tickEventRef.current) {
26887
26896
  emitRef.current(tickEventRef.current, { dt, frame });
26888
26897
  }
26889
- if (backgroundImage) {
26890
- const bgImg = loadImage(backgroundImage);
26898
+ if (backgroundImageRef.current) {
26899
+ const bgImg = loadImage(backgroundImageRef.current);
26891
26900
  if (bgImg) {
26892
- ctx.drawImage(bgImg, 0, 0, width, height);
26901
+ ctx.drawImage(bgImg, 0, 0, widthRef.current, heightRef.current);
26893
26902
  }
26894
26903
  }
26895
26904
  onDrawRef.current?.(ctx, frame);
@@ -26905,7 +26914,7 @@ function GameCanvas2D({
26905
26914
  running = false;
26906
26915
  cancelAnimationFrame(rafRef.current);
26907
26916
  };
26908
- }, [fps]);
26917
+ }, [fps, loadImage]);
26909
26918
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("inline-block", className), children: /* @__PURE__ */ jsxRuntime.jsx(
26910
26919
  "canvas",
26911
26920
  {
@@ -28047,15 +28056,22 @@ function PlatformerCanvas({
28047
28056
  const eventBus = useEventBus();
28048
28057
  const keysRef = React86.useRef(/* @__PURE__ */ new Set());
28049
28058
  const imageCache = React86.useRef(/* @__PURE__ */ new Map());
28059
+ const [loadedImages, setLoadedImages] = React86.useState(/* @__PURE__ */ new Set());
28050
28060
  const loadImage = React86.useCallback((url) => {
28051
28061
  const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
28052
28062
  const cached = imageCache.current.get(fullUrl);
28053
- if (cached?.complete && cached.naturalWidth > 0) return cached;
28063
+ if (cached?.complete && cached.naturalWidth > 0) {
28064
+ if (!loadedImages.has(fullUrl)) {
28065
+ setLoadedImages((prev) => new Set(prev).add(fullUrl));
28066
+ }
28067
+ return cached;
28068
+ }
28054
28069
  if (!cached) {
28055
28070
  const img = new Image();
28056
28071
  img.crossOrigin = "anonymous";
28057
28072
  img.src = fullUrl;
28058
28073
  img.onload = () => {
28074
+ setLoadedImages((prev) => new Set(prev).add(fullUrl));
28059
28075
  updateAssetStatus(fullUrl, "loaded");
28060
28076
  };
28061
28077
  img.onerror = () => {
@@ -28065,7 +28081,7 @@ function PlatformerCanvas({
28065
28081
  updateAssetStatus(fullUrl, "pending");
28066
28082
  }
28067
28083
  return null;
28068
- }, [assetBaseUrl]);
28084
+ }, [assetBaseUrl, loadedImages]);
28069
28085
  React86.useEffect(() => {
28070
28086
  if (typeof window === "undefined") return;
28071
28087
  const canvas = canvasRef.current;
@@ -28247,7 +28263,7 @@ function PlatformerCanvas({
28247
28263
  ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
28248
28264
  ctx.fill();
28249
28265
  }
28250
- });
28266
+ }, [player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, loadedImages]);
28251
28267
  return /* @__PURE__ */ jsxRuntime.jsx(
28252
28268
  "canvas",
28253
28269
  {
@@ -30276,7 +30292,7 @@ function useSafeEventBus10() {
30276
30292
  }
30277
30293
  }
30278
30294
  function SortableListInner({
30279
- items: initialItems = EMPTY_ITEMS,
30295
+ items: initialItemsProp = EMPTY_ITEMS,
30280
30296
  renderItem,
30281
30297
  reorderEvent,
30282
30298
  reorderPayload,
@@ -30284,6 +30300,7 @@ function SortableListInner({
30284
30300
  className
30285
30301
  }) {
30286
30302
  const eventBus = useSafeEventBus10();
30303
+ const initialItems = Array.isArray(initialItemsProp) ? initialItemsProp : initialItemsProp ? [initialItemsProp] : [];
30287
30304
  const handleReorder = React86.useCallback(
30288
30305
  (fromIndex, toIndex, item) => {
30289
30306
  eventBus.emit(`UI:${reorderEvent}`, {
@@ -33780,9 +33797,10 @@ var init_ReplyTree = __esm({
33780
33797
  showActions = true,
33781
33798
  className
33782
33799
  }) => {
33800
+ const nodeList = Array.isArray(nodes) ? nodes : nodes ? [nodes] : [];
33783
33801
  const [collapsedSet, setCollapsedSet] = React86.useState(() => {
33784
33802
  const acc = /* @__PURE__ */ new Set();
33785
- collectInitiallyCollapsed(nodes, acc);
33803
+ collectInitiallyCollapsed(nodeList, acc);
33786
33804
  return acc;
33787
33805
  });
33788
33806
  const toggleCollapse = React86.useCallback((id) => {
@@ -33796,10 +33814,10 @@ var init_ReplyTree = __esm({
33796
33814
  return next;
33797
33815
  });
33798
33816
  }, []);
33799
- if (nodes.length === 0) {
33817
+ if (nodeList.length === 0) {
33800
33818
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("text-sm text-muted-foreground", className), children: "No replies yet." });
33801
33819
  }
33802
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("flex flex-col gap-2 min-w-0", className), children: nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
33820
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("flex flex-col gap-2 min-w-0", className), children: nodeList.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
33803
33821
  ReplyTreeNode,
33804
33822
  {
33805
33823
  node,