Review of "NexStage — Stage Manager for GNOME" version 1.0.0 (1)

Details Page Preview

NexStage brings the macOS Stage Manager experience to your GNOME desktop. A sidebar of live window thumbnails sits at the screen edge, showing your open apps with real-time content previews. Click to focus, scroll to navigate, drag to reorder, right-click for options. Layouts: Arc carousel (signature curved look) or Vertical list. Position: left, right, or bottom. Modes: Raise-only (focus without minimizing) or Minimize-others (single-window stage, classic Stage Manager feel). Live thumbnails with real-time content. Active app highlighted, inactive cards lean for depth. Optional badges (window count per app), tooltips, close buttons. Multi-monitor: per-display or follow-focus. Reserve space (maximized windows avoid sidebar). Persistent or auto-hide. 4-tab Adw preferences window: Layout, Behavior, Animation, Shortcuts. Every detail configurable. Wayland & X11. Pure GJS/Clutter. No Electron, no npm. GPL-3.0+.

Extension Homepage
https://github.com/NexVar/NexStage

No comments.

FAQ

Files

Note: Binary files aren't shown on the web site. To see all files, please download the extension zipfile.

Shexli (experimental) error 2 warning 4

Shexli found 6 issues that may need reviewer attention.

EGO-C49-003 error

extensions targeting GNOME 49 must not call maximize or unmaximize with Meta.MaximizeFlags

This extension explicitly targets GNOME Shell 49 but still passes `Meta.MaximizeFlags` to `maximize()` or `unmaximize()`.

Meta.Window

  • extension.js:551
    win.unmaximize?.(Meta.MaximizeFlags.BOTH)

EGO-C49-004 error

extensions targeting GNOME 49 must not call Meta.Window.get_maximized

This extension explicitly targets GNOME Shell 49 but still calls removed `Meta.Window.get_maximized()`.

Meta.Window

  • extension.js:522
    win.get_maximized?.()

EGO-L-002 warning

objects created by extension should be destroyed in disable()

Objects assigned in `enable()` are missing matching `.destroy()` calls in `disable()` or its helper methods.

Destroy all objects

  • extension.js:1825
    this._tooltip = tooltip
  • extension.js:1232
    container

EGO-L-003 warning

signals connected by extension should be disconnected in disable()

Signals assigned in `enable()` are missing matching disconnect calls in `disable()` or its helper methods.

Disconnect all signals

  • extension.js:2034
    this._contextMenuClickId = clickId
  • extension.js:459
    this._edge.connect('enter-event',  () => { if (!this._isVisible) this._showPanel(); })
  • extension.js:460
    this._panel.connect('enter-event', () => { if (!this._dragging) this._cancelHide(); })
  • extension.js:461
    this._panel.connect('leave-event', () => { if (this._isVisible && !this._dragging) this._startHide(); })
  • extension.js:463
            this._panel.connect('scroll-event', (_a, event) => {
                if (this._dragging) return Clutter.EVENT_STOP;
                if (this._layoutMode === 'vertical') {
                    const dir = event.get_scroll_direction();
                    if (dir === Clutter.ScrollDirection.DOWN) this._scrollV

EGO-L-004 warning

main loop sources should be removed in disable()

Main loop sources assigned in `enable()` are missing matching removals in `disable()` or its helper methods.

Remove main loop sources

  • extension.js:105
                    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 130, () => {
                        this._refresh();
                        this._containers.forEach(c => {
                            c.opacity = 0;
                            c.ease({ opacity: 255, duration: 200, mode: Clutter.AnimationMode.EASE_OUT_QUAD
  • extension.js:1217
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:149
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, 800, () => {
                this._checkPersistence();
                return GLib.SOURCE_REMOVE;
            })
  • extension.js:1549
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:1717
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:1749
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:192
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, 80, () => {
                    if (this._containers.length > prevCount && this._isVisible) {
                        const newC = this._containers[this._containers.length - 1];
                        if (newC) {
                            newC.set_scale(0.5, 0.
  • extension.js:1939
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:1948
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:1962
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:1966
    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { this._refresh(); return GLib.SOURCE_REMOVE; })
  • extension.js:2065
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, () => {
                const focused = global.display.get_focus_window();
                if (focused) {
                    const app = tracker.get_window_app(focused);
                    if (app) {
                        const fid = app.get_id();
                   
  • extension.js:2234
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, Math.round((40 + i * 50) * this._animSpeed), () => {
                    c.ease({
                        x: origX, y: origY,
                        opacity: 255,
                        scale_x: c._isActive ? 1.0 : this._inactiveScale,
                        scale_
  • extension.js:2263
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, (total - 1 - i) * 25, () => {
                    c.ease({
                        x: exitX, y: exitY,
                        opacity: 0,
                        scale_x: 0.8, scale_y: 0.8,
                        duration: 200,
                        mode: Clutter.Anim
  • extension.js:2276
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, Math.max(80, total * 25), () => {
                this._panel.ease({ x: geo.hidX, y: geo.hidY, duration: 200, mode: Clutter.AnimationMode.EASE_IN_QUAD });
                return GLib.SOURCE_REMOVE;
            })
  • extension.js:235
                    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => {
                        this._refresh();
                        return GLib.SOURCE_REMOVE;
                    })
  • extension.js:486
                    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
                        this._adjustWindowForReserve(win);
                        return GLib.SOURCE_REMOVE;
                    })
  • extension.js:507
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => {
                    this._adjustWindowForReserve(win);
                    return GLib.SOURCE_REMOVE;
                })
  • extension.js:556
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => {
                win.move_resize_frame(false, x, y, w, h);
                this._adjustingReserve = false;
                return GLib.SOURCE_REMOVE;
            })
  • extension.js:793
                    GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => {
                        this._refresh();
                        return GLib.SOURCE_REMOVE;
                    })
  • extension.js:90
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => {
                    this._checkPersistence();
                    return GLib.SOURCE_REMOVE;
                })

EGO-L-007 warning

main loop sources should be removed before being recreated

Main loop sources should be removed before creating a new source on the same field.

Remove main loop sources

  • extension.js:839
            this._physicsId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => {
                this._offset   += this._velocity;
                this._velocity *= SCROLL_FRICTION;
                const max = Math.max(0, this._groups.length - 1);
                if (this._offset < 0)   { this._offset = 0;   this._vel

All Versions

Version Status
1.0.0 (1) Unreviewed

Previous Reviews on this Version

mertdlkr posted a review