fix: support stable performance baselines

Issue: #380
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-08-30 00:45:31 +03:00
parent d68d912f83
commit 285688de47
7 changed files with 132 additions and 7 deletions
+4 -1
View File
@@ -204,7 +204,10 @@ try {
});
const cacheSnapshot = (card) => ({
...(card.localName === 'houseplan-space-card' ? {
glowClip: card._glowRuntimeState.clipCache?.size ?? 0,
// #380: the previous stable predates the shared static-card Glow
// runtime. Its default profile still has a valid empty cache, and
// the opt-in Glow profile is explicitly bootstrap-compared below.
glowClip: card._glowRuntimeState?.clipCache?.size ?? 0,
} : {
cleanFloor: card._cleanFloorCache?.size ?? 0,
glowClip: card._glowClipCache?.size ?? 0,
+4 -1
View File
@@ -346,7 +346,10 @@ try {
target: null,
};
card._rszEdgeDown(quietEvent, room.id, 1);
const plan = card._resize?.plan;
// #380: the previous stable owns the active session directly; current
// candidates own it through ResizeController. The explicit contract
// above requires one of these shapes before measurements begin.
const plan = card._resize?.plan || card._rszDrag?.plan;
if (!plan) throw new Error('large-house resize plan was not created');
const target = [
plan.a[0] + plan.n[0] * card._gridPitch,
+15
View File
@@ -0,0 +1,15 @@
/**
* Prepare editor internals for demo harnesses across comparison baselines.
*
* Since #337 current builds keep the editor in a lazy runtime and expose an
* explicit preload method. Older stable builds are monolithic: absence of the
* method means there is nothing to preload, not that the baseline is broken.
* A present method remains fail-closed through its result or exception.
*/
export async function ensureHarnessEditorRuntime(
card = globalThis.window?.__card,
) {
const ensure = card?._ensureEditorRuntime;
if (typeof ensure !== 'function') return true;
return Boolean(await ensure.call(card));
}
+17 -3
View File
@@ -34,7 +34,6 @@ export const LARGE_HOUSE_CARD_CONTRACT = Object.freeze({
'_loadOk',
'_model',
'_path',
'_resize',
'_serverCfg',
'_settingsDialog',
'_tool',
@@ -46,6 +45,13 @@ export const LARGE_HOUSE_CARD_CONTRACT = Object.freeze({
'_onLabsSnapshot', '_planSnapGeometryCache', '_roomDialog', '_setProjection',
'_wallFaceBatch', '_wallFaceGraphCache',
]),
// #380: v1.68.1 owns the same resize session directly on the card; newer
// bundles moved it into ResizeController. A comparison target must expose
// one of the two explicit shapes; the current member retains its object
// type check and is also verified against current production source.
fieldAlternatives: Object.freeze([
Object.freeze({ current: '_resize', legacy: '_rszDrag' }),
]),
fieldTypes: Object.freeze({
_booting: 'boolean',
_cleanFloorCache: 'map',
@@ -115,11 +121,19 @@ export function assertCardContract(card, contract) {
.map((name) => `${name}()`);
const missingFields = contract.fields
.filter((name) => !(name in card) || card[name] === undefined);
const invalidFields = [...contract.fields, ...(contract.optionalFields || [])]
const missingAlternatives = (contract.fieldAlternatives || [])
.filter((choice) => !Object.values(choice)
.some((name) => name in card && card[name] !== undefined))
.map((choice) => Object.values(choice).join('|'));
const alternativeFields = (contract.fieldAlternatives || [])
.flatMap((choice) => Object.values(choice));
const invalidFields = [
...contract.fields, ...(contract.optionalFields || []), ...alternativeFields,
]
.filter((name) => name in card && contract.fieldTypes?.[name]
&& !matches(card[name], contract.fieldTypes[name]))
.map((name) => `${name}:${contract.fieldTypes[name]}`);
const missing = [...missingMethods, ...missingFields];
const missing = [...missingMethods, ...missingFields, ...missingAlternatives];
if (missing.length || invalidFields.length) {
const details = [
missing.length ? `missing private API: ${missing.join(', ')}` : '',
+5 -1
View File
@@ -2,6 +2,7 @@
// via request interception (no HTTP server needed). Usage: const {page,browser}=await launch();
import { chromium } from 'playwright';
import { assertFreshDemoBundleUnlessAllowed } from './bundle-freshness.mjs';
import { ensureHarnessEditorRuntime } from './editor-runtime-compat.mjs';
import { readFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
@@ -88,8 +89,11 @@ async function launchInternal(
// Existing product smokes exercise editor internals directly. They preload
// the new #337 runtime without changing mode; the dedicated network smoke
// uses launchColdView() and proves the real cold-View boundary separately.
// Comparative performance runs also launch the previous stable through this
// file. A pre-#337 monolithic card has no preload method and is already
// ready; a current card still has to return truthy (#380).
if (preloadEditorRuntime) {
const ready = await page.evaluate(() => window.__card._ensureEditorRuntime());
const ready = await page.evaluate(ensureHarnessEditorRuntime);
if (!ready) throw new Error('editor runtime did not preload for browser smoke');
}
return { page, browser };
+34
View File
@@ -0,0 +1,34 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { ensureHarnessEditorRuntime } from '../demo/editor-runtime-compat.mjs';
test('a pre-lazy-editor baseline is already ready for the harness (#380)', async () => {
assert.equal(await ensureHarnessEditorRuntime({}), true);
assert.equal(await ensureHarnessEditorRuntime(null), true);
});
test('a lazy editor runtime is called with its card and must succeed (#380)', async () => {
let owner = null;
const card = {
async _ensureEditorRuntime() {
owner = this;
return true;
},
};
assert.equal(await ensureHarnessEditorRuntime(card), true);
assert.equal(owner, card);
assert.equal(await ensureHarnessEditorRuntime({
async _ensureEditorRuntime() { return false; },
}), false);
});
test('a broken lazy preload remains a hard harness failure (#380)', async () => {
await assert.rejects(
ensureHarnessEditorRuntime({
async _ensureEditorRuntime() { throw new Error('chunk failed'); },
}),
/chunk failed/,
);
});
+53 -1
View File
@@ -21,6 +21,12 @@ const directCardMembers = (relativePath) => {
const declaredMembers = (contract) => new Set([
...contract.methods, ...contract.fields, ...(contract.optionalFields || []),
...(contract.fieldAlternatives || []).flatMap((choice) => Object.values(choice)),
]);
const currentProductionMembers = (contract) => new Set([
...contract.methods, ...contract.fields, ...(contract.optionalFields || []),
...(contract.fieldAlternatives || []).map((choice) => choice.current),
]);
test('large-house benchmark declares every private card member it consumes', () => {
@@ -42,10 +48,18 @@ test('Glow benchmark declares every private card member it consumes', () => {
);
});
test('static-card cache diagnostics accept a pre-Glow stable baseline (#380)', () => {
const source = readFileSync(
new URL('../demo/benchmark_glow.mjs', import.meta.url), 'utf8',
);
assert.match(source, /card\._glowRuntimeState\?\.clipCache\?\.size \?\? 0/);
assert.doesNotMatch(source, /card\._glowRuntimeState\.clipCache/);
});
test('performance contracts reference real production members', () => {
const source = readHouseplanProductionSource();
for (const contract of [LARGE_HOUSE_CARD_CONTRACT, GLOW_CARD_CONTRACT]) {
for (const name of declaredMembers(contract)) {
for (const name of currentProductionMembers(contract)) {
assert.match(source, new RegExp(`\\b(?:private\\s+(?:declare\\s+|get\\s+)?|get\\s+)${name}\\b`),
`${contract.label} declares missing production member ${name}`);
}
@@ -57,6 +71,44 @@ test('performance contracts reference real production members', () => {
}
});
test('large-house contract accepts only an explicit current or stable resize owner', () => {
const methods = Object.fromEntries(
LARGE_HOUSE_CARD_CONTRACT.methods.map((name) => [name, () => undefined]),
);
const fields = Object.fromEntries(
LARGE_HOUSE_CARD_CONTRACT.fields.map((name) => [name, null]),
);
Object.assign(fields, {
_booting: false,
_cleanFloorCache: new Map(),
_devices: [],
_glowClipCache: new Map(),
_gridPitch: 1,
_loadOk: true,
_model: [],
_path: [],
_serverCfg: {},
_tool: 'view',
});
assert.doesNotThrow(() => assertCardContract(
{ ...methods, ...fields, _resize: {} }, LARGE_HOUSE_CARD_CONTRACT,
));
assert.doesNotThrow(() => assertCardContract(
{ ...methods, ...fields, _rszDrag: null }, LARGE_HOUSE_CARD_CONTRACT,
));
assert.throws(
() => assertCardContract(
{ ...methods, ...fields, _resize: false }, LARGE_HOUSE_CARD_CONTRACT,
),
/invalid private API types: _resize:object/,
);
assert.throws(
() => assertCardContract({ ...methods, ...fields }, LARGE_HOUSE_CARD_CONTRACT),
/missing private API: _resize\|_rszDrag/,
);
});
test('contract accepts recent optional fields only when their runtime type is valid', () => {
const base = {
_cleanFloorCache: new Map(), _glowClipCache: new Map(),