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
15 changes: 8 additions & 7 deletions .github/workflows/publish-unsigned-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ on:
description: New prerelease tag; an existing release is never overwritten
required: true
type: string
default: v0.5.0-beta.2
default: v0.5.0-beta.3
preview_title:
description: Public prerelease title
required: true
type: string
default: Parlyn Engine v0.5.0 Beta 2 (unsigned)
default: Parlyn Engine v0.5.0 Beta 3 (unsigned)

permissions:
contents: write
Expand All @@ -29,8 +29,8 @@ jobs:
env:
CSC_IDENTITY_AUTO_DISCOVERY: false
GH_TOKEN: ${{ github.token }}
PREVIEW_TAG: ${{ inputs.preview_tag || 'v0.5.0-beta.2' }}
PREVIEW_TITLE: ${{ inputs.preview_title || 'Parlyn Engine v0.5.0 Beta 2 (unsigned)' }}
PREVIEW_TAG: ${{ inputs.preview_tag || 'v0.5.0-beta.3' }}
PREVIEW_TITLE: ${{ inputs.preview_title || 'Parlyn Engine v0.5.0 Beta 3 (unsigned)' }}

steps:
- name: Checkout
Expand Down Expand Up @@ -68,10 +68,11 @@ jobs:
shell: pwsh
run: |
@"
# Parlyn Engine v0.5.0 Beta 2
# Parlyn Engine v0.5.0 Beta 3

This unsigned Windows beta contains the completed automated Phase 1
foundation candidate. It adds reliable atomic project persistence,
foundation candidate. It restores the packaged Electron host bridge
required for project creation and saving, and includes atomic persistence,
hardened Node and Undo/Redo invariants, visible editor errors, tighter
desktop trust boundaries and corrected packaged toolbar branding.

Expand All @@ -86,7 +87,7 @@ jobs:
remain pending. This release is not SignPath-signed.

Maintainer acceptance instructions:
https://github.com/WebCrew/Parlyn-Engine/blob/main/docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md
https://github.com/WebCrew/Parlyn-Engine/blob/main/docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md
"@ | Set-Content -LiteralPath release/PREVIEW-NOTES.md -Encoding utf8

- name: Publish GitHub pre-release
Expand Down
6 changes: 4 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased — Foundation Stabilization

- Restored the sandboxed Electron preload bridge so packaged project creation and saving work again.
- Added sandboxed preload contract and real Electron smoke tests that verify every desktop host method before Windows packaging.
- Added a reproducible 64-bit Windows NSIS installer foundation.
- Added branded Windows executable and installer metadata.
- Added a manual Windows build workflow with a strict trusted-signing gate.
Expand Down Expand Up @@ -39,8 +41,8 @@
- Bounded loaded scenes to 10,000 nodes and 256 hierarchy levels.
- Reworked plain-JSON validation to avoid recursive call-stack exhaustion.
- Added automated Node hierarchy and Undo/Redo invariant checks.
- Added a repeatable Beta 2 maintainer acceptance checklist for the Phase 1 completion gate.
- Generalized the non-overwriting unsigned preview workflow for an explicit Beta 2 release.
- Added a repeatable Beta 3 maintainer acceptance checklist for the Phase 1 completion gate.
- Generalized the non-overwriting unsigned preview workflow for explicit beta releases.


## v0.5.0 — Transform Tools & Module Foundation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ See [`docs/WINDOWS-DISTRIBUTION.md`](docs/WINDOWS-DISTRIBUTION.md) for signing,
verification and the maintainer acceptance test.

The current Phase 1 acceptance candidate uses the repeatable
[`v0.5.0-beta.2 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md).
[`v0.5.0-beta.3 maintainer checklist`](docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md).

### Code signing policy

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Parlyn Engine v0.5.0 Beta 2 — Maintainer Acceptance
# Parlyn Engine v0.5.0 Beta 3 — Maintainer Acceptance

This is the repeatable Phase 1 acceptance pass. Perform it on the published
`v0.5.0-beta.2` artifact, not on a development checkout or the older Beta 1.
`v0.5.0-beta.3` artifact, not on a development checkout or an older beta.

Smart App Control and normal Windows security settings remain enabled. This
preview is intentionally unsigned while the SignPath Foundation application is
Expand All @@ -19,7 +19,7 @@ Record these values in Issue #23 after the pass:

## 1. Download and integrity

1. Download the installer and `SHA256SUMS.txt` from the Beta 2 prerelease.
1. Download the installer and `SHA256SUMS.txt` from the Beta 3 prerelease.
2. In PowerShell, run:

```powershell
Expand All @@ -37,7 +37,7 @@ Record these values in Issue #23 after the pass:

## 3. Project and persistence

1. Create a project named `Parlyn Beta 2 Test` in a new empty parent folder.
1. Create a project named `Parlyn Beta 3 Test` in a new empty parent folder.
2. Add one 2.5D Sprite and one 3D Mesh.
3. Rename both nodes and change their positions.
4. Save the scene, close Parlyn, reopen the project and confirm names and positions survived.
Expand Down Expand Up @@ -68,8 +68,8 @@ Record these values in Issue #23 after the pass:

## 7. Uninstall preservation

1. Close Parlyn and uninstall Beta 2 normally.
2. Confirm the separately created `Parlyn Beta 2 Test` project still exists with its files intact.
1. Close Parlyn and uninstall Beta 3 normally.
2. Confirm the separately created `Parlyn Beta 3 Test` project still exists with its files intact.

Phase 1 passes only when every required section succeeds or a discovered defect
is fixed and the affected section is repeated against a new artifact.
2 changes: 1 addition & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Already present:

Completion gate:

- complete the repeatable `v0.5.0-beta.2` maintainer test pass and record the result in Issue #23.
- complete the repeatable `v0.5.0-beta.3` maintainer test pass and record the result in Issue #23.

## Phase 2 — Editor foundation

Expand Down
2 changes: 1 addition & 1 deletion docs/TESTING-v0.5.0.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Parlyn Engine v0.5.0 Test Guide

For the Phase 1 release-candidate acceptance pass, use the shorter artifact-specific
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md)
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md)
and record the result in Issue #23. The guide below remains the detailed feature reference.

