This document provides comprehensive guidance for AI assistants working on the ArcanaScreen codebase.
ArcanaScreen is a fully customizable virtual Dungeon Master (DM) screen built with React, TypeScript, and Vite. It provides Game Masters with a flexible interface to organize session resources, manage encounters, track players, and customize their screen layout through drag-and-drop widgets.
Key Characteristics:
- Client-side only (no backend required)
- Browser localStorage for data persistence
- Widget-based modular architecture
- Drag-and-drop layout customization
/arcana-screen/
├── .github/
│ └── worklows/ # GitHub Actions (project board automation)
├── .githooks/ # Git hooks for commit validation
│ ├── pre-commit # Branch name validation
│ └── commit-msg # Commit message validation
├── arcana-screen/ # Main application directory
│ ├── src/
│ │ ├── components/
│ │ │ ├── Grid.tsx # Main grid layout with drag-drop
│ │ │ ├── widgets/ # Widget components
│ │ │ │ ├── DiceRoller.tsx # D&D dice rolling
│ │ │ │ ├── InitiativeTracker.tsx # Combat turn tracking
│ │ │ │ ├── CountdownTimer.tsx # Timer widget
│ │ │ │ ├── SimpleTable.tsx # Editable table widget
│ │ │ │ ├── QuickNotes.tsx # Text notes widget
│ │ │ │ ├── DiceIcons.tsx # SVG dice icons
│ │ │ │ ├── WidgetConfig.tsx # Widget metadata registry
│ │ │ │ └── WidgetComponents.ts # Shared widget utilities
│ │ │ ├── WidgetSidebar/ # Left sidebar components
│ │ │ │ ├── WidgetSidebar.tsx
│ │ │ │ ├── SidebarHeader.tsx
│ │ │ │ ├── WidgetList.tsx
│ │ │ │ ├── WidgetItem.tsx
│ │ │ │ └── types.ts
│ │ │ └── ProfileManager/ # Profile save/load system
│ │ │ ├── ProfileManager.tsx
│ │ │ └── ProfileManagerPanel.tsx
│ │ ├── store/ # Zustand state management
│ │ │ ├── useWidgetStore.ts # Widget layout and data
│ │ │ ├── useProfileStore.ts # Profile management
│ │ │ ├── themeStore.ts # Theme state (light/dark)
│ │ │ └── appStore.ts # Global app state
│ │ ├── utils/
│ │ │ └── localStorageManager.tsx # localStorage utilities
│ │ ├── assets/ # Static assets
│ │ ├── App.tsx # Main App component
│ │ ├── main.tsx # React entry point
│ │ ├── index.css # Global styles & theme vars
│ │ └── App.css # App-specific styles
│ ├── public/ # Static files
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.app.json
│ ├── eslint.config.js
│ └── tailwind.config.js
├── README.md
├── LICENSE
└── CLAUDE.md # This file
| Category | Technology | Version |
|---|---|---|
| Framework | React | ^19.0.0 |
| Language | TypeScript | ~5.7.2 |
| Build Tool | Vite | ^6.3.1 |
| State Management | Zustand | ^5.0.3 |
| CSS Framework | TailwindCSS | ^4.1.4 |
| Drag & Drop | React DnD | ^16.0.1 |
| Toast Notifications | react-hot-toast | ^2.5.2 |
| Linting | ESLint + typescript-eslint | ^9.25.1 |
| Code Formatting | Prettier | ^3.5.3 |
All commands should be run from the arcana-screen/arcana-screen/ directory:
npm run dev # Start Vite dev server with HMR (port 5173)
npm run build # Type-check (tsc) + Vite production build
npm run lint # Run ESLint on project
npm run preview # Preview production build locallyBranches must follow the pattern: {type}/{issue-number}-{description}
Types: feature, fix, chore, refactor
Examples:
feature/23-initiative-trackerfix/45-dice-roller-bugrefactor/12-store-cleanup
Commits must follow Conventional Commits format with issue reference:
{type}[({scope})]: {description} #{issue-number}
Types: feat, fix, chore, refactor, docs, style, test, perf, ci, build
Examples:
feat: add initiative tracker widget #23fix(dice-roller): correct advantage calculation #45docs: update README installation steps #12
The repository includes git hooks in .githooks/:
- pre-commit: Validates branch name convention
- commit-msg: Validates commit message format
To enable hooks: git config core.hooksPath .githooks
All components are functional React components using hooks:
interface WidgetProps {
id: string;
updateWidget: (id: string, updates: Partial<Widget>) => void;
removeWidget?: (id: string) => void;
}
const MyWidget: React.FC<WidgetProps> = ({ id }) => {
const widget = useWidgetStore(state => state.widgets.find(w => w.id === id));
const updateWidget = useWidgetStore(state => state.updateWidget);
const [localState, setLocalState] = useState(...);
// Component logic and JSX
};Stores are organized by feature with persist middleware for localStorage:
// Store pattern
export const useWidgetStore = create<WidgetStore>()(
persist(
(set, get) => ({
widgets: [],
addWidget: (widget) => set({ widgets: [...get().widgets, widget] }),
updateWidget: (id, updates) =>
set({
widgets: get().widgets.map((w) =>
w.id === id ? { ...w, ...updates } : w
),
}),
// ...
}),
{ name: 'arcanaScreenLayout' }
)
);Store Files:
useWidgetStore.ts- Widget layout and data (key:arcanaScreenLayout)useProfileStore.ts- Profile management (key:arcana_profiles)themeStore.ts- Theme state (key:theme)appStore.ts- App state including favorites (key:arcana_favorites)
- Primary: TailwindCSS utility classes
- Secondary: Inline styles for dynamic values
- Theming: Class-based dark mode using
dark-themeandlight-themeclasses - Global CSS: CSS variables defined in
index.css
- Components: PascalCase (e.g.,
DiceRoller,InitiativeTracker) - Files: Match component name (e.g.,
DiceRoller.tsx) - Functions/variables: camelCase
- Types/Interfaces: PascalCase (e.g.,
WidgetProps,TableColumn)
Item types used in the application:
'WIDGET'- Widget cards from sidebar'ROW'/'COLUMN'- Table row/column reordering
| Widget | ID | Purpose |
|---|---|---|
| Countdown Timer | countdown-timer |
Time tracking for encounters |
| Dice Roller | dice-roller |
D2-D100 rolling with advantage/disadvantage |
| Initiative Tracker | initiative-tracker |
Combat turn order management |
| Quick Notes | quick-notes |
Text note taking |
| Simple Table | simple-table |
Editable data tables |
- Create component in
src/components/widgets/NewWidget.tsx - Add metadata to
WidgetConfig.tsx:{ id: 'new-widget', name: 'New Widget', tags: ['tag1', 'tag2'], isFavorite: false }
- Add widget case to
Grid.tsxrender logic - Define widget-specific properties in
Widgetinterface (useWidgetStore.ts)
Widgets receive:
id- Unique widget instance identifier- Access store directly via
useWidgetStore - Update state via
updateWidget(id, { ...updates })
Current State: No formal test suite configured.
Validation relies on:
- TypeScript strict mode type checking
- ESLint static analysis
- Manual browser testing
- Always run
npm run buildto verify TypeScript compilation - Follow the established patterns for components and stores
- Use TailwindCSS for styling when possible
- Keep widget components self-contained
- Persist important state to localStorage via Zustand middleware
- Reference issue numbers in commits
- Don't add backend dependencies (client-side only)
- Don't bypass git hooks without explicit permission
- Don't modify localStorage keys without updating all references
- Don't introduce dependencies without clear justification
Before submitting changes:
- TypeScript compiles without errors (
npm run build) - ESLint passes (
npm run lint) - Branch name follows convention
- Commit messages follow Conventional Commits with issue reference
- No
anytypes without justification - New widgets registered in
WidgetConfig.tsx - State properly persisted if needed
- Some code comments are in Italian (migrating to English)
- Some debugging console.log statements may exist
- Type safety could be improved in some store definitions
- Main entry:
arcana-screen/src/main.tsx - App component:
arcana-screen/src/App.tsx - Widget registry:
arcana-screen/src/components/widgets/WidgetConfig.tsx - Widget store:
arcana-screen/src/store/useWidgetStore.ts - Grid layout:
arcana-screen/src/components/Grid.tsx - Theme variables:
arcana-screen/src/index.css