Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1c4b5e6
Change URL
alanhoskins Jul 6, 2018
eddc0ca
Update home.html
alanhoskins Jul 6, 2018
c6bc187
Merge pull request #1 from alanhoskins/update-url
alanhoskins Jul 6, 2018
fa858f6
start adding modern version of the editor
alanhoskins May 3, 2025
ef40487
prevent nodes from jumping around.
alanhoskins May 3, 2025
5c14517
some updates
alanhoskins May 3, 2025
8a006a0
fix build issues
alanhoskins May 5, 2025
a754722
Add /learn content site, example loading, SEO, and Vercel deploy
alanhoskins Jul 21, 2026
9f7b0f7
Fix Netlify build: pin Node 22 and drop stray pnpm packageManager
alanhoskins Jul 21, 2026
6b90fd3
Remove root vercel.json that broke the new.behaviortrees.com build
alanhoskins Jul 21, 2026
56991fc
Bust asset caches per deploy and harden editor boot
alanhoskins Jul 21, 2026
3aa319f
Make React editor read/write behavior3 format (parity phase A)
alanhoskins Jul 21, 2026
6f5d2e7
Enforce behavior3 connection rules in React editor (parity phase B)
alanhoskins Jul 21, 2026
18ee83a
Add cross-editor parity test suite
alanhoskins Jul 21, 2026
01c9e7a
Add SPA fallback rewrite for new.behaviortrees.com
alanhoskins Jul 21, 2026
21d143e
Add repeatable live-parity harness script
alanhoskins Jul 21, 2026
f38252f
Add editing productivity layer to React editor (parity phase C)
alanhoskins Jul 21, 2026
254ec18
Write live-parity artifacts to the temp dir, drop committed capture
alanhoskins Jul 21, 2026
2ada074
Add project management parity to React editor (parity phase D)
alanhoskins Jul 21, 2026
27d6a4c
Ship the swap-ready deploy for behaviortrees.com (parity phase E)
alanhoskins Jul 21, 2026
987cd72
Fix Vercel build to actually build the guides site; update deps
alanhoskins Jul 21, 2026
cedbdc9
Center and fit the view when a tree is imported without camera data
alanhoskins Jul 21, 2026
c5c26ae
Load project-format examples, matching the classic editor
alanhoskins Jul 21, 2026
7017214
Restyle the editor and guides site onto the Nocturne design system
alanhoskins Jul 21, 2026
6c6a16b
Rework the editor panel layout and property editing
alanhoskins Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ bower_components
dist
.temp-dist
build
deploy
site/dist
src/vendor
cache
teste.py

ignore-*

