Add FxNode render graph playground

Add persistent render graph loadouts, grouped node authoring, code-appending apply behavior, explicit playground lighting, and resilient worker startup.

Amp-Thread-ID: https://ampcode.com/threads/T-01a033d4-0e44-708e-af0e-6166283416ff
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-24 17:45:48 +00:00
co-authored by heaust
parent 951449db0b
commit 6153223fd5
27 changed files with 4672 additions and 207 deletions
+54 -1
View File
@@ -2,6 +2,8 @@ package main
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -66,12 +68,52 @@ func TestHealth(t *testing.T) {
}
}
func TestPlaygroundStoreMigratesLoadouts(t *testing.T) {
path := filepath.Join(t.TempDir(), "legacy.db")
database, err := sql.Open("sqlite3", path)
if err != nil {
t.Fatal(err)
}
if _, err := database.Exec(`
CREATE TABLE playgrounds (
id TEXT NOT NULL,
revision INTEGER NOT NULL,
title TEXT NOT NULL,
code TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (id, revision)
)
`); err != nil {
database.Close()
t.Fatal(err)
}
if err := database.Close(); err != nil {
t.Fatal(err)
}
store, err := openPlaygroundStore(path)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { store.Close() })
saved, err := store.save(context.Background(), "", "Migrated", "const migrated = true;", "")
if err != nil {
t.Fatal(err)
}
if string(saved.Loadouts) != "[]" {
t.Fatalf("loadouts = %s", saved.Loadouts)
}
}
func TestSaveAndLoadPlaygroundRevision(t *testing.T) {
app := testApplication(t)
handler := app.routes()
requestBody, _ := json.Marshal(map[string]string{
requestBody, _ := json.Marshal(map[string]any{
"title": "First scene",
"code": "const scene = true;",
"loadouts": []map[string]any{{
"id": "custom", "name": "Custom", "layout": map[string]any{},
}},
})
request := httptest.NewRequest(http.MethodPost, "/api/playgrounds", bytes.NewReader(requestBody))
response := httptest.NewRecorder()
@@ -118,6 +160,17 @@ func TestSaveAndLoadPlaygroundRevision(t *testing.T) {
if loaded.Code != "const scene = true;" || loaded.Title != "First scene" {
t.Fatalf("loaded = %#v", loaded)
}
var loadouts []struct {
ID string `json:"id"`
Name string `json:"name"`
Layout map[string]any `json:"layout"`
}
if err := json.Unmarshal(loaded.Loadouts, &loadouts); err != nil {
t.Fatal(err)
}
if len(loadouts) != 1 || loadouts[0].ID != "custom" || loadouts[0].Name != "Custom" || len(loadouts[0].Layout) != 0 {
t.Fatalf("loadouts = %s", loaded.Loadouts)
}
request = httptest.NewRequest(http.MethodGet, "/api/playgrounds/"+saved.ID+"/2", nil)
response = httptest.NewRecorder()