Reset core/viewer/editor/mcp packages and apps/editor from private-editor

Wholesale swap of packages/{core,viewer,editor,mcp} and apps/editor with the
versions from the private editor repo, which is the production source of truth.

Setup changes:
- packages/{core,viewer,editor} versions held at 0.7.0 baseline (matching
  the most recent published release) so a bump=minor publishes 0.8.0
- packages/mcp held at 0.1.1 (never published; first publish will go through
  the new release.yml flow)
- peerDependencies and devDependencies for inter-package @pascal-app/*
  references pinned to ^0.7.0 instead of '*' / 'workspace:*' so they are
  valid for npm consumers
- Root package.json: TypeScript bumped to 6.0.2, added overrides for
  @types/react, @types/react-dom, @types/three to prevent JSX namespace
  fragmentation across the workspace
- release.yml extended to also publish editor and mcp; 'both' option renamed
  to 'all'; added a sync step that updates inter-package peerDeps/devDeps to
  match the new versions on every bump (so viewer/editor/mcp tarballs always
  reference the version of core they were built against)
- Root scripts gained release:editor and release:mcp shortcuts

Verification:
- bun install --frozen-lockfile is consistent
- packages/{core,viewer,mcp} build cleanly, dist/index.d.ts emitted
- packages/editor check-types reports 21 pre-existing errors, identical to
  what private-editor currently reports

Open PRs against editor-v2 will need rebasing/conflict resolution.
This commit is contained in:
Wawa
2026-05-09 20:52:26 +00:00
parent 146f0a846f
commit e618175bba
196 changed files with 5899 additions and 3966 deletions
+128 -11
View File
@@ -1,3 +1,4 @@
import { createHash } from 'node:crypto'
import { mkdirSync } from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
@@ -6,6 +7,8 @@ import { z } from 'zod'
import { generateSlug, isValidSlug, sanitizeSlug } from './slug'
import { openSqliteDatabase, type SqliteDatabase } from './sqlite-driver'
import {
type ProjectCreateOptions,
type ProjectStatus,
type SceneEvent,
type SceneEventAppendOptions,
type SceneEventListOptions,
@@ -58,6 +61,15 @@ interface SceneEventRow {
graph_json: string
}
interface ProjectPlaceholder {
id: string
name: string
ownerId: string | null
thumbnailUrl: string | null
createdAt: string
updatedAt: string
}
const GraphSchema = z.object({
nodes: z.record(z.string(), z.unknown()),
rootNodeIds: z.array(z.string()),
@@ -116,6 +128,7 @@ function resolveMaxSceneBytes(
}
function rowToMeta(row: SceneRow): SceneMeta {
const editorUrl = editorUrlForScene(row.id)
return {
id: row.id,
name: row.name,
@@ -127,6 +140,66 @@ function rowToMeta(row: SceneRow): SceneMeta {
updatedAt: row.updated_at,
sizeBytes: row.size_bytes,
nodeCount: row.node_count,
editorUrl,
url: editorUrl,
published: true,
graphHash: hashGraphJson(row.graph_json),
}
}
function editorUrlForScene(id: string): string {
return `/editor/${id}`
}
function hashGraphJson(graphJson: string): string {
return createHash('sha256').update(graphJson).digest('hex')
}
function rowToProjectStatus(row: SceneRow): ProjectStatus {
const editorUrl = editorUrlForScene(row.id)
return {
id: row.id,
projectId: row.project_id ?? row.id,
name: row.name,
editorUrl,
url: editorUrl,
ownerId: row.owner_id,
thumbnailUrl: row.thumbnail_url,
publishedVersion: row.version,
latestVersion: row.version,
draftVersion: null,
browserVisibleVersion: row.version,
version: row.version,
isEmpty: row.node_count === 0,
sizeBytes: row.size_bytes,
nodeCount: row.node_count,
graphHash: hashGraphJson(row.graph_json),
createdAt: row.created_at,
updatedAt: row.updated_at,
}
}
function placeholderToProjectStatus(project: ProjectPlaceholder): ProjectStatus {
const editorUrl = editorUrlForScene(project.id)
return {
id: project.id,
projectId: project.id,
name: project.name,
editorUrl,
url: editorUrl,
ownerId: project.ownerId,
thumbnailUrl: project.thumbnailUrl,
publishedVersion: null,
latestVersion: null,
draftVersion: null,
browserVisibleVersion: null,
version: 0,
isEmpty: true,
sizeBytes: 0,
nodeCount: 0,
graphHash: null,
createdAt: project.createdAt,
updatedAt: project.updatedAt,
}
}
@@ -205,6 +278,7 @@ export class SqliteSceneStore implements SceneStore {
readonly databasePath: string
private readonly maxSceneBytes: number
private readonly projectPlaceholders = new Map<string, ProjectPlaceholder>()
private db: SqliteDatabase | null = null
private dbPromise: Promise<SqliteDatabase> | null = null
@@ -214,6 +288,38 @@ export class SqliteSceneStore implements SceneStore {
this.maxSceneBytes = resolveMaxSceneBytes(env, opts.maxSceneBytes)
}
async createProject(opts: ProjectCreateOptions): Promise<ProjectStatus> {
const db = await this.database()
assertValidName(opts.name)
const id = opts.id ? sanitizeSlug(opts.id) : this.generateUniqueId(db)
if (!isValidSlug(id)) {
throw new SceneInvalidError(`Invalid project id after sanitization: "${id}"`)
}
if (this.getRow(db, id)) {
throw new SceneInvalidError(`Project with id "${id}" already exists`)
}
const now = new Date().toISOString()
const project: ProjectPlaceholder = {
id,
name: opts.name,
ownerId: opts.ownerId ?? null,
thumbnailUrl: null,
createdAt: now,
updatedAt: now,
}
this.projectPlaceholders.set(id, project)
return placeholderToProjectStatus(project)
}
async getProjectStatus(id: string): Promise<ProjectStatus | null> {
const db = await this.database()
const safeId = sanitizeSlug(id)
const row = this.getRow(db, safeId)
if (row) return rowToProjectStatus(row)
const placeholder = this.projectPlaceholders.get(safeId)
return placeholder ? placeholderToProjectStatus(placeholder) : null
}
async save(opts: SceneSaveOptions): Promise<SceneMeta> {
return this.withWriteTransaction((db) => {
assertValidName(opts.name)
@@ -228,6 +334,7 @@ export class SqliteSceneStore implements SceneStore {
}
const existing = this.getRow(db, id)
const placeholder = this.projectPlaceholders.get(id)
if (existing && providedId !== undefined && opts.expectedVersion === undefined) {
throw new SceneInvalidError(
@@ -254,8 +361,12 @@ export class SqliteSceneStore implements SceneStore {
const now = new Date().toISOString()
const version = (existing?.version ?? 0) + 1
const createdAt = existing?.created_at ?? now
const createdAt = existing?.created_at ?? placeholder?.createdAt ?? now
const nodeCount = Object.keys(opts.graph.nodes ?? {}).length
const projectId = opts.projectId ?? existing?.project_id ?? (placeholder ? id : null)
const ownerId = opts.ownerId ?? existing?.owner_id ?? placeholder?.ownerId ?? null
const thumbnailUrl =
opts.thumbnailUrl ?? existing?.thumbnail_url ?? placeholder?.thumbnailUrl ?? null
if (existing) {
db.query(
@@ -272,9 +383,9 @@ export class SqliteSceneStore implements SceneStore {
WHERE id = ?`,
).run(
opts.name,
opts.projectId ?? null,
opts.ownerId ?? null,
opts.thumbnailUrl ?? null,
projectId,
ownerId,
thumbnailUrl,
version,
now,
sizeBytes,
@@ -291,9 +402,9 @@ export class SqliteSceneStore implements SceneStore {
).run(
id,
opts.name,
opts.projectId ?? null,
opts.ownerId ?? null,
opts.thumbnailUrl ?? null,
projectId,
ownerId,
thumbnailUrl,
version,
createdAt,
now,
@@ -307,19 +418,25 @@ export class SqliteSceneStore implements SceneStore {
`INSERT INTO scene_revisions (
scene_id, version, graph_json, author_kind, author_id, created_at
) VALUES (?, ?, ?, ?, ?, ?)`,
).run(id, version, graphJson, 'mcp', opts.ownerId ?? null, now)
).run(id, version, graphJson, 'mcp', ownerId, now)
this.projectPlaceholders.delete(id)
return {
id,
name: opts.name,
projectId: opts.projectId ?? null,
ownerId: opts.ownerId ?? null,
thumbnailUrl: opts.thumbnailUrl ?? null,
projectId,
ownerId,
thumbnailUrl,
version,
createdAt,
updatedAt: now,
sizeBytes,
nodeCount,
editorUrl: editorUrlForScene(id),
url: editorUrlForScene(id),
published: true,
graphHash: hashGraphJson(graphJson),
}
})
}
+55 -2
View File
@@ -10,7 +10,7 @@ export interface SceneMeta {
name: string
projectId: string | null
thumbnailUrl: string | null
/** Monotonic, incremented on every save. */
/** Browser-visible model version. Draft saves may update the same version repeatedly. */
version: number
/** ISO 8601 timestamp. */
createdAt: string
@@ -19,6 +19,18 @@ export interface SceneMeta {
ownerId: string | null
sizeBytes: number
nodeCount: number
/** Browser route agents should return to users. Hosted apps should prefer /editor/<projectId>. */
editorUrl?: string
/** Backward-compatible alias for clients that still read url. */
url?: string
/** True when this save is browser-visible without a separate publish call. */
published?: boolean
/** True when the saved graph is still the mutable browser-visible draft. */
isDraft?: boolean
/** How the scene was saved. Draft saves should not create meaningful history versions. */
saveMode?: SceneSaveMode
/** Stable hash of the graph payload used for save/load/status matching. */
graphHash?: string
}
export interface SceneWithGraph extends SceneMeta {
@@ -43,8 +55,18 @@ export interface SceneSaveOptions {
thumbnailUrl?: string | null
/** When set, save fails with `SceneVersionConflictError` on mismatch. */
expectedVersion?: number
/** `draft` updates the browser-visible working model; `checkpoint` records version history. */
saveMode?: SceneSaveMode
/** Whether a checkpoint should become the published/browser-visible head. */
publish?: boolean
/** Optional hosted MCP session id for project presence/debug metadata. */
agentSessionId?: string
/** Optional high-level operation name for presence/debug metadata. */
operation?: string
}
export type SceneSaveMode = 'draft' | 'checkpoint'
export interface SceneListOptions {
projectId?: string
ownerId?: string
@@ -67,8 +89,39 @@ export interface SceneEventListOptions {
limit?: number
}
export interface ProjectCreateOptions {
id?: SceneId
name: string
ownerId?: string | null
isPrivate?: boolean
}
export interface ProjectStatus {
id: SceneId
projectId: string
name: string
editorUrl: string
url: string
ownerId: string | null
thumbnailUrl: string | null
publishedVersion: number | null
latestVersion: number | null
draftVersion: number | null
browserVisibleVersion: number | null
/** Alias for the browser-visible/latest meaningful version. */
version: number
isEmpty: boolean
sizeBytes: number
nodeCount: number
graphHash: string | null
createdAt: string
updatedAt: string
}
export interface SceneStore {
readonly backend: 'sqlite'
readonly backend: 'sqlite' | 'supabase'
createProject?(opts: ProjectCreateOptions): Promise<ProjectStatus>
getProjectStatus?(id: SceneId): Promise<ProjectStatus | null>
save(opts: SceneSaveOptions): Promise<SceneMeta>
load(id: SceneId): Promise<SceneWithGraph | null>
list(opts?: SceneListOptions): Promise<SceneMeta[]>