You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Generate an app name, tagline, and a longer context paragraph for icon generation, based on this user request: "Create a storyboarding app for designing youtube videos"
Respond with ONLY valid JSON in this exact format:
CRITICAL: If the user provides or suggests an app name in their request (e.g., "called X", "named X", "app name is X", "I want to call it X"), you MUST ALWAYS use that exact name. This takes precedence over all other requirements below.
If no name is provided by the user, then follow these rules:
Descriptive and memorable
NO generic suffixes: "App", "Game", "Application", "Tool", "Software"
NO subjective or qualitative adjectives (e.g., simple, minimal, easy, modern, smart, fast, best)
Focus on WHAT it is or DOES, not HOW it's built or looks
DESCRIPTION (tagline) Requirements:
One clear sentence explaining the app's purpose and core functionality
NO implementation details (UI components, technical architecture, frameworks, design patterns)
Maximum 40 characters in length
User-friendly language (avoid jargon)
Focus on user benefits and what users can accomplish
Start with an action verb when possible
ICON DESCRIPTION (context for icon generation) Requirements:
This is a longer paragraph used internally to give an AI image generator enough context to create a great app icon. It is never shown to users.
150-300 characters in length
Explain what the app actually does in concrete, real-world terms. If the app's domain is niche or involves specific products/platforms/concepts that the image generator wouldn't know about, explain them clearly. For example, if the user says "Glaze logger", explain that Glaze is a macOS desktop app builder and this app monitors its logs.
Suggest specific physical objects, themes, or visual metaphors that could translate into a strong icon subject (e.g., "Think of a compass, a suitcase, or a pinned map marker")
If the user's request suggests an aesthetic direction (e.g., "sleek", "minimal", "retro", "playful", "techy"), include it at the end as "Aesthetic: ..." β but ONLY if the user's words genuinely suggest a style. If there's no style signal, omit the aesthetic line entirely.
Avoid implementation details (React, MongoDB, API, etc.) β focus on the real-world concept
@glaze/core package - Framework code (auto-updated when Glaze runs)
To customize shared components: Create wrappers in renderer/components/
Security: Renderer code must use window.glazeAPI, never import ipcRenderer directly.
Forbidden imports β NEVER use these, and NEVER add eslint-disable to bypass the lint rule:
backendNativeBridge β This is a framework internal. It is not a public API. Importing it (from @glaze/core/backend or @glaze/core/backend/internal) will break your app because it creates a duplicate singleton that is not wired to the IPC server. Use the public APIs instead: dialog, shell, clipboard, systemPreferences, globalShortcut, nativeTheme, screen, powerMonitor, powerSaveBlocker, safeStorage, Notification, Menu, Tray.
@glaze/core/backend/internal β This entire entrypoint is reserved for the Glaze host app. The build will fail if you import from it.
GlazeIPCServer, GlazeLifecycle, registerNativeApiHandlers, wireProtocolHandlers β These are handled automatically by the runtime. Do not import or call them.
If a feature has no public API in @glaze/core/backend, it means the API is not yet implemented in the framework. Tell the user it is not available yet. Do not attempt workarounds via backendNativeBridge. </critical_warnings>
<decision_trees>
Decision Trees
Where Should This Code Go?
Does it need file system access, native OS features, or system APIs?
ββ Yes β Backend (main/handlers/ + main/services/)
ββ No β Does it need to persist data?
ββ Yes β What kind of data?
β ββ UI state (panel sizes, tabs, filters) β localStorage (frontend)
β ββ App data (settings, user content) β JSON files in Application Support (backend)
ββ No β Frontend only (renderer/)
β οΈ Data storage location: App data must go in ~/Library/Application Support/<BUNDLE_ID>/ via app.getPath("userData"). NEVER store in the repository or use process.cwd().
Which IPC API Should I Use?
Is it a native macOS feature (dialog, clipboard, shell, notifications)?
ββ Yes β Check the SDK symbol's defaultPreload metadata and renderer/preload.ts
β ββ exposed β Use window.glazeAPI in frontend
β ββ partial β Use only exposed child symbols, or wire the missing method first
β ββ requires-wiring β Add a minimal preload wrapper or backend IPC handler first
ββ No β Is it custom business logic?
ββ Yes β Create backend handler with ipcMain.handle()
ββ No β Keep in frontend (React state, UI logic)
</decision_trees>
Overview
Desktop app with a Node.js backend and React frontend communicating via IPC.
Use BrowserWindow for creating and managing windows:
Invoke glaze-browser-window-recipes before writing any new BrowserWindow(...). Movable windows need a drag affordance: Toolbar/top .drag-region for app pages; titleBarStyle: "default" or a wrapper for external pages.
For frosted/glass HUDs, popovers, menu-bar panels, palettes, or translucent windows, use native BrowserWindow vibrancy with frame: true. Hide traffic lights with setWindowButtonVisibility(false) instead of switching to frame: false. Never use CSS/WebKit blur (backdrop-filter, -webkit-backdrop-filter, Tailwind backdrop-blur-*) as the window background.
Use setVibrancy() for macOS-native NSVisualEffectView materials like "sidebar" or "hud". Vibrancy is only the material; the native frame supplies the rounded panel shape. Do not combine frame: false with vibrancy for visible panel-style windows.
The templateβs build layout introduces a couple of easy-to-miss path quirks. Follow this checklist whenever you add another window:
Choose the surface strategy. Normal windows and panel-style HUDs/popovers keep frame: true; use native vibrancy for frosted materials and hide traffic lights with setWindowButtonVisibility(false) when needed. Use frame: false only for true transparent/custom-shaped overlays where the renderer owns the whole visible shape.
Choose the drag strategy. App page: Toolbar or top .drag-region. External page: titleBarStyle: "default" or app-owned wrapper.
HTML entry files live at the repo root. Place settings-window.html, about-window.html, etc. next to main-window.html. These files compile directly into build/, so keeping them at the root avoids path juggling.
Renderer entry points go in renderer/<window>/. Create a dedicated folder (e.g., renderer/settings/index.tsx) and always import the local stylesheet:
import"../styles.css";
SDK component styles are injected at runtime by the native shell. For Tailwind token generation, keep @import "@glaze/core/components.tailwind.css" in renderer/styles.css and do not import @glaze/core/components.css directly.
Resolve HTML paths with main/windows/window-paths.ts. Backend modules transpile into build/main/**, so path.join(__dirname, "..", "..", ...) differs per folder depth. Use the helperβs getWindowUrl('settings-window.html') (for a ready-to-load URL) or resolveWindowHtml('settings-window.html') (for just the path) instead of hand-crafted .. sequences.
Vite must know about each *-window.html. The template now auto-discovers them, but if you customize the build make sure every HTML file is listed in rollupOptions.input.
Register a backend helper and optional IPC. Add your own handler (e.g., ipcMain.handle('window:openSettings')) that calls openSettingsWindow() and pair it with a renderer entry under renderer/settings/. This keeps creation/focus logic in one place.
Attach preload only to app-owned pages. Use webPreferences: { preload: getPreloadPath() } for renderer entries that should call window.glazeAPI. Do not expose that preload to arbitrary external websites.
Pair the handler with a button (or menu item) that calls ipcRenderer.invoke('window:openSettings'), and create a matching settings-window.html + renderer/settings/index.tsx.
For renderer-initiated closes, call win.close() from the backend or route through IPC / BrowserWindow.close(...). Do not rely on DOM window.close() for BrowserWindow content; WKWebView keeps browser-style restrictions there.
Menu-bar or background apps that should stay out of Dock and Cmd+Tab by default should declare accessory launch behavior in package.json:
Use this for menu-bar, HUD, overlay, background monitor, or global-shortcut apps. Do not use it for normal primary-window apps. For menu-bar apps whose main UI is a panel, show a native tray menu for simple commands or anchor a compact custom window under the menu-bar icon; reserve persistent floating HUDs for apps that explicitly ask for an overlay. If an accessory app intentionally needs a temporary Dock tile for Settings, toggle the Dock from the backend, not the renderer:
That keeps dock activation policy changes and native window close handling on the same side of the boundary, so Cmd+W continues to close the settings window normally.
Following this checklist ensures every new window works in both dev-server and packaged builds without manual tweaks.
Key Files
main/index.ts - Entry point with app.whenReady() (modify to customize windows, menus, lifecycle)
child_process maxBuffer β always set maxBuffer: 10 * 1024 * 1024 when CLI output may exceed 1 MB (image processing, base64 encoding, large JSON). Always set timeout.
Polling cleanup β every setInterval must have a corresponding clearInterval in app.on("before-quit"). Prefer event-driven approaches when available.
IPC payload separation β never include base64-encoded images, file contents, or binary data in polling or broadcast responses. Send lightweight identifiers; let the frontend fetch heavy data on demand via a separate IPC channel.
macOS app icons β when extracting app icons, use NSWorkspace.iconForFile() via JXA (osascript -l JavaScript), not CFBundleIconFile alone (fails for asset catalog apps). More generally, prefer JXA for Cocoa API access over shell tools.
Bounded caches β all in-memory caches must have a max entry count and TTL. Clean up caches on before-quit.
See glaze-backend-performance skill for detailed patterns and code examples.
Global Shortcuts (System-Wide Hotkeys)
Register keyboard shortcuts that work even when your app isn't focused:
// main/index.ts or any backend fileimport{globalShortcut}from"@glaze/core/backend";// Register a global shortcutconstsuccess=awaitglobalShortcut.register("CommandOrControl+Shift+P",()=>{console.log("Shortcut triggered!");mainWindow.show();// Example: show app window});if(!success){console.log("Shortcut registration failed - may be in use by another app");}// Check if registeredconstisReg=globalShortcut.isRegistered("CommandOrControl+Shift+P");// Unregister when doneglobalShortcut.unregister("CommandOrControl+Shift+P");// Or unregister all shortcuts (e.g., on app quit)app.on("will-quit",()=>{globalShortcut.unregisterAll();});
Notification is a public @glaze/core/backend API. Do not use backendNativeBridge for notifications.
System Tray (Menu Bar)
Add your app to the macOS menu bar with click events and context menus:
Use a native context menu for simple command lists. If the menu-bar app has a richer primary UI, use the tray click bounds or tray.getBounds() to position a compact custom window under the menu-bar icon; do not use a persistent top-right floating panel unless the user asked for a HUD or overlay.
// main/index.ts or any backend fileimport{Tray,Menu}from"@glaze/core/backend";// Create tray with SF Symbol icon (or file path)consttray=newTray("star.fill");// Or: new Tray("/path/to/icon.png")tray.setToolTip("My App");// Hover texttray.setTitle("Status");// Text shown next to icon// Set context menu (appears on click)tray.setContextMenu(Menu.buildFromTemplate([// Icons use SF Symbol names (macOS) or file paths{label: "Show Window",icon: "macwindow",click: ()=>mainWindow.show()},{label: "New Document",icon: "doc.badge.plus",accelerator: "Command+N"},{type: "separator"},// Sublabels show secondary text below the label (macOS 14.4+){label: "Sync Status",icon: "arrow.triangle.2.circlepath",sublabel: "Last synced 2 min ago",},{type: "separator"},// Checkbox items{label: "Enable Notifications",type: "checkbox",checked: true,icon: "bell.fill",},{type: "separator"},// Submenus{label: "Recent",icon: "clock",submenu: [{label: "Project A",icon: "folder.fill"},{label: "Project B",icon: "folder.fill"},],},{type: "separator"},// Disabled items{label: "Upgrade to Pro",icon: "star.fill",enabled: false},{type: "separator"},{label: "Preferences...",icon: "gearshape",accelerator: "Command+,"},{label: "Quit",role: "quit",icon: "power"},]),);// Handle click eventstray.on("click",(event,bounds)=>{console.log("Tray clicked at",bounds);mainWindow.show();});tray.on("right-click",(event,bounds)=>{console.log("Right-clicked at",bounds);});// Get tray position (useful for positioning popover windows)constbounds=awaittray.getBounds();// { x, y, width, height }// Change icon dynamicallytray.setImage("bell.fill");// SF Symboltray.setImage("/path/to/new-icon.png");// File path// Clean up when app quitsapp.on("will-quit",()=>{tray.destroy();});
Available Events:click, right-click, double-click, mouse-enter, mouse-leave, mouse-move, mouse-down, mouse-up
Tray Icon Options:
SF Symbols: "star.fill", "bell.fill", "gear", etc. (macOS)
File paths: PNG files (16x16 or 18x18 for retina recommended)
Template images: Icons automatically adapt to light/dark menu bar
Menu Item Options: | Property | Type | Description | |----------|------|-------------| | label | string | Menu item text | | icon | string | SF Symbol name or file path | | sublabel | string | Secondary text below label (macOS 14.4+) | | accelerator | string | Keyboard shortcut (e.g., "Command+N") | | enabled | boolean | Whether item is clickable (default: true) | | type | string | "normal", "separator", "checkbox", "radio" | | checked | boolean | For checkbox/radio items | | submenu | array | Nested menu items | | role | string | Predefined action ("quit", "copy", etc.) | | click | function | Click handler |
π¨ FRONTEND (renderer/)
Purpose: React application running in native macOS WebView.
renderer/main/home-view.tsx - Main UI (modify this often)
ToolbarTitle is optional: use it only when users need context (active tab/file/section). For simple single-view apps, omit it. Avoid app-name titles unless explicitly requested.
Key insight: After building, node_modules/ is NOT used at runtime. Everything is bundled into the single index.js file.
CRITICAL: Never modify .glaze/ directly. The .glaze/ directory is the build output and must be fully produced by the build process. Never add symlinks, copy files, create node_modules/, or apply any manual fixes to .glaze/ β if something is missing at runtime, fix the build configuration (via glaze.config.ts or @glaze/core build module) so the output is self-contained.
What Gets Bundled
esbuild bundles:
All your main/ TypeScript code
All npm packages imported by your code
Transitive dependencies (dependencies of dependencies)
esbuild does NOT bundle:
Node.js built-ins (fs, path, crypto, etc.) - available at runtime
Native .node modules - require special handling (see below)
Files loaded via fs.readFile() at runtime - not static imports
Packages That Can't Be Bundled
Most npm packages bundle fine into a single ESM file. However, some packages need special handling because they include files that esbuild can't inline into the bundle.
IMPORTANT β Prefer bundleable alternatives first. Before using either plugin below, check if a lighter, pure-JS alternative exists that bundles cleanly with no extra setup:
Instead of
Consider
Why
better-sqlite3 (native)
node:sqlite (built-in, no install needed)
Glaze ships Node.js 24+, which includes a built-in SQLite module
jsdom (heavy, needs externalization)
node-html-parser (pure JS, bundles fine)
If you only need HTML parsing/querying, not full DOM emulation
sharp (native)
No pure-JS equivalent β use externalizePackage (see below)
jimp is far slower and lacks many features.
If no alternative exists, use the appropriate plugin below. There are two plugins, in order of preference:
1. copyNativeBindings β for native .node binaries (preferred)
Use this when a package's JS code bundles fine but it ships a native .node addon that esbuild can't inline. The plugin just copies the single binary file to the build output. The JS gets bundled normally.
Use for:better-sqlite3-multiple-ciphers, and similar packages with a single native addon.
2. externalizePackage β for packages that need their full directory structure (last resort)
Use this when a package can't be bundled at all β e.g. it loads files from disk at runtime using paths relative to its own source, or it expects helper executables/assets to exist next to the package at runtime. This plugin externalizes the entire package from the bundle and copies it along with all its transitive dependencies to the build output's node_modules/. This is heavier than copyNativeBindings because it copies the full package tree.
Use for:sharp, jsdom (loads CSS stylesheets via __dirname), node-pty (loads spawn-helper from prebuilds/ at runtime), or any package where copyNativeBindings isn't sufficient (e.g. the package loads non-binary assets from its directory at runtime).
How to tell which plugin to use: If the package crashes at runtime with __dirname-related errors, missing asset/helper files after bundling, or it expects sibling executables under its package directory, it needs externalizePackage. If it only fails to load a .node binary, copyNativeBindings is sufficient.
// This will FAIL after publish if you don't use copyNativeBindingsimportDatabasefrom"better-sqlite3";// Error: Cannot find module 'better_sqlite3.node'
3. Relying on files in node_modules:
// Bad: node_modules doesn't exist after publishconsttemplatePath=path.join(__dirname,"..","node_modules","some-pkg","template.json");// Good: bundle it as a string or copy it to build/importtemplatefrom"some-pkg/template.json";
4. Using local CLI tools:
// Bad: npx/npm scripts won't work after publishexec("npx some-tool");// Good: bundle the tool or use a Node.js library insteadimport{someTool}from"some-tool-lib";
5. Runtime node_modules symlink hacks:
# Bad: masks bundling bugs and breaks after publish
ln -s ../.glaze-sources/node_modules ../.glaze/node_modules
Never patch .glaze/ like this. The build must produce a self-contained output β whatever ends up in .glaze/ after a build is what runs in production. If something is missing at runtime, fix the build configuration (via glaze.config.ts), not the output directory.
Renderer Runtime Checks
A successful build does not guarantee that the renderer works in WKWebView. After changing dependencies or build config, launch the app and inspect the renderer console if the UI is blank or crashes.
If the backend crash-looped, rebuilding is not enough to validate the fix while the old native process is still running. Quit and reopen the app, then confirm a fresh log shows the backend started successfully before claiming the app works.
For CommonJS/React interop issues, search the built renderer assets:
If this matches, a CommonJS package was bundled without a browser-safe shim. A known high-risk dependency chain is recharts -> react-redux -> use-sync-external-store/with-selector.js. Diagnose with:
npm ls recharts react-redux use-sync-external-store
rg -n "use-sync-external-store/(with-selector|shim/with-selector)" node_modules -S
If the SDK shims do not cover the failing import path, add a Vite alias or plugin in glaze.config.ts under vite: { ... }. Do not put Vite plugins at the top level of defineConfig; Glaze only merges vite.plugins.
If the window looks blank but there are no renderer errors, verify Tailwind content scanning. renderer/styles.css ships a broad @source "./**/*.{ts,tsx}" glob that auto-covers every renderer subfolder (with @source not exclusions for preload.ts and dev/), so new window folders are scanned without edits. Only touch the @source directives if you intentionally narrowed them.
πΌοΈ Static Assets (Images, Media, Fonts)
Importing in React (Recommended)
Best for: Images, icons, and media used in React components.
// β Don't use require() for imagesconstlogo=require('./logo.png');// β Don't construct paths to node_modules (won't exist after publish)consticon=path.join(__dirname,'node_modules','pkg','icon.png');// β Don't use absolute filesystem paths<imgsrc="/Users/me/project/logo.png"/>// β Don't use root-absolute paths for public assets (file:// won't resolve)<imgsrc="/images/logo.png"/>// Wrong!<imgsrc="./images/logo.png"/>// Correct!
Testing Before Publishing
Your app must work from .glaze/build/ without node_modules/. The development structure ensures this:
.glaze/ β Runtime (same as installed apps)
βββ build/ β No node_modules fallback!
.glaze-sources/ β Development only (sibling folder)
βββ node_modules/ β NOT in Node's module resolution path
This means if something isn't properly bundled, you'll catch it during local development - not after publishing.
Test checklist before publish:
App runs and all features work
No console errors about missing modules
Native modules (if any) load correctly
External files load from correct paths
Publishing Flow
When you publish:
build/ directory is zipped β uploaded to store
Users install β build/ is extracted to their .glaze/
App runs from build/main/index.js - no node_modules/
If your app works locally, it will work after install (same directory structure).
Goal: Create a file reader with native file picker
import{useState}from'react';import{Button}from'@glaze/core/components';exportfunctionHomeView(){const[content,setContent]=useState('');consthandleOpenFile=async()=>{// Use window.glazeAPI - NEVER import ipcRenderer directlyconstresult=awaitwindow.glazeAPI.dialog.showOpenDialog({title: 'Open Text File',filters: [{name: 'Text Files',extensions: ['txt']},{name: 'All Files',extensions: ['*']}],properties: ['openFile']});if(!result.canceled&&result.filePaths.length>0){// Read file via backend handlerconstfileData=awaitwindow.glazeAPI.glaze.ipc.invoke('file:read',{path: result.filePaths[0]});setContent(fileData.content);}};consthandleSaveFile=async()=>{constresult=awaitwindow.glazeAPI.dialog.showSaveDialog({title: 'Save Text File',defaultPath: '~/untitled.txt',filters: [{name: 'Text Files',extensions: ['txt']},{name: 'All Files',extensions: ['*']}]});if(!result.canceled&&result.filePath){awaitwindow.glazeAPI.glaze.ipc.invoke('file:write',{path: result.filePath, content });}};return(<divclassName="p-4"><divclassName="flex gap-2"><ButtononClick={handleOpenFile}>OpenFile</Button><ButtononClick={handleSaveFile}>SaveFile</Button></div>{content &&(<preclassName="mt-4 text-secondary">{content}</pre>)}</div>);}
3. Backend Dialog API
You can also use dialogs directly from the backend:
In main/index.ts or any backend file:
import{dialog}from"@glaze/core/backend";import*asfsfrom"fs/promises";asyncfunctionopenAndProcessFile(){constresult=awaitdialog.showOpenDialog({title: "Select a file to process",filters: [{name: "JSON Files",extensions: ["json"]},{name: "All Files",extensions: ["*"]},],properties: ["openFile"],});if(!result.canceled&&result.filePaths.length>0){constcontent=awaitfs.readFile(result.filePaths[0],"utf8");// Process the file...returnJSON.parse(content);}}asyncfunctionsaveProcessedData(data: any){constresult=awaitdialog.showSaveDialog({title: "Save results",defaultPath: "~/results.json",filters: [{name: "JSON Files",extensions: ["json"]}],});if(!result.canceled&&result.filePath){awaitfs.writeFile(result.filePath,JSON.stringify(data,null,2),"utf8");}}
App Updates
Use /upgrade in the AI agent chat to apply pending Glaze app updates.
Glaze handles update ordering, dependency installation, rebuilds, and bundle repackaging automatically. Do not manually edit glaze.lastMigration, glaze.sdkVersion, or other update metadata in package.json.
After migration, renderer entrypoints should import local styles:
import"../styles.css";
Do not switch entrypoints to @glaze/core/components.css; SDK component styles are injected at runtime by the native shell. Use @import "@glaze/core/components.tailwind.css" in renderer/styles.css for Tailwind theme token definitions.
Customizing Shared Components
To customize shared components, create wrappers in renderer/components/:
This preserves your customizations during app migrations.
π Quick Reference
SDK API Reference
For exact @glaze/core exports, window.glazeAPI APIs, and SDK type signatures, read the SDK Symbol Map path for programmatic lookup or SDK Symbol Lines for grep-friendly lookup, then the matching small file from the SDK API Reference index. Symbol keys are window.glazeAPI.* dotted paths for runtime APIs and entrypoint#Export (e.g. @glaze/core/backend#ipcMain) for package exports β a bare export name is never a key, so grep "name":"<bareName>" for bare-name lookup. For renderer window.glazeAPI.* calls, also check the symbol's defaultPreload field: exposed is wired by the scaffolded preload, partial means only listed child symbols are wired, and requires-wiring means add a minimal wrapper in renderer/preload.ts or use backend IPC before calling it. Component entries carry a doc usage-guide path; join it to the runtime SDK Path and read it before writing component code. Do not enumerate or search sdk/current; the reference is generated from the SDK declaration files and ships with the active SDK.
Import Paths
// Backend - Window Managementimport{app,BrowserWindow}from"@glaze/core/backend";// Backend - IPC, Logging & Systemimport{ipcMain,logger,globalShortcut,Notification,Tray,Menu}from"@glaze/core/backend";// Preload ONLY - IPC and contextBridge (DO NOT use in renderer code!)import{ipcRenderer,contextBridge}from"@glaze/core/preload";// Renderer window control - prefer a backend ipcMain.handle() that calls BrowserWindow// from @glaze/core/backend, then invoke it with window.glazeAPI.glaze.ipc.invoke().// Frontend - Types only (safe to import anywhere)importtype{OpenDialogOptions,SaveDialogOptions}from"@glaze/core/ipc";// Frontend - Routerimport{createRoute}from"@tanstack/react-router";// Frontend - Componentsimport{Button}from"@glaze/core/components";// Frontend - Hooksimport{useConnection}from"@glaze/core/hooks";
SECURITY: @glaze/core/preload should ONLY be imported in renderer/preload.ts. Renderer code should use window.glazeAPI instead.
Preload Script Constraints
The preload script is built as a self-contained IIFE (not an ES module) because WKWebView injects it via WKUserScript, which only supports classic scripts. The build system handles this transparently β write normal TypeScript with imports and esbuild bundles everything.
What this means for you:
Constraint
Reason
No dynamic import()
All dependencies resolved at build time
No top-level await
Classic scripts don't support it (wrap in async function)
No Node.js APIs (fs, path, etc.)
WKWebView is a browser environment β use IPC to call backend
Keep the preload thin
Everything is inlined; large deps slow down injection
What works fine:
Static import of any npm package (bundled by esbuild)
Async functions (just can't use top-level await)
All browser/DOM APIs
ipcRenderer and contextBridge from @glaze/core/preload
Context isolation note: Glaze uses WKContentWorld, which provides fully separate JavaScript environments with their own prototypes and globals. Prototype pollution in the page world cannot affect the preload world.
globalShortcut.register() and registerAll() return Promises because native registration can fail. isRegistered(), unregister(), unregisterAll(), setSuspended(), and isSuspended() are synchronous backend APIs.
ποΈ File Modification Guide
Modify Often
main/handlers/*.ts - Backend IPC handlers and business logic
main/windows/*.ts - Window creation helpers for additional windows
renderer/main/root-view.tsx - Global layout
renderer/preload.ts - Add new APIs to expose to renderer
renderer/styles.css - Custom styles; the broad @source glob already covers new renderer folders
renderer/types/ - TypeScript types for custom APIs
Rarely Modify
renderer/main/index.tsx - React entry point setup (providers, root render)
renderer/settings/index.tsx - Settings window React entry point
glaze.config.ts - Build customization (create when needed for native modules)
Never Modify
See Critical Rules at the top of this document for the full list of protected paths.
Settings Convention & Cross-Window Sync
When the user asks to add a "setting" or "preference", place it in the app's Settings window (renderer/settings/settings-view.tsx), not inline in the main UI. The template includes a dedicated Settings window accessible via Cmd+, (Preferences menu item). Only put a setting inline in the main view if the user explicitly requests it.
The Settings window and main window are separate BrowserWindow instances with separate React trees β saving a setting in the backend does NOT automatically update the main window. Broadcast the change so the main window reacts in real time:
Backend handler: after saving, call ipcMain.broadcast("settings:foo-changed", { value }) to push to all windows.
Main window: listen with window.glazeAPI.glaze.ipc.onNotification("settings:foo-changed", callback) and update the React Query cache via queryClient.setQueryData().
Without this, settings only take effect after restarting the app or closing the Settings window.
Debugging Runtime Errors
When the user reports runtime errors, crashes, or mentions "logs", read the system log file first.
Log location: use the Latest Log File from <runtime_context> if present and not (not found yet); otherwise the Log Directory. Filename pattern glaze-{timestamp}.log; a new file is created per app launch, so the most recent file is the current session.
Never read the whole file β it can be large. Grep for error|exception|failed, then Read with an offset near the matched line. Prefer native tools (Read/Grep/Glob) over Bash.
Ignore (not errors):Backend exited with code null (signal SIGKILL) and Exiting with code 1000 to trigger hot reload restart β these are hot-reload messages. Hot reloads usually append to the current log file; only relaunches create a new file.
Steps: find the error β filter by source if needed β locate the stack trace with file/line β fix the root cause, not the symptom β explain the fix to the user.
WKWebView Rendering Caveat
When animating container height inside a glass surface (bg-glass), avoid backdrop-filter on nested controls β especially Button variant="filled", which uses backdrop-blur-xs by default in @glaze/core.
Symptom: footer controls appear duplicated/ghosted for a frame during transitions.
Cause: WebKit compositor artifact from backdrop-filter + clipping + animated height.
Mitigation: prefer backdrop-blur-none on footer buttons within animated glass composers; use strong paint containment on the shell (overflow-hidden, isolate, contain: paint); keep the footer slot a fixed/min height while content height animates.
Output a 1024Γ1024 pixel FULL SQUARE image with exactly two layers: a flat background color and one subject rendered on top.
The background color extends uniformly to every pixel at every edge and every corner, forming a perfectly flat, sharp-cornered square. The entire canvas is filled with the background color wherever the subject is absent. Your output is always a clean, sharp-edged square.
You are painting a subject directly onto a colored background β NOT generating a finished icon placed on a separate background. There is no "icon shape" in the image; the subject is the object itself.
REFERENCE IMAGES
The attached images are your quality benchmark. They are labeled by prefix:
dark- β bright subject on a dark background (dark navy, charcoal, dark teal, dark slate)
color- β subject on a bold, saturated color background (red, purple, green, blue, orange)
bleed- β full-bleed compositions where the subject extends beyond the canvas edges and gets cropped by the squircle mask. These produce the most striking, app-icon results β study them closely
generated- β icons previously generated by this pipeline that achieved great results. Study them for lighting, color, surface quality, and overall feel β but NEVER recreate the same object or concept. Always invent a new subject.
Before generating, study ALL the references. Learn from their EXECUTION, not their subjects. Never recreate the same object from any reference. Instead, absorb these qualities and apply them to an original subject:
Proportions: In every reference, the subject (the element on top of the background) fills most of the canvas. Only a thin margin of background is visible around it. Match these proportions.
Perspective: Every subject faces you HEAD-ON. You see the front face only. Nothing is tilted, rotated, or shown from an angle. The subject feels dimensional through its surface and lighting β not through camera rotation.
Simplicity: Each is ONE simple, bold shape that reads clearly at small sizes. If you squint, the silhouette alone tells you what it is.
Surfaces: Soft gradients, gentle highlights, smooth color transitions. The subject feels slightly elevated from the background β a hint of depth and physical presence. The material is premium: polished plastic, soft-touch glass, refined matte surfaces. Never harsh chrome or brushed-steel.
Lighting: Soft top-left light creates gentle highlights on top edges. A subtle ambient glow where the bright subject meets the dark background gives the "elevated" feeling. No hard shadows.
Color harmony: Rich saturated subject, deep muted background tinted to complement the subject's hue. Warm with warm, cool with cool. Always harmonious.
Background fills everything. The background color reaches every edge and every corner of the square canvas. The background is uniform and continuous β it extends identically to all edges with a seamless, invisible transition to the canvas boundary.
ICON DESIGN
Remember: you are generating a flat background with a subject rendered on top β not a standalone icon graphic placed on a background. The "subject" below is the physical object or symbol itself, painted directly onto the background color.
Dark (most common) β deep muted background, bright subject glowing against it. Tint the background to complement the subject's color.
Color β a bold, saturated single color filling the canvas, with the subject in white, black, or a complementary tone. Ultra-simple, almost glyph-like.
Composition β single subject or full-bleed:
Most great icons use a single bold subject so large that it nearly bleeds off the canvas edges. Full-bleed is encouraged β when the subject extends beyond the canvas and the squircle mask crops it, the result looks most like a real macOS app icon. Study the bleed- references: the subject doesn't just sit on the background, it IS the icon. Even full-bleed compositions have ONE clear focal element.
Size: The subject is MASSIVE β it should fill almost the entire canvas, leaving only a thin margin of background visible around it. Study the reference images closely: in every one, the subject occupies the vast majority of the space. Match those proportions exactly. If it looks "big enough," scale it up another 20%.
Perspective: Perfectly front-on. The camera is directly in front of the subject, not above it, not below it, not to the side. You see ONLY the front face β like looking straight at a painting on a wall. No backward tilt, no top-down angle, no receding perspective. A book shows its front cover flat. A button shows its face flat. A screen shows its display flat. If the top edge of the subject appears to recede or tilt away from you, the angle is wrong.
Materials: The subject should feel tactile and seductive β surfaces that glow from within, materials that look warm, refractive, or polished. Soft matte gradients, gentle specular highlights, smooth color transitions. Molten glass, polished amber, translucent crystal. This inner luminosity should come from the material itself β not from effects layered on top (no lens flares, no sparkle particles, no neon halos). Avoid harsh metallic chrome or brushed-steel gradients.
Simplicity: The icon must read clearly at 32Γ32 pixels. One bold shape, one clear silhouette. No small details, no clutter, no secondary elements. When in doubt, remove detail.
Lighting: Soft, diffused, from top-left. No drop shadows on the background.
No text. No letters, words, labels, or abbreviations anywhere β not even on the object.
No nested frames. No rounded rectangles, cards, panels, or borders inside the icon. Do not render the subject as a miniature icon sitting on the background β the subject is the object itself, painted directly onto the background.
AESTHETIC TARGET
The result belongs on a MacBook dock next to Apple's own apps. Simple, bold, refined. Premium surfaces, huge confident sizing, one clear concept. When in doubt, make it simpler and bigger.
USER REFERENCES
If the user uploaded reference images, match their style, color, material, and mood. User references override everything above.
OUTPUT
A 1024Γ1024 full square. The background color extends uniformly to every pixel at every edge and corner β a perfectly flat, sharp-cornered square. One huge subject rendered directly on the flat background β not a miniature icon or framed graphic placed on a separate surface. Premium macOS quality. Clean rendering β no artifacts, stray marks, or visual noise.