Files
soroban-abacus-flashcards/packages/abacus-react/MIGRATION_GUIDE.md
Thomas Hallock 7c33d0246f fix: prevent undefined displayRules error in worksheet generator
Fixes production error "Cannot read properties of undefined (reading 'carryBoxes')"
that occurred when users tried to adjust difficulty settings.

Root cause: displayRules was undefined for new users or users with old V1 config
in database. Difficulty adjustment buttons accessed displayRules.carryBoxes without
checking if displayRules existed first.

Changes:
- AdditionWorksheetClient: Initialize displayRules with defaults when missing
- ConfigPanel: Use null-coalescing operators instead of non-null assertions
- ConfigPanel: Add error logging when required fields are missing
- NEW: WorksheetErrorBoundary component to catch all errors in worksheet page
- page.tsx: Wrap client component with error boundary

This ensures users see helpful error messages instead of blank pages,
and never need to open the browser console to understand what went wrong.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 13:01:54 -06:00

300 lines
7.9 KiB
Markdown

# Migration Guide: useAbacusState → useAbacusPlaceStates
## Overview
The `useAbacusState` hook has been **deprecated** in favor of the new `useAbacusPlaceStates` hook. This migration is part of a larger architectural improvement to eliminate array-based column indexing in favor of native place-value semantics.
## Why Migrate?
### Problems with `useAbacusState` (deprecated)
- ❌ Uses **array indices** for columns (0=leftmost, requires totalColumns)
- ❌ Requires threading `totalColumns` through component tree
- ❌ Index math creates confusion: `columnIndex = totalColumns - 1 - placeValue`
- ❌ Prone to off-by-one errors
- ❌ No support for BigInt (large numbers >15 digits)
### Benefits of `useAbacusPlaceStates` (new)
- ✅ Uses **place values** directly (0=ones, 1=tens, 2=hundreds)
- ✅ Native semantic meaning, no index conversion needed
- ✅ Cleaner architecture with `Map<PlaceValue, State>`
- ✅ Supports both `number` and `BigInt` for large values
- ✅ Type-safe with `ValidPlaceValues` (0-9)
- ✅ No totalColumns threading required
## Migration Steps
### 1. Update Hook Usage
**Before (deprecated):**
```tsx
import { useAbacusState } from "@soroban/abacus-react";
function MyComponent() {
const {
value,
setValue,
columnStates, // Array of column states
getColumnState,
setColumnState,
toggleBead,
} = useAbacusState(123, 5); // totalColumns=5
// Need to calculate indices
const onesColumnIndex = 4; // rightmost
const tensColumnIndex = 3; // second from right
return <AbacusReact value={value} columns={5} />;
}
```
**After (new):**
```tsx
import { useAbacusPlaceStates } from "@soroban/abacus-react";
function MyComponent() {
const {
value,
setValue,
placeStates, // Map<PlaceValue, PlaceState>
getPlaceState,
setPlaceState,
toggleBeadAtPlace,
} = useAbacusPlaceStates(123, 4); // maxPlaceValue=4 (0-4 = 5 columns)
// Direct place value access - no index math!
const onesState = getPlaceState(0);
const tensState = getPlaceState(1);
return <AbacusReact value={value} columns={5} />;
}
```
### 2. Update State Access Patterns
**Before (array indexing):**
```tsx
// Get state for tens column (need to know position in array)
const tensIndex = columnStates.length - 2; // second from right
const tensState = columnStates[tensIndex];
```
**After (place value):**
```tsx
// Get state for tens place - no calculation needed!
const tensState = getPlaceState(1); // 1 = tens place
```
### 3. Update State Manipulation
**Before:**
```tsx
// Toggle bead in ones column (need BeadConfig with column index)
toggleBead({
type: "earth",
value: 1,
active: false,
position: 2,
placeValue: 0, // This was confusing - had place value BUT operated on column index
});
```
**After:**
```tsx
// Toggle bead at ones place - clean and semantic
toggleBeadAtPlace({
type: "earth",
value: 1,
active: false,
position: 2,
placeValue: 0, // Now actually used as place value!
});
```
### 4. Update Iteration Logic
**Before (array iteration):**
```tsx
columnStates.forEach((state, columnIndex) => {
const placeValue = columnStates.length - 1 - columnIndex; // Manual conversion
console.log(`Column ${columnIndex} (place ${placeValue}):`, state);
});
```
**After (Map iteration):**
```tsx
placeStates.forEach((state, placeValue) => {
console.log(`Place ${placeValue}:`, state); // Direct access, no conversion!
});
```
## API Comparison
### useAbacusState (deprecated)
```typescript
function useAbacusState(
initialValue?: number,
targetColumns?: number,
): {
value: number;
setValue: (newValue: number) => void;
columnStates: ColumnState[]; // Array
getColumnState: (columnIndex: number) => ColumnState;
setColumnState: (columnIndex: number, state: ColumnState) => void;
toggleBead: (bead: BeadConfig) => void;
};
```
### useAbacusPlaceStates (new)
```typescript
function useAbacusPlaceStates(
controlledValue?: number | bigint,
maxPlaceValue?: ValidPlaceValues,
): {
value: number | bigint;
setValue: (newValue: number | bigint) => void;
placeStates: PlaceStatesMap; // Map
getPlaceState: (place: ValidPlaceValues) => PlaceState;
setPlaceState: (place: ValidPlaceValues, state: PlaceState) => void;
toggleBeadAtPlace: (bead: BeadConfig) => void;
};
```
## Complete Example
### Before: Array-based (deprecated)
```tsx
import { useState } from "react";
import { useAbacusState, AbacusReact } from "@soroban/abacus-react";
function DeprecatedExample() {
const { value, setValue, columnStates } = useAbacusState(0, 3);
const handleAddTen = () => {
// Need to know array position of tens column
const totalColumns = columnStates.length;
const tensColumnIndex = totalColumns - 2; // Complex!
const current = columnStates[tensColumnIndex];
// Increment tens digit
const currentTensValue =
(current.heavenActive ? 5 : 0) + current.earthActive;
const newTensValue = (currentTensValue + 1) % 10;
setValue(value + 10);
};
return (
<div>
<AbacusReact value={value} columns={3} interactive />
<button onClick={handleAddTen}>Add 10</button>
</div>
);
}
```
### After: Place-value based (new)
```tsx
import { useState } from "react";
import { useAbacusPlaceStates, AbacusReact } from "@soroban/abacus-react";
function NewExample() {
const { value, setValue, getPlaceState } = useAbacusPlaceStates(0, 2);
const handleAddTen = () => {
// Direct access to tens place - simple!
const tensState = getPlaceState(1); // 1 = tens
// Increment tens digit
const currentTensValue =
(tensState.heavenActive ? 5 : 0) + tensState.earthActive;
const newTensValue = (currentTensValue + 1) % 10;
if (typeof value === "number") {
setValue(value + 10);
} else {
setValue(value + 10n);
}
};
return (
<div>
<AbacusReact value={value} columns={3} interactive />
<button onClick={handleAddTen}>Add 10</button>
</div>
);
}
```
## BigInt Support (New Feature)
The new hook supports BigInt for numbers exceeding JavaScript's safe integer limit:
```tsx
const { value, setValue } = useAbacusPlaceStates(
123456789012345678901234567890n, // BigInt!
29, // 30 digits (place values 0-29)
);
console.log(typeof value); // "bigint"
```
## Type Safety Improvements
The new hook uses branded types and strict typing:
```tsx
import type {
ValidPlaceValues, // 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
PlaceState,
PlaceStatesMap,
} from "@soroban/abacus-react";
// Type-safe place value access
const onesState: PlaceState = getPlaceState(0);
const tensState: PlaceState = getPlaceState(1);
// Compile-time error for invalid place values
const invalidState = getPlaceState(15); // Error if maxPlaceValue < 15
```
## Timeline
- **Current**: Both hooks available, `useAbacusState` marked `@deprecated`
- **Next major version**: `useAbacusState` will be removed
- **Recommendation**: Migrate as soon as possible
## Getting Help
If you encounter issues during migration:
1. Check the [README.md](./README.md) for updated examples
2. Review [Storybook stories](./src) for usage patterns
3. Open an issue at https://github.com/anthropics/claude-code/issues
## Summary
| Feature | useAbacusState (old) | useAbacusPlaceStates (new) |
| ---------------- | -------------------- | -------------------------- |
| Architecture | Array-based columns | Map-based place values |
| Index math | Required | Not needed |
| Semantic meaning | Indirect | Direct |
| BigInt support | ❌ No | ✅ Yes |
| Type safety | Basic | Enhanced |
| Column threading | Required | Not required |
| **Status** | ⚠️ Deprecated | ✅ Recommended |
**Bottom line:** The new hook eliminates complexity and makes your code more maintainable. Migration is straightforward - primarily renaming and removing index calculations.