@ankhorage/zora 25.11.1 → 26.0.1

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -4
  3. package/dist/capabilities/index.js +65 -41
  4. package/dist/capabilities/index.js.map +3 -3
  5. package/dist/features/chess/adapters/inbound/ChessBoard.d.ts +1 -1
  6. package/dist/features/chess/adapters/inbound/ChessBoard.d.ts.map +1 -1
  7. package/dist/features/chess/adapters/inbound/ChessBoard.js +130 -83
  8. package/dist/features/chess/adapters/inbound/ChessBoard.js.map +1 -1
  9. package/dist/features/chess/meta.d.ts +97 -72
  10. package/dist/features/chess/meta.d.ts.map +1 -1
  11. package/dist/features/chess/meta.js +64 -40
  12. package/dist/features/chess/meta.js.map +1 -1
  13. package/dist/features/chess/public.d.ts +1 -7
  14. package/dist/features/chess/public.d.ts.map +1 -1
  15. package/dist/features/chess/public.js +0 -6
  16. package/dist/features/chess/public.js.map +1 -1
  17. package/dist/features/chess/utils/createChessBoardCellPressHandler.d.ts +6 -0
  18. package/dist/features/chess/utils/createChessBoardCellPressHandler.d.ts.map +1 -0
  19. package/dist/features/chess/utils/createChessBoardCellPressHandler.js +17 -0
  20. package/dist/features/chess/utils/createChessBoardCellPressHandler.js.map +1 -0
  21. package/dist/features/chess/utils/createChessBoardCells.d.ts +12 -0
  22. package/dist/features/chess/utils/createChessBoardCells.d.ts.map +1 -0
  23. package/dist/features/chess/utils/createChessBoardCells.js +28 -0
  24. package/dist/features/chess/utils/createChessBoardCells.js.map +1 -0
  25. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +2 -3
  26. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -1
  27. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js +3 -1
  28. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js.map +1 -1
  29. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.d.ts +5 -5
  30. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.d.ts.map +1 -1
  31. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.js +13 -7
  32. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.js.map +1 -1
  33. package/dist/readerFree.d.ts +2 -2
  34. package/dist/readerFree.d.ts.map +1 -1
  35. package/dist/readerFree.js +1 -1
  36. package/dist/readerFree.js.map +1 -1
  37. package/dist/types/authoring.d.ts +2 -1
  38. package/dist/types/authoring.d.ts.map +1 -1
  39. package/dist/types/authoring.js.map +1 -1
  40. package/dist/types/chess.d.ts +27 -20
  41. package/dist/types/chess.d.ts.map +1 -1
  42. package/dist/types/chess.js.map +1 -1
  43. package/package.json +34 -94
  44. package/src/features/chess/adapters/inbound/ChessBoard.tsx +263 -112
  45. package/src/features/chess/meta.ts +64 -40
  46. package/src/features/chess/public.test.tsx +77 -228
  47. package/src/features/chess/public.ts +2 -9
  48. package/src/features/chess/utils/createChessBoardCellPressHandler.ts +30 -0
  49. package/src/features/chess/utils/createChessBoardCells.ts +54 -0
  50. package/src/features/chessTabletopOwnership.test.ts +3 -7
  51. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.test.ts +1 -2
  52. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +2 -3
  53. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.test.ts +45 -0
  54. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts +3 -1
  55. package/src/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.test.ts +65 -3
  56. package/src/features/graph-view/adapters/inbound/web-artifact/getGraphGeometryKey.ts +13 -7
  57. package/src/features/registry/componentMeta.test.ts +12 -1
  58. package/src/readerFree.ts +3 -15
  59. package/src/types/authoring.ts +2 -1
  60. package/src/types/chess.ts +28 -21
  61. package/web-dist/chunks/Accordion-91sjb3zh.js +453 -0
  62. package/web-dist/components/chess-board/ChessBoard.js +2 -1
  63. package/web-dist/components/graph-view/GraphView.js +878 -317
  64. package/web-dist/components/opening-book/OpeningBook.js +2 -1
  65. package/web-dist/manifest.json +8 -4
  66. package/dist/features/chess/utils/chessEngine.d.ts +0 -8
  67. package/dist/features/chess/utils/chessEngine.d.ts.map +0 -1
  68. package/dist/features/chess/utils/chessEngine.js +0 -71
  69. package/dist/features/chess/utils/chessEngine.js.map +0 -1
  70. package/dist/features/chess/utils/chessSquares.d.ts +0 -4
  71. package/dist/features/chess/utils/chessSquares.d.ts.map +0 -1
  72. package/dist/features/chess/utils/chessSquares.js +0 -3
  73. package/dist/features/chess/utils/chessSquares.js.map +0 -1
  74. package/dist/features/chess/utils/createBoardSquares.d.ts +0 -4
  75. package/dist/features/chess/utils/createBoardSquares.d.ts.map +0 -1
  76. package/dist/features/chess/utils/createBoardSquares.js +0 -9
  77. package/dist/features/chess/utils/createBoardSquares.js.map +0 -1
  78. package/dist/features/chess/utils/getSquareFile.d.ts +0 -4
  79. package/dist/features/chess/utils/getSquareFile.d.ts.map +0 -1
  80. package/dist/features/chess/utils/getSquareFile.js +0 -5
  81. package/dist/features/chess/utils/getSquareFile.js.map +0 -1
  82. package/dist/features/chess/utils/getSquareRank.d.ts +0 -4
  83. package/dist/features/chess/utils/getSquareRank.d.ts.map +0 -1
  84. package/dist/features/chess/utils/getSquareRank.js +0 -5
  85. package/dist/features/chess/utils/getSquareRank.js.map +0 -1
  86. package/dist/features/chess/utils/isLightSquare.d.ts +0 -4
  87. package/dist/features/chess/utils/isLightSquare.d.ts.map +0 -1
  88. package/dist/features/chess/utils/isLightSquare.js +0 -6
  89. package/dist/features/chess/utils/isLightSquare.js.map +0 -1
  90. package/src/features/chess/utils/chessEngine.test.ts +0 -35
  91. package/src/features/chess/utils/chessEngine.ts +0 -83
  92. package/src/features/chess/utils/chessSquares.ts +0 -6
  93. package/src/features/chess/utils/createBoardSquares.ts +0 -13
  94. package/src/features/chess/utils/getSquareFile.ts +0 -6
  95. package/src/features/chess/utils/getSquareRank.ts +0 -6
  96. package/src/features/chess/utils/isLightSquare.ts +0 -8
  97. package/web-dist/chunks/Accordion-xcm1qc39.js +0 -3813
