Skip to content

Nocturne redesign#51

Open
alanhoskins wants to merge 25 commits into
behavior3:masterfrom
alanhoskins:nocturne-redesign
Open

Nocturne redesign#51
alanhoskins wants to merge 25 commits into
behavior3:masterfrom
alanhoskins:nocturne-redesign

Conversation

@alanhoskins

Copy link
Copy Markdown

No description provided.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant