Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 24 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,36 @@

### Features

- **`view_scene` sees the house as a front photo does.** `from` also takes the sides a photo is named by, standing in the street: `street`, `street-left`, `street-right`, `left`, `right`, `back`, `back-left`, `back-right`, from the street edge the site records, else the house's own front (its −z, the way a generated house faces; core's `buildingStreet`, which the hosted Architect's street-relative tools read too). `camera: 'current'` looks through the person's own view where the host has one (the MCP refuses it, `no_current_camera`), `lighting` renders the picture in a photo's light (`day`, `overcast`, `sunset`, `dusk`, `night`) and `maxPx` makes it smaller (a picture is never longer than 1920 px). A view of a building, or of the whole scene, frames its roofs with its storeys, and a plugin's node on a level (a roof plane) up to its storey's top; a street side with buildings on several sites and no target or interior floor is refused (`which_building`). A `SceneViewHost` receives the `lighting` to render in. `camera-controls:capture-session` is a typed event, counted once in core (`isCaptureSessionOn`, `subscribeCaptureSessions`) from the moment it loads: runs that overlap (a sheet's pictures, an agent's view) present the finished house once and put the person's view back after the last, and a plugin draws its own finished view from the same count.
- **Self-hosting is one container.** `ghcr.io/pascalorg/pascal-editor:<version>` ships the same portable runtime as the CLI, multi-arch, published by the release workflow. The public URL is read per request (`PASCAL_PUBLIC_URL`, replacing the build-time `NEXT_PUBLIC_APP_URL` and `MINT_PASCAL_HOST_ORIGIN`), so one image serves any domain or port. A single admin password (`PASCAL_ADMIN_PASSWORD`) gates every page and API behind `/login`; agents keep the `PASCAL_SCENE_API_TOKEN` bearer; a network host with nothing configured serves a setup page and nothing else; `PASCAL_AUTH=proxy` defers to an authenticating reverse proxy. See `SETUP.md`, "Self-hosting".
- **`view_scene` sees the house as a front photo does.** `from` also takes the sides a photo is named by, standing in the street: `street`, `street-left`, `street-right`, `left`, `right`, `back`, `back-left`, `back-right`, from the street edge the site records, else the house's own front (its −z, the way a generated house faces; core's `buildingStreet`, which the hosted Architect's street-relative tools read too). `camera: 'current'` looks through the person's own view where the host has one (the MCP refuses it, `no_current_camera`), `lighting` renders the picture in a photo's light (`day`, `overcast`, `sunset`, `dusk`, `night`) and `maxPx` makes it smaller (a picture is never longer than 1920 px). A view of a building, or of the whole scene, frames its roofs with its storeys, and a plugin's node on a level (a roof plane) up to its storey's top; a street side with buildings on several sites and no target or interior floor is refused (`which_building`). A `SceneViewHost` receives the `lighting` to render in. `camera-controls:capture-session` is a typed event, counted once in core (`isCaptureSessionOn`, `subscribeCaptureSessions`) from the moment it loads: runs that overlap (a sheet's pictures, an agent's view) present the finished house once and put the person's view back after the last, and a plugin draws its own finished view from the same count. Every picture is planned at 1920 × 1200, and `interior: { levelId }` looks inside one floor ([#1034](https://github.com/pascalorg/editor/pull/1034), [#1051](https://github.com/pascalorg/editor/pull/1051), [#1075](https://github.com/pascalorg/editor/pull/1075))
- **One finder, one node read, one scene check.** `find_by_type` takes `find_nodes`' filters (`parentId`, `zoneId`, `sourceId`, `sourceIdPrefix`, or none, with `limit`), `get_node` gives what `describe_node` added (`ancestorIds`, resolved heights and thicknesses, `displayName`, `missingChildIds`), and `verify_scene` lists every schema error by node and path and the spaces the walls leave without a room ([#1081](https://github.com/pascalorg/editor/pull/1081), [#1084](https://github.com/pascalorg/editor/pull/1084))
- **The site comes first.** `get_site` reads the lot, its buildable area, street, north, ground and zoning, each buildable edge with its frontage and depth, and the 30–45% footprint a house usually covers; `place_building` stands a house on its lot by moving the building node only ([#1044](https://github.com/pascalorg/editor/pull/1044), [#1080](https://github.com/pascalorg/editor/pull/1080))
- **One notation.** Every length and angle of the shared tools reads a bare number in its field's unit or text in any unit (inches, feet, millimetres included); `roofPitch()` reads a pitch as degrees or rise over run (`"6:12"`, `"6/12"`, `"6 in 12"`), and `create_roof`'s `overhang` is the eaves' reach in plan ([#1079](https://github.com/pascalorg/editor/pull/1079))
- **One finish tool.** `paint` gives every part with surfaces a finish (blocks, stairs, cabinets, roof fascias, wall faces by their rooms) and keeps native materials as scene materials; `search_materials` searches and browses the library a page at a time ([#1034](https://github.com/pascalorg/editor/pull/1034), [#1076](https://github.com/pascalorg/editor/pull/1076))
- **L and U stairs.** `create_stair` takes `layout: 'l' | 'u'`, `turn`, `lowerSteps`, railing styles and materials, and `toLevelId: null` for porch or deck steps ([#1073](https://github.com/pascalorg/editor/pull/1073))
- **Roofs on the roof level.** A roof lives on the level directly above the walls that carry it: `create_roof`, the Roof tool, `apply_patch`, paste, duplicate and every other writer seat it there; `verify_scene` reports older roofs (`roof_mixed_in_storey`) and `move_roofs_to_roof_level` or a roof panel's "Move to the roof level" moves them without changing how they look ([#1039](https://github.com/pascalorg/editor/pull/1039), [#1047](https://github.com/pascalorg/editor/pull/1047), [#1057](https://github.com/pascalorg/editor/pull/1057))
- **Gable overhang.** A roof segment's `gableOverhang` sets how far its gable ends reach past their walls, apart from the eaves, with a "Gable overhang" slider; left out, the ends reach as the eaves do ([#1082](https://github.com/pascalorg/editor/pull/1082))
- **Barrel and glazed roofs.** `roofType: 'barrel'` arches a segment; `roofSystem: 'glazed'` builds a gable, straight shed or barrel roof as panes and framing, with grids, panel overrides and `glazing-frame` / `glazing-glass` / `glazing-solid` paint slots. See `wiki/glazed-roofs.md` ([#1055](https://github.com/pascalorg/editor/pull/1055))
- **Blender geometry scripts.** An authored object, window, door or column can be a `bpy` module (`language: 'bpy'`), built by a host-configured `BlenderScriptBuilder` or, on a local MCP, the binary `PASCAL_BLENDER` names. See `wiki/blender-geometry-scripts.md` ([#1033](https://github.com/pascalorg/editor/pull/1033))
- **Plugin agent tools.** A plugin ships `./agent-tools` contracts and `./agent-operations` beside its manifest; an MCP host registers them with `registerOperationTool`, and a chat extension can add cards, `/` actions and a start screen ([#1026](https://github.com/pascalorg/editor/pull/1026), [#1043](https://github.com/pascalorg/editor/pull/1043))
- **Views.** The stage is one or two panes, each a view: 3D, 2D or a plugin's (`registerEditorHostView`, `openEditorView`), with a view bar per pane and a plugin directory view (`PLUGINS_VIEW_ID`) ([#1023](https://github.com/pascalorg/editor/pull/1023))
- **More for hosts.** `loadingSlot` supplies the loading surface, `actionMenuPlacement: 'top'` docks the tool menu under the toolbar, and `selectZoneOrRoom`, `frameViewerNode`, `enterUnitFocus`, `IconRefGlyph` and `IconRefImage` are exported; the plan frames a node when it is the only view and lights what a host's chat points at. See `packages/editor/README.md` ([#995](https://github.com/pascalorg/editor/pull/995), [#1040](https://github.com/pascalorg/editor/pull/1040), [#1043](https://github.com/pascalorg/editor/pull/1043), [#1070](https://github.com/pascalorg/editor/pull/1070))
- **Light chrome.** The editor follows the host's `.dark` class instead of forcing dark, and `InterfaceThemeRow` gives a host's settings a System / Light / Dark row ([#1041](https://github.com/pascalorg/editor/pull/1041))
- **Self-hosting is one container.** `ghcr.io/pascalorg/pascal-editor:<version>` ships the same portable runtime as the CLI, multi-arch, published by the release workflow. The public URL is read per request (`PASCAL_PUBLIC_URL`, replacing the build-time `NEXT_PUBLIC_APP_URL` and `MINT_PASCAL_HOST_ORIGIN`), so one image serves any domain or port. A single admin password (`PASCAL_ADMIN_PASSWORD`) gates every page and API behind `/login`; agents keep the `PASCAL_SCENE_API_TOKEN` bearer; a network host with nothing configured serves a setup page and nothing else; `PASCAL_AUTH=proxy` defers to an authenticating reverse proxy. See `SETUP.md`, "Self-hosting" ([#1032](https://github.com/pascalorg/editor/pull/1032), [#1038](https://github.com/pascalorg/editor/pull/1038))
- **The standalone app is a product, not a shell.** Home at `/` lists projects (the unsaved scratch canvas and `/scenes` are gone; `/scenes` and `/scene/<id>` redirect to `/` and `/project/<id>`), new ones are named `Project #n`, each card renames and deletes; the editor gets a navbar (logo back to home, inline rename, Preview); `/settings` holds the instance info, the agent endpoint and sign-out; thumbnails are captured after edits and stored under the data directory. `/privacy`, `/terms` and `vercel.json` leave the open-source app.
- **Public embeds.** A Share button in the editor navbar turns on a per-project public link: a read-only 3D view at `/embed/<id>` that needs no password and can be iframed, with a copyable link and `<iframe>` snippet. Projects are private until shared, and turning the link off makes it a 404 again. See `SETUP.md`, "Self-hosting".
- **Public embeds.** A Share button in the editor navbar turns on a per-project public link: a read-only 3D view at `/embed/<id>` that needs no password and can be iframed, with a copyable link and `<iframe>` snippet. Projects are private until shared, and turning the link off makes it a 404 again. See `SETUP.md`, "Self-hosting" ([#1032](https://github.com/pascalorg/editor/pull/1032))

### Breaking

- **Three MCP tools retired.** `validate_scene` → `verify_scene`, `find_nodes` → `find_by_type`, `describe_node` → `get_node`. They are no longer listed; a call to one answers `tool_retired` with its `replacement` ([#1081](https://github.com/pascalorg/editor/pull/1081), [#1084](https://github.com/pascalorg/editor/pull/1084))
- **The editor no longer forces dark chrome.** A host that wants it dark puts `dark` on an ancestor and on `<body>` (or `<html>`) for the menus that portal there; the standalone app does ([#1041](https://github.com/pascalorg/editor/pull/1041))
- **`viewMode` / `setViewMode` are deprecated** for views (`viewLayouts`, `visibleScene`, `isViewVisible`). They still work and old preferences migrate ([#1023](https://github.com/pascalorg/editor/pull/1023))
- **`wall.assembly` is now F2 assembly layers** (breaking change to the additive plugin v1 contract; the WS5 shape shipped days earlier in #937). Walls store `{ layers: [{ id, role, thickness, core?, material?, … }], face?, presetId?, cavityInsulation? }` instead of `{ exterior, sheathing, framing, interior, preset }`. Scenes saved with the old shape are converted on load and at the scene API boundary, so `thickness` and every drawing stay the same. Plugins that read `wall.assembly.exterior` / `.framing` / `.preset` directly should read `wallAssemblyToLegacy(wall.assembly)` (the old shape, or `null` for a stack it cannot express), `wallAssemblyFraming(wall)` or `wallAssemblyExteriorFinish(wall)`; `WALL_ASSEMBLY_PRESETS[i].assembly` is F2 too.

### Release process

- The Release workflow checks that every package already exists on npm before it builds anything, so a package never published by hand fails the run at the start instead of mid-publish; `wiki/npm-release.md` has "Adding a package" ([#1049](https://github.com/pascalorg/editor/pull/1049))
- The release commit and tags are pushed with the `RELEASE_DEPLOY_KEY` deploy key, which the `main` ruleset lets through ([#1050](https://github.com/pascalorg/editor/pull/1050))

## 1.0.0 (2026-09-12)

### Features
Expand Down
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ New node kinds and sidebar panels can ship as a plugin instead of editing the bu
- Include screenshots or recordings for visual changes
- If you're unsure about an approach, open an issue or discussion first

## Releases

Maintainers release every `@pascal-app/*` package together from the **Release** workflow; contributors never bump versions in a PR. Two things a PR can change need a maintainer before the next release:

- **A new package.** Add it to `PACKAGES` in `.github/workflows/release.yml`, then a maintainer publishes it to npm once by hand and adds its trusted publisher. Until then the workflow fails at its first step, before anything is published:

```bash
cd packages/<pkg> && bun run build && npm publish --access public --tag latest --ignore-scripts
```

- **The release push.** The release commit and tags go to `main` with the `RELEASE_DEPLOY_KEY` deploy key, the one identity besides admins that the `main` ruleset lets through. Keep it in the ruleset's bypass list when the ruleset changes.

[Releasing to npm](wiki/npm-release.md) has the trusted-publisher settings, key rotation and recovery steps.

## Reporting bugs

Use the [Bug Report](https://github.com/pascalorg/editor/issues/new?template=bug_report.yml) template. Include steps to reproduce — this helps us fix things faster.
Expand Down
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ import { builtinPlugin } from '@pascal-app/nodes'
await loadPlugin(builtinPlugin)
```

See the [`@pascal-app/viewer` quick start](packages/viewer/README.md#usage) for a React example.
See the [`@pascal-app/viewer` quick start](packages/viewer/README.md#usage) for a React example,
and [`@pascal-app/editor`](packages/editor/README.md) to embed the full editor: its loading
surface, tool-menu placement, light or dark chrome, views and selection helpers.


## Repository Architecture
Expand Down Expand Up @@ -544,12 +546,15 @@ turbo build --filter=@pascal-app/core

Releases run from `.github/workflows/release.yml` (`workflow_dispatch`, with
`bump` and `dry-run` inputs). Every package shares one version: the workflow
bumps all seven, rewrites the internal `@pascal-app/*` ranges, builds, publishes
in dependency order (`core` → `viewer` → `editor` → `nodes` → `mcp` →
`ifc-converter` → `cli`), then commits the release and pushes one tag per
package. A dry run validates the builds without touching the registry.
[Releasing to npm](wiki/npm-release.md) covers authentication, recovering a
partial run, publishing by hand and the follow-ups.
bumps all eight, rewrites the internal `@pascal-app/*` ranges, checks that each
one already exists on npm (a package never published fails the run before
anything is built), builds, publishes in dependency order (`core` →
`geometry-script` → `viewer` → `editor` → `nodes` → `mcp` → `ifc-converter` →
`cli`), then commits the release and pushes one tag per package with the release
deploy key, which the `main` ruleset lets through. A dry run validates the builds
without touching the registry. [Releasing to npm](wiki/npm-release.md) covers
authentication, adding a package, the deploy key, recovering a partial run,
publishing by hand and the follow-ups.

---

Expand Down
Loading
Loading