This test focuses on the two new foundations introduced in v0.5.0: viewport transform gizmos and the module lifecycle.
Expand Down
4 changes: 2 additions & 2 deletions docs/WINDOWS-DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,6 @@ the maintainer's Windows machine. Public trusted-signing acceptance remains
pending separately from the resolved development-binary blocker.

The full Phase 1 functional acceptance candidate is the unsigned
`v0.5.0-beta.2` prerelease. Its repeatable checklist is documented in
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md),
`v0.5.0-beta.3` prerelease. Its repeatable checklist is documented in
[`MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md`](MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md),
and the result is recorded in Issue #23 before Phase 1 can be marked complete.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
"scripts": {
"build:renderer": "esbuild src/renderer/app.mjs --bundle --outfile=src/renderer/dist/app.js --format=iife --platform=browser --target=chrome120 --sourcemap",
"build:renderer:release": "esbuild src/renderer/app.mjs --bundle --outfile=src/renderer/dist/app.js --format=iife --platform=browser --target=chrome120 --minify",
"build:windows": "npm run check && npm run build:renderer:release && electron-builder --win nsis",
"build:windows:dir": "npm run check && npm run build:renderer:release && electron-builder --win dir",
"build:windows": "npm run check && npm run test:desktop-smoke && npm run build:renderer:release && electron-builder --win nsis",
"build:windows:dir": "npm run check && npm run test:desktop-smoke && npm run build:renderer:release && electron-builder --win dir",
"dev": "npm run build:renderer && electron .",
"start": "npm run dev",
"check": "node scripts/check.cjs"
"check": "node scripts/check.cjs",
"test:desktop-smoke": "electron scripts/check-electron-preload.cjs"
},
"dependencies": {
"three": "0.185.1"
Expand Down
18 changes: 18 additions & 0 deletions scripts/check-desktop-boundaries.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const assert = require('assert/strict');
const fs = require('fs/promises');
const os = require('os');
const path = require('path');
const vm = require('vm');
const { assertTrustedIpcEvent, assertIpcPayload } = require('../src/main/ipcSecurity');
const { resolveExistingProjectPath, resolveWritableProjectPath } = require('../src/main/projectPaths');

Expand Down Expand Up @@ -42,6 +43,7 @@ const { resolveExistingProjectPath, resolveWritableProjectPath } = require('../s
const html = await fs.readFile(path.join(repositoryRoot, 'src/renderer/index.html'), 'utf8');
const renderer = await fs.readFile(path.join(repositoryRoot, 'src/renderer/app.mjs'), 'utf8');
const main = await fs.readFile(path.join(repositoryRoot, 'src/main/main.js'), 'utf8');
const preload = await fs.readFile(path.join(repositoryRoot, 'src/main/preload.js'), 'utf8');
const pkg = JSON.parse(await fs.readFile(path.join(repositoryRoot, 'package.json'), 'utf8'));
assert.match(html, /class="brand" aria-label="Parlyn Engine"/);
assert.match(html, /alt="" aria-hidden="true" class="brand-logo"/);
Expand All @@ -53,8 +55,24 @@ const { resolveExistingProjectPath, resolveWritableProjectPath } = require('../s
assert.ok(pkg.build.files.includes('assets/branding/**/*'), 'Packaged editor must include its branding assets.');
assert.match(main, /setWindowOpenHandler/);
assert.match(main, /will-navigate/);
assert.match(main, /secureHandle\('parlyn:app:get-info'/);
assert.match(main, /secureHandle\('parlyn:project:open'/);

let exposedHost = null;
vm.runInNewContext(preload, {
require(moduleName) {
assert.equal(moduleName, 'electron', 'Sandboxed preload may only load Electron APIs.');
return {
contextBridge:{ exposeInMainWorld(name, value) { assert.equal(name, 'parlynHost'); exposedHost = value; } },
ipcRenderer:{ invoke:async () => ({}) }
};
}
}, { filename:'src/main/preload.js' });
assert.ok(exposedHost, 'Preload must expose window.parlynHost.');
for (const method of ['getAppInfo','createProject','openProject','saveProjectScene','saveProjectWorld','saveSceneAs','openScene','importAssets']) {
assert.equal(typeof exposedHost[method], 'function', `Preload host is missing ${method}().`);
}

console.log('Desktop trust boundary and editor error contract check passed.');
})().catch((error) => {
console.error(error);
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-electron-preload.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const assert = require('assert/strict');
const path = require('path');
const { app, BrowserWindow, ipcMain } = require('electron');

const methods = [
'getAppInfo',
'createProject',
'openProject',
'saveProjectScene',
'saveProjectWorld',
'saveSceneAs',
'openScene',
'importAssets'
];

app.commandLine.appendSwitch('disable-gpu');

app.whenReady().then(async () => {
ipcMain.handle('parlyn:app:get-info', () => ({ version:app.getVersion(), platform:process.platform }));
const win = new BrowserWindow({
show:false,
webPreferences:{
preload:path.join(__dirname, '..', 'src', 'main', 'preload.js'),
contextIsolation:true,
nodeIntegration:false,
sandbox:true
}
});
await win.loadURL('data:text/html,<title>Parlyn preload smoke test</title>');
const result = await win.webContents.executeJavaScript(`(async () => ({
missing:${JSON.stringify(methods)}.filter((name) => typeof window.parlynHost?.[name] !== 'function'),
appInfo:await window.parlynHost?.getAppInfo?.()
}))()`);
assert.deepEqual(result.missing, [], `Electron preload is missing: ${result.missing.join(', ')}`);
assert.equal(result.appInfo?.version, app.getVersion());
assert.equal(result.appInfo?.platform, process.platform);
console.log('Electron sandboxed preload smoke test passed.');
win.destroy();
app.quit();
}).catch((error) => {
console.error(error);
app.exit(1);
});
3 changes: 2 additions & 1 deletion scripts/check.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ const required = [
'docs/SMART-SYSTEMS.md',
'docs/AUTHENTICATION.md',
'docs/WINDOWS-DISTRIBUTION.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.2.md',
'docs/MAINTAINER-ACCEPTANCE-v0.5.0-beta.3.md',
'build/icon.ico',
'.github/workflows/windows-installer.yml',
'scripts/check-smart-systems.mjs',
'scripts/check-core-persistence.mjs',
'scripts/check-core-invariants.mjs',
'scripts/check-desktop-boundaries.cjs',
'scripts/check-electron-preload.cjs',
'scripts/check-windows-distribution.cjs',
'scripts/verify-windows-artifacts.ps1'
];
Expand Down
5 changes: 5 additions & 0 deletions src/main/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ function secureHandle(channel, handler, { payload = false } = {}) {
});
}

secureHandle('parlyn:app:get-info', async () => ({
version:app.getVersion(),
platform:process.platform
}));

secureHandle('parlyn:scene:save-as', async (payload) => {
const defaultName = `${slug(payload?.name || 'scene','scene')}.parlyn-scene.json`;
const result = await dialog.showSaveDialog({ title:'Save Parlyn Scene', defaultPath:defaultName, filters:[{ name:'Parlyn Scene', extensions:['json'] }] });
Expand Down
3 changes: 1 addition & 2 deletions src/main/preload.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('parlynHost', {
version:require('../../package.json').version,
platform:process.platform,
getAppInfo:()=>ipcRenderer.invoke('parlyn:app:get-info'),
saveSceneAs:(payload)=>ipcRenderer.invoke('parlyn:scene:save-as',payload),
openScene:()=>ipcRenderer.invoke('parlyn:scene:open'),
createProject:(payload)=>ipcRenderer.invoke('parlyn:project:create',payload),
Expand Down
9 changes: 8 additions & 1 deletion src/renderer/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,14 @@ async function bootstrap() {
selectById(scene.root.children[1].id);
updateHistoryButtons();
setDirty(false);
status.textContent = `Ready \xB7 Parlyn ${host?.version ?? "0.5.0"} \xB7 THREE renderer backend`;
let appVersion = "0.5.0";
try {
const appInfo = await host.getAppInfo();
if (appInfo?.version) appVersion = appInfo.version;
} catch (error) {
console.warn("Could not read Parlyn application information:", error);
}
status.textContent = `Ready \xB7 Parlyn ${appVersion} \xB7 THREE renderer backend`;
}
bootstrap().catch((error) => {
console.error("Parlyn failed to initialize:", error);
Expand Down
Loading