Window Panel Authoring
A window is an overlay whose render module returns a WindowPanel. The engine (WindowPanel, window manager, persistence, links) is the package @ai-matrx/summon; a defect in that engine is fixed in ../aidream/apps/shared/summon in the same session, never worked around in the window. Slots, drag, tray and phone presentation: window-panels skill. Definition, render module, registry and opener rules: overlay-system skill (read it first, the file list below assumes it).
If the window is an agent-aware surface, invoke surface-authoring and its overlay-surface reference. A window resident never inherits ambient surface values; an outside helper may receive an approved snapshot under its documented integration contract.
Files you touch
| File | When |
|---|
features/window-panels/windows/[<feature>/]MyFeatureWindow.tsx | The window component. |
features/overlays/definitions/<id>.overlay.tsx | Definition: id, label, instance, and the window block (windowId, mobile, preservation, restoreDefaults, ephemeral, deprecated), optional link. Holds the opener (useOpenX, XController). |
features/overlays/definitions/<id>.render.tsx | Lazy chunk: narrows data, wires props by name (no spread). |
definitions/registry.ts | The one hand line, d.<id>Overlay,; then pnpm gen:overlays (overlay-system "Add an overlay"). |
features/window-panels/tools-grid/toolsGridTiles.ts | Optional Tools grid tile. |
definitions/<id>.link.ts | Optional ?panels= hydrator. |
Steps
1. The window component
"use client";
import { useState } from "react";
import { WindowPanel } from "@ai-matrx/summon/window";
import { MyFeatureBody } from "@/features/my-feature/components/MyFeatureBody";
interface MyFeatureWindowProps {
onClose: () => void;
initialSelectedId?: string | null; // seeded from overlay data by the render module
}
export default function MyFeatureWindow({ onClose, initialSelectedId }: MyFeatureWindowProps) {
const [selectedId, setSelectedId] = useState<string | null>(initialSelectedId ?? null);
const collectData = (): Record<string, unknown> => ({ selectedId });
return (
<WindowPanel
title="My Feature"
id="my-feature-window" // stable, never random: geometry is keyed by it
overlayId="myFeatureWindow" // identical to the definition id; this is the close binding
minWidth={380} minHeight={280} width={560} height={440} position="center"
onClose={onClose}
onCollectData={collectData}
bodyClassName="flex min-h-0 flex-1 flex-col overflow-hidden p-0"
>
<MyFeatureBody selectedId={selectedId} onSelect={setSelectedId} />
</WindowPanel>
);
}
Rules:
- No
isOpen prop and no if (!isOpen) return null: a closed overlay is never rendered, so the render module only mounts an open window.
id is stable. overlayId equals the definition id. Close binding is exactly one of overlayId, boundToOverlay, onClose (type-enforced).
instance: "multi" windows are keyed by the render module's key={instanceId}; pass the instance id into the component so each instance gets distinct state (windowInstanceId pattern in NotesWindow).
onCollectData returns all keys the window preserves, read from live state; the React Compiler memoizes it, so no manual useCallback.
- Body is content only; chrome is slots (
window-panels skill). A window wraps the canonical component (FEATURE.md law), never a hand-rolled copy.
- A window component that other code props-extends uses
Omit<WindowPanelBaseProps, ...> from @ai-matrx/summon/window.
2. The definition and render module
Create <id>.overlay.tsx with a window block and <id>.render.tsx that renders MyFeatureWindow with onClose={() => close()} and initialSelectedId narrowed from data; add d.myFeatureWindowOverlay, to registry.ts and run pnpm gen:overlays (overlay-system). Do not import MyFeatureWindow from anywhere except its render module (and its own tests).
window: {
windowId: "my-feature-window",
mobile: "drawer", // fullscreen | drawer | card | hidden
preservation: { dataKeys: ["selectedId"] }, // opt in to refresh restore (default-deny)
restoreDefaults: { selectedId: null },
// ephemeral: true, // never saves anything
},
3. (Optional) Tools grid tile
Tiles are declarative in tools-grid/toolsGridTiles.ts; ToolsGrid.tsx and activateToolsGridTile.ts read the definition via appOverlayDefinition(overlayId) (instance strategy comes from instance). Never add a tile in features/window-panels/components/SidebarWindowToggle.tsx.
{ id: "tile.my-feature", label: "My Feature", icon: MyFeatureIcon, category: "general",
overlayId: "myFeatureWindow" /* seedData: (ctx) => ({ selectedId: null }) */ },
4. (Optional) ?panels= address
Three edits, all required, or the link opens an empty frame: (a) link: { key: "my_feature", hydrate } on the definition (heavy hydrator in myFeatureWindow.link.ts, loaded lazily; the hydrator must call open({ data })), (b) urlSyncKey="my_feature" on the WindowPanel when it differs from the default, (c) urlSyncId={subjectId} on the WindowPanel. Two windows may not claim one key (the package throws on a duplicate link key). features/window-panels/__tests__/everyWindowHasAnAddress.test.ts refuses a window with a durable subject (...Id, Slug, Token data) and no address, unless listed in registry/window-address-baseline.json (which only shrinks).
Persistence contract (default-deny)
A window survives a refresh only when its window.preservation.dataKeys lists what to keep and it is not ephemeral. Geometry (rect, windowState, sidebarOpen, zIndex) is captured by the package; the window supplies content via onCollectData, filtered to dataKeys, JSON only, at most maxDataBytes (32 KiB default). Saves are debounced 250 ms after any window-manager or state change and flushed on pagehide; close removes the window from the saved workspace. Storage is users.user_surface_state (details: window-panels persistence branch). Callback-group windows (callbackGroupId in data) are ephemeral: true and listed addressless with the reason.
Ephemeral windows
ephemeral: true on the definition's window block for debug panels, one-shot dialogs and callback-group windows. It governs restore only, never reachability: an ephemeral window can and should still have a link.
Updating a window
- Change
restoreDefaults and preservation.dataKeys together (a new key missing from dataKeys is silently dropped from storage).
- Update
onCollectData to return all current keys.
- Props changed: update the render module's by-name wiring and the opener's
Open…Options in the .overlay file. Then run pnpm gen:overlays if label, instance or window identity moved (pnpm check:overlays fails until you do).
Sidebar layout
<div className="flex flex-col min-h-0 h-full">
<div className="px-2 py-1 border-b type-title shrink-0">Header</div>
<div className="flex-1 min-h-0 p-1.5 space-y-1">{items.map((item) => <Item key={item.id} />)}</div>
</div>
h-full min-h-0 flex flex-col on the root; no overflow-y-auto on the root (WindowPanel handles it); no shrink-0 on the root.
Common mistakes
| Mistake | Symptom | Fix |
|---|
overlayId in the component differs from the definition id | Type error, or the window reads another overlay's metadata | Make them identical |
Definition missing from registry.ts | Window never renders; pnpm check:overlays names it | Add d.<id>Overlay, to APP_OVERLAYS |
| Generated index stale | pnpm check:overlays fails | Run pnpm gen:overlays and commit the result |
Definition file lacks "use client" | Production build fails collecting page data for a server page | Add it |
No window.preservation | Window reopens empty after refresh; Save does nothing | Declare dataKeys |
| Multi-instance window without a per-instance id | Windows share state, or restore finds no instance | Use key={instanceId} and pass instanceId through |
Random id on WindowPanel | Geometry lost on re-render | Stable string |
ephemeral: true but the window needs restore | Window does not reopen | Remove ephemeral, declare preservation |
Link edits skip urlSyncId | Every instance writes the same token | Pass the subject id |
| Fixing package behavior in the window | Diverges from every other host | Fix ../aidream/apps/shared/summon |
Resources
- Package guides:
../aidream/apps/shared/summon/README.md (WindowPanel, links, persistence, handles) and FEATURE.md (invariants).
- App window facts and laws:
features/window-panels/FEATURE.md. Definitions: features/overlays/definitions/. Host: features/overlays/host/.
- Openers, registry, mounting:
overlay-system skill. Slots, drag, tray: window-panels skill. Removal: remove-window-panel skill.