@@ -185,7 +185,7 @@ function isUsableViewport(viewport: { readonly width: number; readonly height: n
185
185
  );
186
186
  }
187
187
 
188
- /*** Measure rendered node geometry for public consumers while keeping Cytoscape private. */
188
+ /*** Measure rendered node hit geometry without exposing paint-only outlines or Cytoscape. */
189
189
  function measureRenderedNodes(cy: Core, nodeIds: readonly string[]): GraphViewMeasurement | null {
190
190
  const viewport = { width: cy.width(), height: cy.height() };
191
191
  if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0) return null;
@@ -194,12 +194,11 @@ function measureRenderedNodes(cy: Core, nodeIds: readonly string[]): GraphViewMe
194
194
  const node = cy.getElementById(id);
195
195
  if (!node.isNode()) return [];
196
196
 
197
- const bounds = node.renderedBoundingBox();
198
197
  return [
199
198
  {
200
199
  id,
201
200
  position: node.renderedPosition(),
202
- size: { width: bounds.w, height: bounds.h },
201
+ size: { width: node.renderedOuterWidth(), height: node.renderedOuterHeight() },
203
202
  },
204
203
  ];
205
204
  });
@@ -214,6 +214,51 @@ test('preserves selection and positions through hover presentation updates witho
214
214
  }
215
215
  });
216
216
 