**/.claude/settings.local.json
31 changes: 31 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands
- `npm install` & `bower install` - Install dependencies
- `gulp serve` - Development mode with live reload (http://127.0.0.1:8000)
- `gulp dev` - Build for development
- `gulp build` - Build for production
- `gulp dist` - Package for desktop distribution

## Linting
- JSHint is used for linting (integrated in build tasks)
- No explicit lint command, but linting happens during build

## Code Style
- Follow AngularJS style guide (camelCase for variables and functions)
- Dependency injection using array syntax for minification safety
- Use descriptive function and variable names
- Follow existing patterns in the codebase for new components
- Four space indentation, maintain consistent whitespace

## Project Structure
- Angular modules in src/app
- Editor core in src/editor
- LESS styling in src/assets/less
- Follow existing file organization for new components

## Error Handling
- Use editor.error.js for editor-related errors
- Follow existing error handling patterns in similar components
24 changes: 24 additions & 0 deletions behaviour-tree-editor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
97 changes: 97 additions & 0 deletions behaviour-tree-editor/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a modernized version of the Behavior3 Editor, reimplemented using modern web technologies:

- Vite + React 19
- TypeScript
- Tailwind CSS v4
- Zustand for state management
- ReactFlow for canvas editor

## Build Commands

- `pnpm install` - Install dependencies
- `pnpm run dev` - Run development server
- `pnpm run build` - Build for production
- `pnpm run lint` - Run ESLint on codebase
- `pnpm run preview` - Preview production build locally

## Code Style

- Use TypeScript with strict typing
- Follow React hooks best practices
- Use Zustand for state management
- Organize imports alphabetically
- Prefer named exports over default exports
- Use functional components with hooks
- Use Tailwind CSS for styling
- Maintain proper error handling with try/catch blocks

## Project Structure

```
src/
├── components/ # React components
│ ├── editor/ # Tree editor components
│ │ ├── edges/ # Custom edge components
│ │ └── nodes/ # Custom node components
│ ├── layouts/ # Layout components
│ ├── panels/ # Panel components
│ └── ui/ # ShadCN UI components
├── lib/ # Core functionality
│ ├── behavior/ # Behavior tree logic
│ └── utils.ts # Utility functions
├── pages/ # Page components
│ ├── editor/ # Editor page
│ ├── home/ # Home page
│ ├── projects/ # Projects page
│ └── settings/ # Settings page
├── stores/ # Zustand state stores
└── types/ # TypeScript type definitions
```

## Architecture Details

- Behavior trees are rendered using ReactFlow
- Component-based architecture with reusable UI components
- Data is persisted using localStorage
- Custom node types for different behavior tree node categories
- Zustand handles state with immer middleware for immutable updates
- ShadCN UI components for consistent design

## Important Implementation Notes

### Node Position Handling

To prevent nodes from jumping when adding/editing nodes:

1. **Position Caching System**:
- We use `prevNodePositionsRef` to cache node positions between renders
- This cache takes precedence over positions from the store
- In `syncTreeToFlow`, we prioritize cached positions over store positions

2. **Optimized State Updates**:
- ReactFlow state is updated immediately for smooth UI
- Store updates are batched with `requestAnimationFrame`
- Position updates are always rounded to a grid (15px intervals)

3. **Root Node Special Handling**:
- The root node has special rendering (blue styling)
- Root node position is always preserved during tree sync operations
- Root node is protected from deletion

4. **Smart Diffing**:
- The sync function uses smart diffing to avoid unnecessary re-renders
- It only updates nodes when actual data (excluding position) changes
- This prevents cascading updates that could cause position jumps

When modifying node positions or editing node operations, make sure to:
- Update the position cache when directly modifying node positions
- Use rounded position values (Math.round(position.x / 15) * 15)
- Apply UI state changes before store updates for responsive feedback
- Use requestAnimationFrame for batched updates
- Ensure position values are properly passed to both node.position and node.data.position
54 changes: 54 additions & 0 deletions behaviour-tree-editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
```
9 changes: 9 additions & 0 deletions behaviour-tree-editor/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Advanced Features
- [ ] Add node validation rules
- [ ] Implement tree simulation/debugging
- [ ] Add more export formats

## Electron Integration
- [ ] Set up electron.js for desktop packaging
- [ ] Implement file system storage bridges
- [ ] Create platform-specific builds
91 changes: 91 additions & 0 deletions behaviour-tree-editor/docs/boot-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Boot parity with the classic editor

The classic editor's [`src/app/app.js`](../../src/app/app.js) is not tree logic — it is the
**boot contract**: everything that happens between page load and a usable editor. Format-level
parity is covered elsewhere (see [Related coverage](#related-coverage)); this document tracks
the startup obligations specifically, and which of them this app is expected to honour.

Automated coverage lives in [`src/boot-parity.test.tsx`](../src/boot-parity.test.tsx) (headless,
runs with `pnpm test`) and steps 4–5 of [`scripts/live-parity.cjs`](../scripts/live-parity.cjs)
(real browser, driving the new editor and comparing against the classic editor's step-1 export).

## The contract

| `app.js` | Obligation | Here | Status |
|---|---|---|---|
| :10 | `isDesktop` from Electron globals | — | **Non-goal** — this app is web-only |
| :12-14 | `$rootScope.go` navigation helper | `react-router-dom` | Equivalent by construction |
| :30 | `$location.path('/')` — always boot at home | `BrowserRouter` honours the real URL | **Intentional divergence** — deep links are a feature here |
| :33-35 | Tag the canvas `b3-drop-node` so palette nodes can be dropped | `behavior-tree-editor.tsx` `onDragOver`/`onDrop` | Covered (live harness step 5) |
| :38 | `settingsModel.getSettings()` applies settings to the editor singleton | `behavior-tree-editor.tsx` reads `bt-show-grid`/`bt-auto-save` at editor mount; `app-base.tsx` applies `bt-theme` | Equivalent by different means — there is no global editor singleton to configure |
| :39-93 | Reopen the project the last session left open | `app-base.tsx` → `useProjectStore.restoreLastProject` | Covered |
| :82-84 | A corrupt recent project must not block startup | `restoreLastProject` returns `false` on parse failure | Covered |
| :43-51 | Fade out and remove `#page-preload` | — | **Non-goal** — Vite ships no splash screen |
| :53-77 | `?example=` → open/create the `Examples` project → import → go to the editor | `components/example-loader.tsx` | Covered |
| :68-72 | Branch on `data.trees`: project files import via `projectAsData`, tree files via `treeAsData` | `example-loader.tsx` branches on `parseImportedJson`'s `kind` | Covered (was a real gap — see below) |
| :73 | `editor.clearDirty()` so the leave-guard does not fire | — | **Non-goal** — this app has no `beforeunload` guard to clear |

## Deliberate additions

Behaviour this app has that `app.js` does not. These are choices, not drift, and the tests
pin them so they stay choices:

- **Errors are surfaced.** A missing or malformed `?example=` file fails silently in the
classic editor; here it logs and raises a toast without disturbing startup.
- **A starter project on first visit.** `pages/editor/editor-page.tsx` creates a "Demo Project"
when someone reaches `/editor` with no project *and* no stored projects. The classic editor
shows an empty editor instead. Anyone who closed their project on purpose still gets the
"No Project Open" screen.
- **Bundles without `selectedTree` select the first tree, not the last.** `b3ToProject` defaults
`selectedTreeId` to the first tree when the file omits `selectedTree`, so the example loader
always selects it; the classic editor only selects when the file says to, leaving the
last-added tree active. Visible only for multi-tree bundles with no `selectedTree`, and
first-tree feels like the saner landing spot.

## Gaps found and closed

- **Project-shaped example files were rejected.** `example-loader.tsx` required
`kind === 'tree'` and threw on anything else, while `app.js:68-72` has always branched on
`data.trees`. Latent — all three files in `src/examples/` are tree-shaped — but it would
have broken silently the moment a bundled example was added. The loader now mirrors
`ImportManager.projectAsData`: merge the file's custom nodes and every tree into the open
`Examples` project, then honour its `selectedTree`.

## Notes on ordering

`app.js` *sequences* the restore and the deep link: the recent project resolves, then
`loadExample()` runs, so the example always ends up as the open project. Here they are two
independent effects, and the same end state depends on `ExampleLoader`'s effect running before
`app-base`'s plus `restoreLastProject`'s "already have a project" guard. That is not obvious
from reading either file, so `boot-parity.test.tsx` pins the outcome directly.

## Running the checks

```bash
pnpm test # boot-parity suite plus the format-level tests
```

Against real browsers and the real classic editor:

```bash
# new editor local, classic editor from production
npm run build && npm run preview -- --port 5199 &
CHROMIUM_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
NEW_EDITOR_URL=http://localhost:5199 node scripts/live-parity.cjs
```

Both editors local (needs Node <23 for the classic gulp build):

```bash
./build-deploy.sh && python3 -m http.server 8123 -d deploy &
OLD_EDITOR_URL=http://127.0.0.1:8123 NEW_EDITOR_URL=http://localhost:5199 \
CHROMIUM_PATH=... node scripts/live-parity.cjs
```

## Related coverage

- `src/lib/behavior/parity.test.ts` — round-trips real classic-editor exports; asserts the
16-node behavior3 palette
- `src/lib/behavior/cross-editor.e2e.test.ts` — feeds this app's exports through the real
classic editor's `ImportManager` and re-exports
- `scripts/live-parity.cjs` steps 1–3 — bidirectional file parity between the two live sites
28 changes: 28 additions & 0 deletions behaviour-tree-editor/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
Loading