Files
feuerwerk/src/main.ts
T
adminandClaude Opus 5 940a78e8ee The editor ships its own font, Source Sans 3 and Code Pro (#202)
Theme named Inter and JetBrains Mono, but neither was ever shipped,
so every machine drew the editor in whatever it had installed. The
e2e runs measured text in a different face than the user saw.

- Five Latin woff2 faces live in src/ui/fonts/ next to their OFL
  licences: Source Sans 3 400/500/600/700 and Source Code Pro 400.
- fonts.ts loads them through FontFace, and main.ts waits for them
  before the first widget exists, so Pixi measures the real face.
- A face that fails to load is reported in the console and the
  system fallback in Theme takes over.
- Theme is retuned by hand: 12 px body, 22 px rows, smaller gaps
  and padding, radius 8, and weight 500 for bold and headings.
- The scene background default follows Theme.colors.graphBackground.
  This makes core import from ui for the first time.

Findings:
- The 500 file is in the folder but fonts.ts does not register it
  yet, so weight 500 currently falls back to the 400 face.

Tests:
- ./check.sh: 141 files, 2080 tests, exit 0.
- e2e not run. Checks that measure widths or row positions are
  expected to need updating for the new metrics.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:55:22 +02:00

71 lines
2.6 KiB
TypeScript

import { Application, Container } from 'pixi.js';
// Static imports on purpose: Pixi registers render pipes (graphics, text, sprite) as a
// module side effect, and a renderer only knows the pipes that exist when it is created.
// Lazy chunks loaded after `app.init()` would therefore fail with "unknown render pipe".
import { captureConsole } from './editor/console/log';
import { startEditor } from './editor/App';
import { mountPlayground } from './playground';
import { initRenderLoop, initUI, loadEditorFonts, Theme, VStack } from './ui';
// Before anything else: a renderer warns while it is being created, and those warnings are
// exactly the ones worth having in the panel.
captureConsole();
// `desynchronized` bypasses the compositor queue (headless click-to-paint 72 → 16 ms) but may
// tear; `?vsync` opts out. Pixi has no option for it, hence our own context.
function lowLatencyContext(): { canvas?: HTMLCanvasElement; context?: WebGL2RenderingContext } {
const canvas = document.createElement('canvas');
const context = canvas.getContext('webgl2', {
alpha: false,
antialias: true,
premultipliedAlpha: true,
stencil: true,
desynchronized: true,
});
return context ? { canvas, context } : {};
}
async function main(): Promise<void> {
const params = new URLSearchParams(location.search);
const fonts = loadEditorFonts();
const app = new Application();
await app.init({
resizeTo: window,
background: Theme.colors.background,
antialias: true,
resolution: Math.min(2, window.devicePixelRatio || 1),
autoDensity: true,
preference: 'webgl',
...(params.has('vsync') ? {} : lowLatencyContext()),
});
document.body.appendChild(app.canvas);
await fonts;
const root = new VStack();
const overlay = new Container();
app.stage.addChild(root, overlay);
app.stage.eventMode = 'static';
app.stage.hitArea = app.screen;
initUI(app, root, overlay);
// From here on the picture is drawn when something changes, not sixty times a second.
initRenderLoop(app);
if (params.has('playground')) mountPlayground(app, root);
else await startEditor(app, root);
app.renderer.on('resize', () => {
app.stage.hitArea = app.screen;
root.resize(app.screen.width, app.screen.height);
});
}
window.addEventListener('error', (event) => {
const error: unknown = event.error;
console.error('[uncaught]', error instanceof Error ? error.stack : event.message);
});
main().catch((error) => {
console.error(error);
document.body.innerHTML = `<pre style="color:#e0553f;padding:16px;font:13px monospace">${String(error)}</pre>`;
});