217
+ test('does not relayout when controlled selection adds a paint-only outline', async () => {
218
+ const ready = Promise.withResolvers<GraphViewController>();
219
+ const calls = { sort: 0 };
220
+ const runtime = createGraphRuntime(undefined, { current: { onReady: ready.resolve } });
221
+ const input = {
222
+ nodes: [
223
+ { id: 'src', label: 'src' },
224
+ { id: 'dependency', label: 'dependency' },
225
+ ],
226
+ edges: [{ source: 'src', target: 'dependency' }],
227
+ layout: 'grid' as const,
228
+ layoutOptions: {
229
+ sort: () => {
230
+ calls.sort += 1;
231
+ return 0;
232
+ },
233
+ },
234
+ richNodeRendering: false,
235
+ sizeNodesToLabels: true,
236
+ styleRules: [
237
+ {
238
+ selector: 'node',
239
+ style: { height: 24, label: 'data(label)', padding: '12px', width: 24 },
240
+ },
241
+ { selector: 'node:selected', style: { 'outline-width': 2 } },
242
+ ],
243
+ };
244
+ try {
245
+ runtime.update(input);
246
+ await ready.promise;
247
+ const sortCount = calls.sort;
248
+ runtime.setSelectedNodeIds(['src']);
249
+ runtime.update({
250
+ ...input,
251
+ nodes: [
252
+ { id: 'src', classes: 'highlight', label: 'src' },
253
+ { id: 'dependency', label: 'dependency' },
254
+ ],
255
+ });
256
+ expect(calls.sort).toBe(sortCount);
257
+ } finally {
258
+ runtime.destroy();
259
+ }
260
+ });
261
+
217
262
  test('forwards semantic selection intent from rich-node press events', async () => {
218
263
  const ready = Promise.withResolvers<GraphViewController>();
219
264
  const events: GraphViewElementEvent[] = [];
@@ -192,7 +192,9 @@ function updateRuntime(state: GraphRuntimeState, input: GraphRuntimeUpdate) {
192
192
  applyKnownNodeSizes(state);
193
193
  state.loopSizer.update();
194
194
  const geometry = getGraphGeometryKey(state.cy);
195
- const relayout = geometry !== state.geometryRef.current || hasGraphLayoutChanged(previous, input);
195
+ // Keep native double-press targets stable for paint-only class changes. A class that changes
196
+ // measured node dimensions remains a geometry change and must relayout.
197
+ const relayout = hasGraphLayoutChanged(previous, input) || geometry !== state.geometryRef.current;
196
198
  state.geometryRef.current = geometry;
197
199
  if (!relayout) {
198
200
  if (
@@ -4,13 +4,19 @@ import cytoscape from 'cytoscape';
4
4
  import { getGraphGeometryKey } from './getGraphGeometryKey';
5
5
  import { syncGraphElements } from './syncGraphElements';
6
6
 
7
- test('ignores styled highlight colors but detects changed layout dimensions', () => {
7
+ test('uses body, padding, borders and labels while ignoring paint-only presentation', () => {
8
8
  const cy = cytoscape({
9
9
  headless: true,
10
10
  styleEnabled: true,
11
11
  style: [
12
- { selector: 'node', style: { width: 'data(width)', height: 24 } },
12
+ {
13
+ selector: 'node',
14
+ style: { width: 'data(width)', height: 24, padding: 8, 'border-width': 2 },
15
+ },
13
16
  { selector: 'node.highlight', style: { 'background-color': '#ff0000' } },
17
+ { selector: 'node.hovered', style: { 'overlay-opacity': 0.4 } },
18
+ { selector: 'node:selected', style: { 'outline-width': 2 } },
19
+ { selector: 'node.expanded', style: { padding: 16, width: 160 } },
14
20
  ],
15
21
  });
16
22
  try {
@@ -19,7 +25,63 @@ test('ignores styled highlight colors but detects changed layout dimensions', ()
19
25
  syncGraphElements(cy, [{ id: 'a', classes: 'highlight', data: { width: 80 } }], []);
20
26
  expect(cy.getElementById('a').style('background-color')).toBe('rgb(255,0,0)');
21
27
  expect(getGraphGeometryKey(cy)).toBe(before);
22
- syncGraphElements(cy, [{ id: 'a', classes: 'highlight', data: { width: 160 } }], []);
28
+ cy.getElementById('a').select();
29
+ expect(getGraphGeometryKey(cy)).toBe(before);
30
+ syncGraphElements(cy, [{ id: 'a', classes: 'hovered', data: { width: 80 } }], []);
31
+ expect(getGraphGeometryKey(cy)).toBe(before);
32
+ syncGraphElements(cy, [{ id: 'a', classes: 'expanded', data: { width: 80 } }], []);
33
+ expect(getGraphGeometryKey(cy)).not.toBe(before);
34
+ } finally {
35
+ cy.destroy();
36
+ }
37
+ });
38
+
39
+ test('includes plain and Unicode label bounds when their font metrics change', () => {
40
+ const cy = cytoscape({
41
+ headless: true,
42
+ styleEnabled: true,
43
+ elements: [{ data: { id: 'plain' } }, { data: { id: 'unicode' } }],
44
+ });
45
+ const plain = cy.getElementById('plain');
46
+ const unicode = cy.getElementById('unicode');
47
+ const labelBounds = new Map([
48
+ ['plain', { h: 18, w: 42 }],
49
+ ['unicode', { h: 18, w: 56 }],
50
+ ]);
51
+ const boundingBox = (id: string) => () => {
52
+ const bounds = labelBounds.get(id);
53
+ if (!bounds) throw new Error(`Missing label bounds for ${id}.`);
54
+ return { x1: 0, x2: bounds.w, y1: 0, y2: bounds.h, ...bounds };
55
+ };
56
+ plain.boundingBox = boundingBox('plain');
57
+ unicode.boundingBox = boundingBox('unicode');
58
+ try {
59
+ const before = getGraphGeometryKey(cy);
60
+ labelBounds.set('unicode', { h: 22, w: 76 });
61
+ expect(getGraphGeometryKey(cy)).not.toBe(before);
62
+ } finally {
63
+ cy.destroy();
64
+ }
65
+ });
66
+
67
+ test('excludes compound bounds and edge paint while retaining child body geometry', () => {
68
+ const cy = cytoscape({
69
+ headless: true,
70
+ styleEnabled: true,
71
+ elements: [
72
+ { data: { id: 'parent' } },
73
+ { data: { id: 'child', parent: 'parent' } },
74
+ { data: { id: 'loop', source: 'child', target: 'child' } },
75
+ ],
76
+ style: [{ selector: 'node', style: { height: 24, width: 'data(width)' } }],
77
+ });
78
+ try {
79
+ const child = cy.getElementById('child');
80
+ child.data('width', 80);
81
+ const before = getGraphGeometryKey(cy);
82
+ cy.getElementById('parent').position({ x: 500, y: 400 });
83
+ expect(getGraphGeometryKey(cy)).toBe(before);
84
+ child.data('width', 160);
23
85
  expect(getGraphGeometryKey(cy)).not.toBe(before);
24
86
  } finally {
25
87
  cy.destroy();
@@ -1,18 +1,24 @@
1
1
  import type { Core } from 'cytoscape';
2
2
 
3
3
  /***
4
- * Identifies layout-relevant leaf dimensions without including positions or presentation colors.
5
- * Adding viewport, color or arbitrary data to this key turns interaction updates into relayouts.
6
- * Measure after style reconciliation; actual size changes still require layout. Compound bounds
7
- * follow child positions, so keying their measured bounds would feed layout output back into input.
8
- * Styled geometry tests and runtime interaction tests protect these distinctions.
4
+ * Identifies layout-relevant leaf dimensions without including positions or paint-only outlines.
5
+ * Cytoscape's layoutDimensions() includes selected-outline rendering even though layouts use only
6
+ * the node body, padding, border, and label bounds. Key those direct inputs instead. Compound
7
+ * bounds follow child positions, so keying their measured bounds would feed layout output back
8
+ * into input. Styled geometry tests and runtime interaction tests protect these distinctions.
9
9
  */
10
10
  export function getGraphGeometryKey(cy: Core): string {
11
11
  return JSON.stringify(
12
12
  cy.nodes().map((node) => {
13
13
  if (node.isParent()) return [node.id()];
14
- const dimensions = node.layoutDimensions({ nodeDimensionsIncludeLabels: true });
15
- return [node.id(), dimensions.w, dimensions.h];
14
+ const labelBounds = node.boundingBox({
15
+ includeEdges: false,
16
+ includeLabels: true,
17
+ includeNodes: false,
18
+ includeOverlays: false,
19
+ includeUnderlays: false,
20
+ });
21
+ return [node.id(), node.outerWidth(), node.outerHeight(), labelBounds.w, labelBounds.h];
16
22
  }),
17
23
  );
18
24
  }
@@ -382,7 +382,18 @@ describe('ZORA_COMPONENT_META invariants', () => {
382
382
  });
383
383
  expect(ZORA_COMPONENT_META.ChessBoard.bindings?.props?.legalTargets?.value).toEqual({
384
384
  type: 'array',
385
- itemType: 'string',
385
+ itemType: 'object',
386
+ fields: [
387
+ { path: '[].column', type: 'number', label: 'Column' },
388
+ { path: '[].row', type: 'number', label: 'Row' },
389
+ ],
390
+ });
391
+ expect(ZORA_COMPONENT_META.ChessBoard.bindings?.props?.selectedCell?.value).toEqual({
392
+ type: 'object',
393
+ fields: [
394
+ { path: 'column', type: 'number', label: 'Column' },
395
+ { path: 'row', type: 'number', label: 'Row' },
396
+ ],
386
397
  });
387
398
  expect(ZORA_COMPONENT_META.OpeningBook.bindings?.props?.moves?.value).toEqual({
388
399
  type: 'array',
package/src/readerFree.ts CHANGED
@@ -119,18 +119,17 @@ export type {
119
119
  } from './features/chat/public';
120
120
  export { ChatListItem, MessageBubble } from './features/chat/public';
121
121
  export type {
122
+ ChessBoardCell,
122
123
  ChessBoardColorOverrides,
123
124
  ChessBoardColorScheme,
125
+ ChessBoardDimensions,
124
126
  ChessBoardOrientation,
125
127
  ChessBoardProps,
126
128
  ChessMoveAttempt,
127
- ChessMoveResult,
128
129
  ChessPieceCode,
129
130
  ChessPieceRenderContext,
130
131
  ChessPieceRenderer,
131
132
  ChessPieceState,
132
- ChessPromotionPiece,
133
- ChessSquareId,
134
133
  OpeningBookColorOverrides,
135
134
  OpeningBookColorScheme,
136
135
  OpeningBookMove,
@@ -138,18 +137,7 @@ export type {
138
137
  } from './features/chess/public';
139
138
  export type { ChessColorThemeShape } from './features/chess/public';
140
139
  export { ChessBoard, chessBoardMeta, OpeningBook, openingBookMeta } from './features/chess/public';
141
- export {
142
- chessSquares,
143
- createBoardSquares,
144
- createChessBoardColorScheme,
145
- createOpeningBookColorScheme,
146
- getLegalTargets,
147
- getSquareFile,
148
- getSquareRank,
149
- isLightSquare,
150
- readChessPieces,
151
- tryMove,
152
- } from './features/chess/public';
140
+ export { createChessBoardColorScheme, createOpeningBookColorScheme } from './features/chess/public';
153
141
  export type { ChipProps } from './features/chip/public';
154
142
  export type { ChipGroupItem, ChipGroupProps } from './features/chip/public';
155
143
  export { Chip } from './features/chip/public';
@@ -8,7 +8,8 @@ import type {
8
8
 
9
9
  export type ZoraComponentCategory = 'foundation' | 'component' | 'pattern' | 'layout';
10
10
 
11
- export type ZoraComponentPropType = UiComponentPropType;
11
+ /** ZORA authoring additionally describes serializable object-shaped values. */
12
+ export type ZoraComponentPropType = UiComponentPropType | 'object';
12
13
 
13
14
  export type ZoraComponentPropValue =
14
15
  | string
@@ -4,33 +4,36 @@ import type { ZoraBaseProps } from './base';
4
4
 
5
5
  export type ChessBoardOrientation = 'white' | 'black';
6
6
  export type ChessPieceCode = string;
7
- export type ChessSquareId =
8
- `${'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h'}${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8}`;
9
- export type ChessPromotionPiece = 'q' | 'r' | 'b' | 'n';
10
7
 
11
- export interface ChessMoveAttempt {
12
- readonly from: ChessSquareId;
13
- readonly to: ChessSquareId;
14
- readonly promotion?: ChessPromotionPiece;
8
+ /** A caller-owned cell location in a rectangular chess presentation board. */
9
+ export interface ChessBoardCell {
10
+ readonly column: number;
11
+ readonly row: number;
15
12
  }
16
13
 
17
- export interface ChessMoveResult extends Omit<ChessMoveAttempt, 'promotion'> {
18
- readonly fen: string;
19
- readonly lan: string;
20
- readonly promotion?: ChessPromotionPiece;
21
- readonly san: string;
14
+ /** The bounded presentation dimensions supported by ChessBoard. */
15
+ export interface ChessBoardDimensions {
16
+ readonly columns: number;
17
+ readonly rows: number;
18
+ }
19
+
20
+ export interface ChessMoveAttempt {
21
+ readonly from: ChessBoardCell;
22
+ readonly to: ChessBoardCell;
22
23
  }
23
24
 
24
25
  export interface ChessPieceState {
25
26
  readonly color: 'black' | 'white';
26
27
  readonly piece: ChessPieceCode;
27
- readonly square: ChessSquareId;
28
+ readonly cell: ChessBoardCell;
28
29
  }
29
30
 
30
31
  export interface ChessPieceRenderContext {
31
32
  readonly color: string;
32
33
  readonly piece: ChessPieceCode;
33
- readonly square: ChessSquareId;
34
+ readonly cell: ChessBoardCell;
35
+ /** Available cell edge length in pixels. */
36
+ readonly size: number;
34
37
  }
35
38
 
36
39
  export type ChessPieceRenderer = (context: ChessPieceRenderContext) => React.ReactNode;
@@ -65,20 +68,24 @@ export interface OpeningBookColorScheme {
65
68
  export type OpeningBookColorOverrides = Partial<OpeningBookColorScheme>;
66
69
 
67
70
  export interface ChessBoardProps extends ZoraBaseProps {
68
- readonly fen?: string;
71
+ /** Number of displayed columns, from 2 through 24. */
72
+ readonly columns?: number;
73
+ /** Number of displayed rows, from 2 through 24. */
74
+ readonly rows?: number;
69
75
  readonly pieces?: readonly ChessPieceState[];
70
76
  readonly orientation?: ChessBoardOrientation;
71
- readonly selectedSquare?: ChessSquareId | null;
72
- readonly legalTargets?: readonly ChessSquareId[];
77
+ readonly selectedCell?: ChessBoardCell | null;
78
+ readonly legalTargets?: readonly ChessBoardCell[];
73
79
  readonly lastMove?: ChessMoveAttempt | null;
74
80
  readonly disabled?: boolean;
75
81
  readonly showCoordinates?: boolean;
76
- readonly validateMoves?: boolean;
82
+ /** Enables explicit editor resize controls when a dimensions callback is supplied. */
83
+ readonly resizable?: boolean;
77
84
  readonly colorScheme?: ChessBoardColorOverrides;
78
- readonly onSquarePress?: (square: ChessSquareId) => void;
85
+ readonly onCellPress?: (cell: ChessBoardCell) => void;
79
86
  readonly onMoveAttempt?: (move: ChessMoveAttempt) => void;
80
- readonly onLegalMove?: (move: ChessMoveResult) => void;
81
- readonly onInvalidMove?: (move: ChessMoveAttempt) => void;
87
+ /** Receives a caller-owned resize proposal; ZORA never mutates board dimensions. */
88
+ readonly onDimensionsChange?: (dimensions: ChessBoardDimensions) => void;
82
89
  readonly renderPiece?: ChessPieceRenderer;
83
90
  }
84
91