Menu / Tray / Dialog / NotificationStatus: future · Priority: medium · Depends on: Window API
Note: This is a future plan, not a commitment. The syntax and API shown here are proposals — they can be completely different when actually implemented.
The first wave of native OS modules after the Window API pattern is proven. Each follows the same convention: config-object constructor (if instantiable), hand-written .d.ts, works both imperatively and (where relevant) via file conventions.
MenuInstantiable class — an app menu bar and/or context menus.
const fileMenu = new Menu({ title: "File", items: [
{ label: "Open", onClick: () => openFile() },
{ label: "Quit", onClick: () => App.quit() }
]})
// context menus attach to elements:
<div onContextMenu={() => fileMenu.showAt(mouseX, mouseY)}>…</div>TrayUsually a singleton (static class) — system tray icon + menu. Revisit if multi-tray is ever needed.
Tray.setIcon("./assets/icon.png")
Tray.setMenu([{ label: "Show", onClick: () => mainWin.show() }])DialogStatic methods, no persistent identity — native open/save dialogs.
const file = await Dialog.showOpen({ filters: [{ name: "Images", exts: ["png", "jpg"] }] })NotificationInstantiable class with a lifecycle (show/close) — OS notifications.
const n = new Notification({ title: "Build finished", body: "morph run succeeded" })
n.show()class + new + config object).d.ts shipped in node_modules/morph for full editor autocompleteimport { Menu } from 'morph')API.md (or docs section) lists all public exports — the contract checklist when adding modulesPartial coverage here would be worse than none — an app with dialogs but no menus still can't ship. So the commitment is the full set, in dependency order (each step independently shippable, none useful half-done):
Dialog first — most self-contained (no persistent state, no window chrome interplay). showOpen/showSave/message boxes, async via the coroutine scheduler. Unblocks every file-handling app on day one.Menu second — app menu bar + context menus. Most visible value; every commercial app needs File/Edit/View before anything else.Notification third — class lifecycle (show/close/click-action), grouping + actions per OS convention. Background apps are pointless without a way to tap the user on the shoulder.Tray last — singleton with icon + menu, depending on Desktop Integration (single-instance + login-item are what make tray-first apps real).Full scope per module (not just the happy path): disabled states + separators + accelerators for menus; multi-select + filters + default-path for dialogs; action buttons + reply fields for notifications; tooltip + click/double-click distinction for tray. The boring completeness is the feature — users file bugs about the second menu separator, never the first.
| Module | State |
|---|---|
Menu / Tray / Dialog / Notification |
❌ Nothing in the runtime (grep confirms no widget/class exists) |
morph check stub tags |
⚠️ input / select / textarea flagged as registered-but-unimplemented; native modules aren't registered at all |
Dialog.showOpen returning a Promise fits the existing coroutine scheduler (morph::Result<T>), but blocking native dialogs run on which thread?Dialog first (most self-contained, no persistent state) or Menu (most visible value)?Dialog.showOpen/showSave — static class, async via coroutinesMenu — app menu bar wired to MorphWindowNotification — class with show/close lifecycleTray — singleton; revisit only if multi-tray becomes real