@signal9/era-ui 3.17.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ export const SNAPSHOT_VERSION = 1;
14
14
  * trusting `as`, and refuses an unknown `version` outright — silently importing
15
15
  * a layout you only half understand is worse than declining it.
16
16
  *
17
- * Individual windows are filtered, not fatal: one malformed entry should not
17
+ * Individual panes are filtered, not fatal: one malformed entry should not
18
18
  * cost the user the rest of the layout. applySnapshot() drops unknown appIds the
19
19
  * same way, for the same reason.
20
20
  */
@@ -33,7 +33,7 @@ export function readSnapshot(json) {
33
33
  return null;
34
34
  if (typeof o.activeWorkspaceId !== 'string')
35
35
  return null;
36
- if (!Array.isArray(o.workspaces) || !Array.isArray(o.windows))
36
+ if (!Array.isArray(o.workspaces) || !Array.isArray(o.panes))
37
37
  return null;
38
38
  const isPoint = (v) => typeof v === 'object' &&
39
39
  v !== null &&
@@ -56,7 +56,7 @@ export function readSnapshot(json) {
56
56
  if (workspaces.length === 0)
57
57
  return null;
58
58
  const states = ['normal', 'minimized', 'maximized'];
59
- const windows = o.windows.filter((w) => {
59
+ const panes = o.panes.filter((w) => {
60
60
  if (typeof w !== 'object' || w === null)
61
61
  return false;
62
62
  const x = w;
@@ -72,20 +72,20 @@ export function readSnapshot(json) {
72
72
  savedAt: typeof o.savedAt === 'number' ? o.savedAt : 0,
73
73
  activeWorkspaceId: o.activeWorkspaceId,
74
74
  workspaces,
75
- windows
75
+ panes
76
76
  };
77
77
  }
78
78
  const DEFAULT_SIZE = { width: 480, height: 320 };
79
79
  /**
80
- * The source of truth for the desktop: which workspaces and windows exist,
80
+ * The source of truth for the desktop: which workspaces and panes exist,
81
81
  * their geometry, and their stacking / focus order. Everything visual
82
- * (Desktop, Window, Taskbar, Launcher, …) is a pure view of this. Runes-based,
82
+ * (Desktop, Pane, Taskbar, Launcher, …) is a pure view of this. Runes-based,
83
83
  * shared via context.
84
84
  */
85
- export class WindowManager {
86
- /** Every live window, across all workspaces. Order is spawn order; paint
85
+ export class PaneManager {
86
+ /** Every live pane, across all workspaces. Order is spawn order; paint
87
87
  * order is by `z`; visibility is by workspace. */
88
- windows = $state([]);
88
+ panes = $state([]);
89
89
  /** Registered app templates, keyed by id. */
90
90
  apps = $state.raw({});
91
91
  /** The virtual desktops. Always at least one. */
@@ -98,7 +98,7 @@ export class WindowManager {
98
98
  * The work area = bounds − insets; maximize, snap and drag containment
99
99
  * honour it. */
100
100
  insets = $state({ top: 0, right: 0, bottom: 0, left: 0 });
101
- /** Drag containment: a window's bar may never leave the top of the work
101
+ /** Drag containment: a pane's bar may never leave the top of the work
102
102
  * area, and at least this many px of it must stay reachable on every
103
103
  * other side. */
104
104
  dragKeep = 25;
@@ -106,7 +106,7 @@ export class WindowManager {
106
106
  #seq = 0;
107
107
  #wsSeq = 0;
108
108
  /** The usable desktop: bounds minus reserved chrome. Everything that places
109
- * a window against "the screen" (maximize, snap, containment) uses this,
109
+ * a pane against "the screen" (maximize, snap, containment) uses this,
110
110
  * never raw bounds — a strip hidden behind chrome is as lost as one
111
111
  * off-screen. */
112
112
  workArea = $derived.by(() => {
@@ -118,14 +118,14 @@ export class WindowManager {
118
118
  height: Math.max(0, this.bounds.height - top - bottom)
119
119
  };
120
120
  });
121
- /** The active workspace's windows — what the Desktop paints and the
121
+ /** The active workspace's panes — what the Desktop paints and the
122
122
  * Taskbar lists. */
123
- activeWindows = $derived.by(() => this.windows.filter((w) => w.workspaceId === this.activeWorkspaceId));
124
- /** The focused window = the top-most non-minimized one on the active
123
+ activePanes = $derived.by(() => this.panes.filter((w) => w.workspaceId === this.activeWorkspaceId));
124
+ /** The focused pane = the top-most non-minimized one on the active
125
125
  * workspace. */
126
126
  focusedId = $derived.by(() => {
127
127
  let top = null;
128
- for (const w of this.activeWindows) {
128
+ for (const w of this.activePanes) {
129
129
  if (w.state === 'minimized')
130
130
  continue;
131
131
  if (!top || w.z > top.z)
@@ -141,20 +141,20 @@ export class WindowManager {
141
141
  this.apps = { ...this.apps, ...Object.fromEntries(apps.map((a) => [a.id, a])) };
142
142
  }
143
143
  #find(id) {
144
- return this.windows.find((w) => w.id === id);
144
+ return this.panes.find((w) => w.id === id);
145
145
  }
146
- /* ---------------- windows ---------------- */
147
- /** Spawn a window for an app (or focus the existing one if it's a
146
+ /* ---------------- panes ---------------- */
147
+ /** Spawn a pane for an app (or focus the existing one if it's a
148
148
  * singleton — from any workspace; it is brought to the active one). */
149
149
  open(appId, overrides) {
150
150
  const app = this.apps[appId];
151
151
  if (!app)
152
152
  return null;
153
153
  if (app.singleton) {
154
- const existing = this.windows.find((w) => w.appId === appId);
154
+ const existing = this.panes.find((w) => w.appId === appId);
155
155
  if (existing) {
156
156
  // Reveal, don't relocate: restore() → focus() switches to the
157
- // workspace the window already lives on, the way every virtual
157
+ // workspace the pane already lives on, the way every virtual
158
158
  // desktop behaves. An explicit override still moves it.
159
159
  if (overrides?.workspaceId)
160
160
  existing.workspaceId = overrides.workspaceId;
@@ -165,7 +165,7 @@ export class WindowManager {
165
165
  const id = `${appId}#${++this.#seq}`;
166
166
  // Cascade from the work area's origin so stacked spawns don't land
167
167
  // exactly on top of each other (or under top chrome).
168
- const n = this.windows.length % 8;
168
+ const n = this.panes.length % 8;
169
169
  const { x, y } = this.workArea;
170
170
  const win = {
171
171
  id,
@@ -185,13 +185,13 @@ export class WindowManager {
185
185
  actions: app.actions,
186
186
  props: app.props
187
187
  };
188
- this.windows.push(win);
188
+ this.panes.push(win);
189
189
  return id;
190
190
  }
191
191
  close(id) {
192
- this.windows = this.windows.filter((w) => w.id !== id);
192
+ this.panes = this.panes.filter((w) => w.id !== id);
193
193
  }
194
- /** Make a window the frontmost VISIBLE one: switch to its workspace,
194
+ /** Make a pane the frontmost VISIBLE one: switch to its workspace,
195
195
  * un-minimize it, and bring it to the front. Focus means reveal at every
196
196
  * level — never a silent off-screen z bump. */
197
197
  focus(id) {
@@ -222,7 +222,7 @@ export class WindowManager {
222
222
  else
223
223
  this.focus(id);
224
224
  }
225
- /** Fill the work area (middle-click on the window's ×). Keeps the first
225
+ /** Fill the work area (middle-click on the pane's ×). Keeps the first
226
226
  * pre-maximize/pre-snap frame so restore returns to the true origin. */
227
227
  maximize(id) {
228
228
  const w = this.#find(id);
@@ -258,8 +258,8 @@ export class WindowManager {
258
258
  w.state = 'normal';
259
259
  this.focus(id);
260
260
  }
261
- /** Re-fit windows after the desktop or its chrome resizes: maximized
262
- * windows re-fill the work area; normal windows re-clamp so the bar stays
261
+ /** Re-fit panes after the desktop or its chrome resizes: maximized
262
+ * panes re-fill the work area; normal panes re-clamp so the bar stays
263
263
  * below the top and at least dragKeep px stays reachable on every other
264
264
  * side — the same fence the drag containment enforces, applied at rest.
265
265
  * The Desktop calls this whenever the work area changes; chrome itself
@@ -268,7 +268,7 @@ export class WindowManager {
268
268
  const { x, y, width, height } = this.workArea;
269
269
  if (!width || !height)
270
270
  return;
271
- for (const w of this.windows) {
271
+ for (const w of this.panes) {
272
272
  if (w.state === 'maximized') {
273
273
  w.pos = { x, y };
274
274
  w.size = { width, height };
@@ -288,7 +288,7 @@ export class WindowManager {
288
288
  /**
289
289
  * The whole desktop as one JSON-safe document.
290
290
  *
291
- * Reads every workspace and every window across ALL of them, not just the
291
+ * Reads every workspace and every pane across ALL of them, not just the
292
292
  * active one — a snapshot that only captured what was on screen would quietly
293
293
  * drop the other workspaces the moment it was restored.
294
294
  */
@@ -298,7 +298,7 @@ export class WindowManager {
298
298
  savedAt: Date.now(),
299
299
  activeWorkspaceId: this.activeWorkspaceId,
300
300
  workspaces: this.workspaces.map((w) => ({ ...w })),
301
- windows: this.windows.map((w) => {
301
+ panes: this.panes.map((w) => {
302
302
  const app = this.apps[w.appId];
303
303
  const snap = {
304
304
  appId: w.appId,
@@ -312,7 +312,7 @@ export class WindowManager {
312
312
  };
313
313
  // Only record a title the user changed. Storing the app's own default
314
314
  // would pin it, so renaming the app in code would not reach any
315
- // window restored from an older snapshot.
315
+ // pane restored from an older snapshot.
316
316
  if (app && w.title !== app.title)
317
317
  snap.title = w.title;
318
318
  if (w.props !== undefined)
@@ -325,15 +325,15 @@ export class WindowManager {
325
325
  * Replace the desktop with a snapshot.
326
326
  *
327
327
  * Named applySnapshot, not restore: `restore(id)` already means "un-maximize
328
- * this window", and one verb cannot mean both.
328
+ * this pane", and one verb cannot mean both.
329
329
  *
330
330
  * Windows whose `appId` is no longer registered are SKIPPED rather than
331
331
  * failing the whole restore: an app can be removed between export and import,
332
- * and losing one window beats losing the layout. Same for a window pointing at
332
+ * and losing one pane beats losing the layout. Same for a pane pointing at
333
333
  * a workspace the snapshot does not contain.
334
334
  */
335
335
  applySnapshot(snapshot) {
336
- this.windows = [];
336
+ this.panes = [];
337
337
  this.workspaces = snapshot.workspaces.map((w) => ({ ...w }));
338
338
  if (this.workspaces.length === 0)
339
339
  this.workspaces = [{ id: 'w1', name: '1' }];
@@ -344,11 +344,11 @@ export class WindowManager {
344
344
  ? snapshot.activeWorkspaceId
345
345
  : this.workspaces[0].id;
346
346
  let z = 0;
347
- for (const s of snapshot.windows) {
347
+ for (const s of snapshot.panes) {
348
348
  const app = this.apps[s.appId];
349
349
  if (!app || !known.includes(s.workspaceId))
350
350
  continue;
351
- this.windows.push({
351
+ this.panes.push({
352
352
  id: `${s.appId}#${++this.#seq}`,
353
353
  appId: s.appId,
354
354
  workspaceId: s.workspaceId,
@@ -368,7 +368,7 @@ export class WindowManager {
368
368
  });
369
369
  }
370
370
  this.#z = z;
371
- this.focusedId = this.activeWindows.at(-1)?.id ?? null;
371
+ this.focusedId = this.activePanes.at(-1)?.id ?? null;
372
372
  }
373
373
  /** Half / quadrant tiling against the work area. */
374
374
  snap(id, zone) {
@@ -411,7 +411,7 @@ export class WindowManager {
411
411
  if (this.workspaces.some((ws) => ws.id === id))
412
412
  this.activeWorkspaceId = id;
413
413
  }
414
- /** Remove a workspace. Its windows migrate to the fallback (the active
414
+ /** Remove a workspace. Its panes migrate to the fallback (the active
415
415
  * workspace, or the first remaining one) — closing a desktop never
416
416
  * silently closes the work on it. The last workspace can't be removed. */
417
417
  removeWorkspace(id) {
@@ -420,12 +420,12 @@ export class WindowManager {
420
420
  this.workspaces = this.workspaces.filter((ws) => ws.id !== id);
421
421
  if (this.activeWorkspaceId === id)
422
422
  this.activeWorkspaceId = this.workspaces[0].id;
423
- for (const w of this.windows) {
423
+ for (const w of this.panes) {
424
424
  if (w.workspaceId === id)
425
425
  w.workspaceId = this.activeWorkspaceId;
426
426
  }
427
427
  }
428
- /** Send a window to another workspace (it stays where it was on screen). */
428
+ /** Send a pane to another workspace (it stays where it was on screen). */
429
429
  moveToWorkspace(windowId, workspaceId) {
430
430
  const w = this.#find(windowId);
431
431
  if (!w || !this.workspaces.some((ws) => ws.id === workspaceId))
@@ -433,14 +433,14 @@ export class WindowManager {
433
433
  w.workspaceId = workspaceId;
434
434
  }
435
435
  }
436
- const WM_KEY = Symbol('era-wm');
437
- export function setWindowManagerContext(wm) {
438
- setContext(WM_KEY, wm);
439
- return wm;
436
+ const PM_KEY = Symbol('era-pm');
437
+ export function setPaneManagerContext(pm) {
438
+ setContext(PM_KEY, pm);
439
+ return pm;
440
440
  }
441
- export function getWindowManager() {
442
- const wm = getContext(WM_KEY);
443
- if (!wm)
444
- throw new Error('getWindowManager() must be used inside a <Desktop>.');
445
- return wm;
441
+ export function getPaneManager() {
442
+ const pm = getContext(PM_KEY);
443
+ if (!pm)
444
+ throw new Error('getPaneManager() must be used inside a <Desktop>.');
445
+ return pm;
446
446
  }
@@ -6,16 +6,16 @@
6
6
  import X from '@lucide/svelte/icons/x';
7
7
  import type { Plugin } from '@neodrag/svelte';
8
8
  import { cn } from '../utils/index.js';
9
- import { getWindowManager, type AppWindow } from './wm.svelte.js';
9
+ import { getPaneManager, type AppPane } from './pane-manager.svelte.js';
10
10
 
11
- let { window: win }: { window: AppWindow } = $props();
11
+ let { pane }: { pane: AppPane } = $props();
12
12
 
13
- const wm = getWindowManager();
14
- const focused = $derived(wm.focusedId === win.id);
13
+ const pm = getPaneManager();
14
+ const focused = $derived(pm.focusedId === pane.id);
15
15
 
16
- // Shell containment: a window can never become unrecoverable. The bar may
16
+ // Shell containment: a pane can never become unrecoverable. The bar may
17
17
  // not leave the TOP of the work area at all (y ≥ workArea.y), and on every
18
- // other side at least wm.dragKeep px of the window must stay inside the
18
+ // other side at least pm.dragKeep px of the pane must stay inside the
19
19
  // work area (which excludes chrome — a strip hidden behind the taskbar
20
20
  // would be just as lost). Clamped mid-drag the way neodrag's own bounds
21
21
  // plugin does it: offset + proposed delta → clamp → re-propose the delta,
@@ -26,9 +26,9 @@
26
26
  const shellClamp: Plugin = {
27
27
  name: 'era:shell-clamp',
28
28
  drag(ctx) {
29
- const area = wm.workArea;
29
+ const area = pm.workArea;
30
30
  if (!area.width || !area.height) return;
31
- const keep = wm.dragKeep;
31
+ const keep = pm.dragKeep;
32
32
  const width = (ctx.rootNode as HTMLElement).offsetWidth;
33
33
  const px = ctx.offset.x + (ctx.proposed.x ?? 0);
34
34
  const py = ctx.offset.y + (ctx.proposed.y ?? 0);
@@ -37,9 +37,9 @@
37
37
  ctx.propose(cx - ctx.offset.x, cy - ctx.offset.y);
38
38
  }
39
39
  };
40
- const windowPlugins = [shellClamp];
40
+ const panePlugins = [shellClamp];
41
41
 
42
- // The window's single control: left-click CLOSES, right-click MINIMIZES (the
42
+ // The pane's single control: left-click CLOSES, right-click MINIMIZES (the
43
43
  // taskbar restores it), middle-click toggles MAXIMIZE — no dedicated buttons.
44
44
  // While a non-primary button is held the glyph previews its action (minus /
45
45
  // square); a left press keeps the ×, so holding never implies another action
@@ -53,43 +53,44 @@
53
53
  // clobber that on every reorder. A directive-style single-property write is safe.
54
54
  let ref = $state<HTMLDivElement | null>(null);
55
55
  $effect(() => {
56
- if (ref) ref.style.zIndex = String(win.z);
56
+ if (ref) ref.style.zIndex = String(pane.z);
57
57
  });
58
58
  </script>
59
59
 
60
60
  <Pane.Root
61
61
  bind:ref
62
- data-window
63
- bind:position={win.pos}
64
- bind:size={win.size}
65
- resizable={win.resizable}
66
- plugins={windowPlugins}
67
- onpointerdowncapture={() => wm.focus(win.id)}
62
+ data-pane
63
+ bind:position={pane.pos}
64
+ bind:size={pane.size}
65
+ resizable={pane.resizable}
66
+ plugins={panePlugins}
67
+ onpointerdowncapture={() => pm.focus(pane.id)}
68
68
  class={cn(
69
69
  'transition-[box-shadow,opacity]',
70
- // Focused window reads forward; the rest recede (dimmer, flatter).
70
+ // Focused pane reads forward; the rest recede (dimmer, flatter).
71
71
  focused ? 'outline -outline-offset-1 outline-primary/70' : 'opacity-95 shadow-(--era-shadow)',
72
- win.state === 'minimized' && 'hidden'
72
+ pane.state === 'minimized' && 'hidden'
73
73
  )}
74
74
  >
75
75
  <Pane.Handle class="gap-(--era-gap)">
76
- {#if win.icon}
77
- {@const Icon = win.icon}
76
+ {#if pane.icon}
77
+ {@const Icon = pane.icon}
78
78
  <Icon class="size-(--era-h-xs) shrink-0 text-muted" />
79
79
  {/if}
80
- <span class={cn('truncate text-body', focused ? 'text-bright' : 'text-muted')}>{win.title}</span
80
+ <span class={cn('truncate text-body', focused ? 'text-bright' : 'text-muted')}
81
+ >{pane.title}</span
81
82
  >
82
83
  <!-- Close / minimize / maximize. Deliberately NOT data-pane-control (same as
83
- the raw Pane.Close): a left press-and-HOLD drags the window — neodrag only
84
+ the raw Pane.Close): a left press-and-HOLD drags the pane — neodrag only
84
85
  drags the primary button and suppresses the trailing click after a real
85
86
  drag, so a clean click still closes, and right/middle arming is a
86
87
  different button entirely. -->
87
- <!-- Window actions sit LEFT of the close button, on the same --era-gap
88
+ <!-- Pane actions sit LEFT of the close button, on the same --era-gap
88
89
  rhythm as the rest of the handle. Like the close button they carry no
89
90
  data-pane-control, so a press-and-hold anywhere on the bar still drags
90
- the window and only a clean click activates them. -->
91
+ the pane and only a clean click activates them. -->
91
92
  <div class="ml-auto flex shrink-0 items-center gap-(--era-gap)">
92
- {@render win.actions?.()}
93
+ {@render pane.actions?.()}
93
94
  <!-- The macOS traffic lights, one button: red = close at rest, and the
94
95
  held preview recolours to the action it will commit — yellow for
95
96
  minimize, green for maximize — so the tone and glyph agree on what
@@ -116,18 +117,18 @@
116
117
  armed === 'maximize' ? '[&_svg]:size-(--era-icon-xs)' : '[&_svg]:size-(--era-icon-xxs)'
117
118
  )}
118
119
  aria-label="Close (right-click minimizes, middle-click maximizes)"
119
- onclick={() => wm.close(win.id)}
120
+ onclick={() => pm.close(pane.id)}
120
121
  oncontextmenu={(e: MouseEvent) => e.preventDefault()}
121
122
  onpointerdown={(e: PointerEvent) => {
122
123
  // Cancelling the middle pointerdown suppresses its compatibility
123
124
  // mouse events, so the browser's autoscroll never engages.
124
125
  if (e.button === 1) e.preventDefault();
125
126
  armed =
126
- e.button === 2 && win.minimizable ? 'minimize' : e.button === 1 ? 'maximize' : null;
127
+ e.button === 2 && pane.minimizable ? 'minimize' : e.button === 1 ? 'maximize' : null;
127
128
  }}
128
129
  onpointerup={(e: PointerEvent) => {
129
- if (e.button === 2 && armed === 'minimize') wm.minimize(win.id);
130
- else if (e.button === 1 && armed === 'maximize') wm.toggleMaximize(win.id);
130
+ if (e.button === 2 && armed === 'minimize') pm.minimize(pane.id);
131
+ else if (e.button === 1 && armed === 'maximize') pm.toggleMaximize(pane.id);
131
132
  armed = null;
132
133
  }}
133
134
  onpointerleave={() => (armed = null)}
@@ -144,10 +145,10 @@
144
145
  </div>
145
146
  </Pane.Handle>
146
147
 
147
- <Pane.Content class={win.bodyClass}>
148
- {#if win.component}
149
- {@const Body = win.component}
150
- <Body window={win} {wm} />
148
+ <Pane.Content class={pane.bodyClass}>
149
+ {#if pane.component}
150
+ {@const Body = pane.component}
151
+ <Body {pane} {pm} />
151
152
  {/if}
152
153
  </Pane.Content>
153
154
  </Pane.Root>
@@ -0,0 +1,8 @@
1
+ import * as Pane from '../ui/pane';
2
+ import { type AppPane } from './pane-manager.svelte.js';
3
+ type $$ComponentProps = {
4
+ pane: AppPane;
5
+ };
6
+ declare const Pane: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type Pane = ReturnType<typeof Pane>;
8
+ export default Pane;
@@ -9,7 +9,7 @@
9
9
  import Bell from '@lucide/svelte/icons/bell';
10
10
  import Terminal from '@lucide/svelte/icons/terminal';
11
11
  import { cn } from '../utils/index.js';
12
- import { getWindowManager } from './wm.svelte.js';
12
+ import { getPaneManager } from './pane-manager.svelte.js';
13
13
  import { getNotifications } from './notifications.svelte.js';
14
14
  import { LAYER } from './layers.js';
15
15
 
@@ -36,16 +36,16 @@
36
36
  position?: 'top' | 'bottom';
37
37
  /** Float the bar on the desktop with a uniform --era-gap gutter (fully
38
38
  * rounded, borderless — a Bar tile) instead of running flush edge to
39
- * edge. The registered inset covers gutter + bar + gutter, so windows
39
+ * edge. The registered inset covers gutter + bar + gutter, so panes
40
40
  * keep the same even gap to the bar that the bar keeps to the shell. */
41
41
  floating?: boolean;
42
42
  /** Pinned launchers — click spawns that app even when it isn't running. */
43
43
  pinned?: { appId: string; title: string; icon?: Component<IconProps> | null }[];
44
- /** Show the open/minimized window tabs (default true). Hidden, windows
45
- * stay reachable through the command bar's `windows` view. */
44
+ /** Show the open/minimized pane tabs (default true). Hidden, panes
45
+ * stay reachable through the command bar's `panes` view. */
46
46
  showWindows?: boolean;
47
- /** Include the built-in `windows` command in the command bar (default
48
- * true) — the palette view that focuses/restores an open window. Distinct
47
+ /** Include the built-in `panes` command in the command bar (default
48
+ * true) — the palette view that focuses/restores an open pane. Distinct
49
49
  * from `showWindows`, which gates the tab strip and deliberately keeps
50
50
  * this view as the tabs' recovery path. Set false alongside
51
51
  * `showWorkspaces={false}` for a single-purpose shell whose palette shows
@@ -53,7 +53,7 @@
53
53
  showWindowsCommand?: boolean;
54
54
  /** Show the multi-workspace UI (default true). Hidden, both the numbered
55
55
  * switcher and the command bar's `workspace` view go away — a
56
- * single-workspace shell. `wm.addWorkspace()` still works for consumers
56
+ * single-workspace shell. `pm.addWorkspace()` still works for consumers
57
57
  * who drive workspaces themselves. */
58
58
  showWorkspaces?: boolean;
59
59
  /** Extra root commands appended to the built-in ones. */
@@ -78,11 +78,11 @@
78
78
  end?: Snippet;
79
79
  } = $props();
80
80
 
81
- const wm = getWindowManager();
81
+ const pm = getPaneManager();
82
82
  const notif = getNotifications();
83
83
 
84
84
  // Register our reserved chrome on whichever edge we dock to: maximize stops
85
- // at the bar, and drag containment keeps window bars reachable beside it
85
+ // at the bar, and drag containment keeps pane bars reachable beside it
86
86
  // (not hidden behind). Floating bars reserve their gutter on BOTH sides —
87
87
  // measured from the rendered offset, so it tracks the live --era-gap value
88
88
  // across density changes (barHeight moves with it, re-running this).
@@ -99,43 +99,43 @@
99
99
  : (host?.clientHeight ?? 0) - ref.offsetTop - ref.offsetHeight;
100
100
  inset = barHeight + Math.max(0, gutter) * 2;
101
101
  }
102
- wm.insets[edge] = inset;
102
+ pm.insets[edge] = inset;
103
103
  return () => {
104
- wm.insets[edge] = 0;
104
+ pm.insets[edge] = 0;
105
105
  };
106
106
  });
107
107
 
108
108
  // The command bar owns everything the old spotlight overlay did, and more:
109
- // every registered app launches from the root view, `windows` drills into
110
- // the open windows (focus/restore — the recovery path when the window tabs
109
+ // every registered app launches from the root view, `panes` drills into
110
+ // the open panes (focus/restore — the recovery path when the pane tabs
111
111
  // are hidden), and `workspace` drills into numbered workspaces + `new`.
112
112
  // Consumer `commands` append after the built-ins (merged in a thin outer
113
- // derived, so a consumer-commands change doesn't rebuild the wm closures
113
+ // derived, so a consumer-commands change doesn't rebuild the pm closures
114
114
  // and vice-versa).
115
115
  const builtinItems = $derived.by((): CommandBarItem[] => {
116
- const items: CommandBarItem[] = Object.values(wm.apps).map((app) => ({
116
+ const items: CommandBarItem[] = Object.values(pm.apps).map((app) => ({
117
117
  value: `app:${app.id}`,
118
118
  label: app.title,
119
119
  icon: app.icon ?? undefined,
120
120
  keywords: [app.id],
121
121
  sublabel: 'launch',
122
- onSelect: () => void wm.open(app.id)
122
+ onSelect: () => void pm.open(app.id)
123
123
  }));
124
124
  // Each built-in view is assembled only when shown, so a hidden view costs
125
- // nothing and takes no reactive dependency on its wm slice.
125
+ // nothing and takes no reactive dependency on its pm slice.
126
126
  if (showWindowsCommand) {
127
127
  items.push({
128
- value: 'windows',
129
- label: 'windows',
128
+ value: 'panes',
129
+ label: 'panes',
130
130
  icon: AppWindowIcon,
131
- sublabel: 'focus an open window',
132
- placeholder: 'which window?',
133
- items: wm.windows.map((w) => ({
131
+ sublabel: 'focus an open pane',
132
+ placeholder: 'which pane?',
133
+ items: pm.panes.map((w) => ({
134
134
  value: `win:${w.id}`,
135
135
  label: w.title,
136
136
  icon: w.icon ?? undefined,
137
137
  sublabel: w.state === 'minimized' ? 'minimized' : undefined,
138
- onSelect: () => wm.focus(w.id)
138
+ onSelect: () => pm.focus(w.id)
139
139
  }))
140
140
  });
141
141
  }
@@ -147,17 +147,17 @@
147
147
  sublabel: 'switch / create',
148
148
  placeholder: 'which workspace?',
149
149
  items: [
150
- ...wm.workspaces.map((ws, i) => ({
150
+ ...pm.workspaces.map((ws, i) => ({
151
151
  value: `ws:${ws.id}`,
152
152
  label: `${i + 1} ${ws.name}`,
153
153
  keywords: [String(i + 1)],
154
- onSelect: () => wm.switchWorkspace(ws.id)
154
+ onSelect: () => pm.switchWorkspace(ws.id)
155
155
  })),
156
156
  {
157
157
  value: 'ws:new',
158
158
  label: 'new',
159
159
  sublabel: 'create a workspace',
160
- onSelect: () => void wm.addWorkspace()
160
+ onSelect: () => void pm.addWorkspace()
161
161
  }
162
162
  ]
163
163
  });
@@ -212,27 +212,27 @@
212
212
  {#if pinned.length}
213
213
  <div class="flex shrink-0 items-center gap-(--era-gap)">
214
214
  {#each pinned as p (p.appId)}
215
- <Button icon size="default" aria-label={p.title} onclick={() => wm.open(p.appId)}>
215
+ <Button icon size="default" aria-label={p.title} onclick={() => pm.open(p.appId)}>
216
216
  {#if p.icon}{@const Icon = p.icon}<Icon class="size-(--era-h-xs)" />{/if}
217
217
  </Button>
218
218
  {/each}
219
219
  </div>
220
220
  {/if}
221
221
 
222
- <!-- Running windows on the active workspace: real Buttons — `active` latches
223
- the focused window's pressed face (surface-aware for free), click
222
+ <!-- Running panes on the active workspace: real Buttons — `active` latches
223
+ the focused pane's pressed face (surface-aware for free), click
224
224
  focuses / minimises. Optional: hide them and use the command bar's
225
- `windows` view instead. -->
225
+ `panes` view instead. -->
226
226
  {#if showWindows}
227
227
  <div
228
228
  class="scrollbar-none flex max-w-[40%] min-w-0 shrink-0 items-center gap-(--era-gap) overflow-x-auto"
229
229
  >
230
- {#each wm.activeWindows as win (win.id)}
230
+ {#each pm.activePanes as win (win.id)}
231
231
  <Button
232
- active={wm.focusedId === win.id && win.state !== 'minimized'}
233
- onclick={() => wm.toggleMinimize(win.id)}
232
+ active={pm.focusedId === win.id && win.state !== 'minimized'}
233
+ onclick={() => pm.toggleMinimize(win.id)}
234
234
  onauxclick={(e: MouseEvent) => {
235
- if (e.button === 1) wm.close(win.id);
235
+ if (e.button === 1) pm.close(win.id);
236
236
  }}
237
237
  onpointerdown={(e: PointerEvent) => {
238
238
  // Cancel middle pointerdown so autoscroll never engages.
@@ -250,16 +250,16 @@
250
250
  <!-- Numbered workspace switcher, term-style on the right — appears only
251
251
  once there's something to switch between (and only when the bar exposes
252
252
  workspaces at all). -->
253
- {#if showWorkspaces && wm.workspaces.length > 1}
253
+ {#if showWorkspaces && pm.workspaces.length > 1}
254
254
  <div class="flex shrink-0 items-center gap-(--era-gap)">
255
- {#each wm.workspaces as ws, i (ws.id)}
255
+ {#each pm.workspaces as ws, i (ws.id)}
256
256
  <Button
257
257
  icon
258
258
  size="default"
259
- active={wm.activeWorkspaceId === ws.id}
259
+ active={pm.activeWorkspaceId === ws.id}
260
260
  title={ws.name}
261
261
  aria-label="Switch to workspace {i + 1} ({ws.name})"
262
- onclick={() => wm.switchWorkspace(ws.id)}
262
+ onclick={() => pm.switchWorkspace(ws.id)}
263
263
  >
264
264
  {i + 1}
265
265
  </Button>
@@ -8,7 +8,7 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
8
8
  position?: 'top' | 'bottom';
9
9
  /** Float the bar on the desktop with a uniform --era-gap gutter (fully
10
10
  * rounded, borderless — a Bar tile) instead of running flush edge to
11
- * edge. The registered inset covers gutter + bar + gutter, so windows
11
+ * edge. The registered inset covers gutter + bar + gutter, so panes
12
12
  * keep the same even gap to the bar that the bar keeps to the shell. */
13
13
  floating?: boolean;
14
14
  /** Pinned launchers — click spawns that app even when it isn't running. */
@@ -17,11 +17,11 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
17
17
  title: string;
18
18
  icon?: Component<IconProps> | null;
19
19
  }[];
20
- /** Show the open/minimized window tabs (default true). Hidden, windows
21
- * stay reachable through the command bar's `windows` view. */
20
+ /** Show the open/minimized pane tabs (default true). Hidden, panes
21
+ * stay reachable through the command bar's `panes` view. */
22
22
  showWindows?: boolean;
23
- /** Include the built-in `windows` command in the command bar (default
24
- * true) — the palette view that focuses/restores an open window. Distinct
23
+ /** Include the built-in `panes` command in the command bar (default
24
+ * true) — the palette view that focuses/restores an open pane. Distinct
25
25
  * from `showWindows`, which gates the tab strip and deliberately keeps
26
26
  * this view as the tabs' recovery path. Set false alongside
27
27
  * `showWorkspaces={false}` for a single-purpose shell whose palette shows
@@ -29,7 +29,7 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
29
29
  showWindowsCommand?: boolean;
30
30
  /** Show the multi-workspace UI (default true). Hidden, both the numbered
31
31
  * switcher and the command bar's `workspace` view go away — a
32
- * single-workspace shell. `wm.addWorkspace()` still works for consumers
32
+ * single-workspace shell. `pm.addWorkspace()` still works for consumers
33
33
  * who drive workspaces themselves. */
34
34
  showWorkspaces?: boolean;
35
35
  /** Extra root commands appended to the built-in ones. */