Nocturne redesign#51
Open
alanhoskins wants to merge 25 commits into
Open
Conversation
Update url
Monetization phase 1 for behaviortrees.com: - Add Astro content site in site/ with seven behavior tree guides served at /learn/, including per-page meta tags, JSON-LD structured data, sitemap, robots.txt, newsletter signup, and affiliate links - Add ?example=<name> URL loading to the editor: fetches a tree from /examples/<name>.json into an "Examples" project and opens the editor; ship three example trees (enemy-patrol, open-the-door, robot-pick-and-place) copied to build/examples by a new gulp task - Add SEO meta tags, Open Graph/Twitter cards, canonical URL, OG image, and Plausible analytics snippet to the editor index.html - Link the editor home page to the new guides and examples - Restore src/app/services/storage/ (accidentally deleted in fa858f6, which broke the editor at boot on master) - Fix tree import crash: ImportManager passed true as the root arg of organize(); fix orderByIndex self-assignment in OrganizeManager - Fix gulp 3 on modern Node (16-22) via graceful-fs ^4 npm override - Add build-deploy.sh to build editor + site and merge into deploy/, and vercel.json so Vercel builds and serves deploy/ on push Author: Alan Hoskins
The Netlify site (behaviortrees.com) was failing to build: - The Netlify UI had NODE_VERSION pinned to 8.17.0 from the original behavior3 era; netlify.toml now pins Node 22, which the whole toolchain supports (gulp 3 via the graceful-fs override, Astro) - The root package.json carried a stray "packageManager": "pnpm@9" field (belongs to behaviour-tree-editor/, which has its own manifest); Netlify tried to install pnpm and failed on Node 8. Removed it and added an engines range instead - netlify.toml sets build command bash build-deploy.sh, publishes deploy/, and passes NPM_FLAGS=--ignore-scripts so Netlify's automatic npm install skips the dead electron-prebuilt postinstall Author: Alan Hoskins
Production behaviortrees.com deploys via Netlify (netlify.toml). The Vercel project serving new.behaviortrees.com builds the React app from behaviour-tree-editor/, but it picked up the repo-root vercel.json install command, which ran npm/bower against the wrong directory and failed. Removing the file restores Vercel's auto-detected Vite + pnpm build for that project. Author: Alan Hoskins
- Append ?v=<version>-<date> to all css/js asset URLs in index.html so returning visitors with cached bundles (same filenames for years) pick up new builds; this is why ?example= loading appeared broken for existing users while working in fresh browsers - Boot no longer hangs on the preload screen if the most recent project in localStorage fails to load: a corrupt recent project now falls through to a normal start (and example loading still runs) Author: Alan Hoskins
The rewrite previously used its own JSON schema (blocks/position/ rootId/viewport), which could not open files from the old editor or produce files it accepts. This adds full behavior3 interop: - New src/lib/behavior/b3.ts: treeToB3/projectToB3/customNodesToB3 export and b3ToTree/b3ToProject/b3ToNodes import implementing the behavior3 schema exactly (root block excluded and hoisted to tree level, display/camera coords, child for decorators vs children for composites, project as trees array + custom_nodes array) - parseImportedJson detects project/tree/nodes files, including legacy React-schema saves, which are converted and their lowercase node names mapped to behavior3 names - Imported trees without display info are auto-laid-out using the old editor's horizontal organize algorithm - Import UI now handles all three scopes: project files replace the project, tree files add a tree (new addImportedTree store action), node files register custom nodes (new addNodes store action); standalone tree files imported from the Projects page become a single-tree project - Export modal and all localStorage persistence now write behavior3 - Complete default palette: all 16 behavior3 nodes with exact names (adds MemSequence, MemPriority, RepeatUntilFailure, RepeatUntilSuccess, MaxTime, Limiter, Runner, Error; removes the non-standard selector/condition entries), root blocks named Root - Canvas refits the viewport and clears the position cache when the selected tree changes, so imported trees are visible immediately - Add vitest with 14 round-trip tests using the shipped example trees as fixtures (import->export semantic equality, export->import-> export stability, old-format shape guarantees, legacy conversion) Verified end to end in the browser: importing examples/enemy-patrol .json renders all 12 blocks auto-organized, and the saved project in localStorage is valid behavior3. Author: Alan Hoskins
createConnection now implements the old editor's ConnectionSystem semantics instead of only blocking a second parent: - Connections into a root block, from action/condition leaves, and self-connections are rejected (returns null; no undo-stack entry) - A block keeps a single parent: connecting an already-parented block re-parents it, removing the old incoming connection - Root and decorator sources keep exactly one child: a new connection replaces the previous child connection (previously a decorator accepted unlimited children, producing trees behavior3 runtimes reject) The canvas mirrors the reject rules via ReactFlow's isValidConnection so invalid drops give immediate visual feedback; the store remains authoritative for both drag-to-connect paths. Adds 8 store tests covering composite/decorator/root arity, re-parenting, and rejection cases (22 tests total). Author: Alan Hoskins
Answers "can we prove parity" with three layers of tests: - Fixtures captured from the real old editor (deploy build driven headlessly, exported via its own ExportManager) committed under __fixtures__/: parity.test.ts verifies imports preserve every node position exactly (no auto-layout when display data exists) and that re-exports reproduce the old editor's tree payload field-for-field, plus project-level trees/custom nodes/selection - Palette parity tests pin DEFAULT_NODES to the old editor's exact 16-node registration (names, categories, behavior3js parameter defaults) - cross-editor.e2e.test.ts drives the REAL old editor in a browser: everything the new editor exports (three example trees + a full project) is imported through the old ImportManager and re-exported through its ExportManager with identical semantics and zero page errors; skips automatically unless OLD_EDITOR_URL/CHROMIUM_PATH are set (see file header for how to run) 29 tests total; unit suite stays hermetic for CI. Author: Alan Hoskins
Direct navigation to routes like /projects returned Vercel 404s because the React app uses BrowserRouter with no rewrite rule. The vercel.json lives in behaviour-tree-editor/ so it only applies to the Vercel project rooted there, not to the Netlify production site. Author: Alan Hoskins
scripts/live-parity.cjs runs the bidirectional production check on demand: capture a real export from behaviortrees.com, import it into new.behaviortrees.com through the UI, verify identical semantics and exact positions, then round-trip the new editor's persisted project back through the old editor's import/export. Intended as the gate before the eventual DNS flip. Author: Alan Hoskins
Keyboard shortcuts matching the old editor's menubar bindings: ctrl/cmd+z undo, ctrl/cmd+shift+z (or +y) redo, ctrl/cmd+c/x/v copy/cut/paste, ctrl/cmd+d duplicate, ctrl/cmd+a select all, ctrl/cmd+shift+a deselect, ctrl/cmd+i invert selection, "a" auto organize, ctrl/cmd+s save. Shortcuts ignore typing contexts. Store edit operations: - copyBlocks/cutBlocks/pasteClipboard/duplicateBlocks with a per-project clipboard (cleared on project create/load, like the old editor's project-scoped clipboard); clones get fresh ids, offset positions, and keep the connections between copied blocks; root blocks are never copied - organize(treeId, layout): OrganizeManager port extracted to lib/behavior/organize.ts with horizontal AND vertical layouts and the old root-position-preserving offset; b3 import now reuses it (byIndex mode honors file child order) - updateNode supports renames: re-keys the template, updates blocks in every tree, refuses collisions UI: - Toolbar undo/redo buttons (disabled when stacks are empty) and an auto-organize button (right-click toggles horizontal/vertical); canvas listens for a bt-canvas-refresh event and drops its position cache so store-side position changes (undo/redo/organize) render - Full custom-node editor modal (name/category/title/description + properties table) replacing the bare name prompt; custom palette entries get hover edit/delete actions - Properties panel can add and remove property keys on blocks 16 new store tests (39 total). Verified in the browser: paste/ duplicate/undo/redo counts, drag-then-organize repositioning, vertical layout, custom node creation, property add — zero page errors. Author: Alan Hoskins
The harness wrote its captured export and diff files next to itself, which leaked one into the previous commit. They now go to os.tmpdir(). Author: Alan Hoskins
Store gains a real project lifecycle: - saveProject actually persists (behavior3 JSON under bt-project-<id> plus a bt-current-project pointer) instead of console.log; the 8 scattered inline serialize+setItem blocks in the canvas and layout now call it - createProject persists immediately; loadProject records the pointer - restoreLastProject reopens the last project on startup (wired in App mount), matching the old editor's recents behavior - renameProject and closeProject; renameTree also syncs the root block title/description as behavior3 requires UI: - Trees panel: rename via pencil or double-click with inline input - Projects page: Open badge on the current project, open/close/rename (inline, persists for closed projects by rewriting their stored payload), delete confirms and closes if it was open - Editor page only auto-creates the demo project on a genuine first visit (no stored projects); closing a project now lands on the No Project Open screen, whose buttons now link to /projects 7 new store tests with a localStorage stub (46 total). Verified in the browser: save -> reload restores the project with all trees, tree/project rename persist across reload, close shows the empty state — zero page errors. Author: Alan Hoskins
Prepares the domain swap: the React editor becomes behaviortrees.com on Vercel, the classic editor stays available at old.behaviortrees.com on Netlify. React app: - index.html gets the production SEO head (title, description, canonical to www.behaviortrees.com, OG/Twitter cards, og-image, favicon, Plausible) matching what the classic editor shipped - /?example=<name> deep links now work: ExampleLoader fetches /examples/<name>.json and opens it in a stable "Examples" project, so every "open in editor" link in the /learn guides keeps working unchanged after the swap; examples accumulate in that project - vite config serves ../src/examples in dev and copies them into dist/examples on build — single source of truth with the classic editor; favicon + og-image live in public/imgs at the same URLs the guides reference - Home page: guides + example links, and a pointer to the classic editor; header nav gains Learn; footer links learn + classic editor - @types/node added for the node-side vite config Deploy: - vercel.json now installs and builds ../site (Astro guides) and merges it into dist, so the Vercel project serves the editor at /, guides at /learn/, examples, robots.txt and sitemap — identical URLs to current production, no SEO churn - /learn article footers link the classic editor Verified on the merged output: SEO meta, both example deep links, guides pages, robots/sitemap/examples/imgs all 200, zero page errors. 46 unit tests green. Author: Alan Hoskins
The phase E deploy failed on Vercel: the build command copied ../site/dist without ever building the Astro site (locally it had been built beforehand). The build command now runs the Astro build before merging. Verified by running the exact command locally. Dependency updates (semver-compatible only): - behaviour-tree-editor: react 19.2, react-router-dom 7.18, zustand 5.0.14, tailwind 4.3, radix minors, sonner, tooling minors. pnpm audit --prod: no known vulnerabilities; remaining findings are dev-only chains behind majors (vite 8, typescript 7, eslint 10, zod 4, uuid 14, immer 11, lucide 1.x) intentionally deferred - site: in-range updates; remaining advisory is esbuild dev-server file read (Windows dev only, no production exposure) whose fix is the astro 7 major — deferred 46 unit tests green after updates. Author: Alan Hoskins
Examples ship with no top-level `display` key, so ImportManager.treeAsData left the camera at (0,0), mapping tree-local origin to the canvas top-left. Since organize() lays the tree out symmetrically above and below y=0, the upper half landed off-screen and the left edge sat behind the sidebar. Add getContentBounds/getViewport/centerOnContent/fitToScreen to ViewManager, built on the existing setCam and zoom primitives. The viewport accounts for the sidebar and menubar insets so the tree is centered in the visible area rather than the raw canvas, and the fitted zoom snaps down to a zoom_step multiple, capped at zoom_initial, so it only ever zooms out. Insets live in settings rather than being measured from the DOM: src/editor is DOM-agnostic, and at import time the editor state has not rendered yet so the sidebars are not in the document. Call fitToScreen from the existing `if (!data.display)` branch, which already means "this file carries no layout information, so compute it". This also covers File > Import of display-less JSON. Saved projects still restore their exact camera, since they carry display.camera_*. Also expose it manually as View > Center view, bound to `c`.
The example loader only accepted tree files, so a project-format example threw "example is not a tree file". Branch on the payload kind the way the classic editor does (app.js:68-72, ImportManager.js:4-14): project files merge their custom nodes and every tree into the open project, tree files import as a single tree. Extends scripts/live-parity.cjs to cover the boot path and adds boot-parity.test.tsx plus docs/boot-parity.md.
Replaces the hardcoded slate/emerald palette with a token layer in src/index.css, in three parts: static ramps and scales in @theme, the theme-flippable semantic values as plain CSS under :root/[data-theme], and an @theme inline bridge so Tailwind utilities generate. The inline bridge is required — without it the value resolves once at :root and nested overrides silently no-op. Setting --spacing to 0.175rem reproduces Nocturne's 0.70x density scale, so existing spacing utilities retune without component edits. The canvas opts back out to the stock 4px base, since the density also shrank the connection handles and an 8px hit-target competes with panOnDrag. Theme switching never actually worked: dark: was media-query based while the app toggled a .dark class. Rather than port 259 dark: variants, the tokens themselves are theme-aware and every variant is gone. Theme is applied by an inline script before first paint, and the editor and guides share the bt-theme key on one origin. Also drops tailwind.config.js, which was never loaded (no @config) and would have crashed if it were (require() in an ESM module), so tailwindcss-animate had never applied; tw-animate-css replaces it. Self-hosts Inter as a variable woff2 with a metric-matched fallback.
Moves the node palette into the left rail under Trees, leaving the right rail to Properties at full height. All three sections collapse independently and hand their space to their sibling — Nodes was pinned at h-1/2, so collapsing Properties used to leave half the rail empty. Both rails are drag-resizable, with widths and collapse state persisted to bt-panel-layout; collapse previously lived in component state and was lost on every navigation away from /editor. The palette's category strip gains overflow arrows that appear only on the side with hidden tabs, driven by a ResizeObserver since the rail resizes without firing scroll events. Properties now take a key and a value together, as the classic key-table did (keytable.directive.js). Value coercion keeps that directive's numeric rule and adds real booleans, which it never supported. Values commit on blur or Enter rather than per keystroke — updateBlock deep-clones the whole project onto the undo stack, so a ten-character value cost ten undo entries. Duplicate keys now report instead of failing silently. The canvas is untouched: EditorLayout's prop contract is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.