From de871a2562133fe537cbcc8b08a527b9d192c478 Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Sun, 7 Jun 2026 21:02:44 +0900 Subject: [PATCH 1/5] Expand README Usage section: command palette and project layouts Add a dedicated Usage section covering the command palette (including opening a directory as a project by typing a path) and project layouts. Move Configuration above Usage and replace the defaults table with a link to the source. Keep Configuration and Usage distinct with minimal overlap. --- README.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7892c08..38af6ee 100644 --- a/README.md +++ b/README.md @@ -78,17 +78,7 @@ https://github.com/user-attachments/assets/1486ed55-e653-43ce-98aa-232a61d234a7 Macterm reads your `~/.config/ghostty/config` on launch — themes, fonts, palettes, keybinds, and everything else Ghostty supports works the same here. See the [Ghostty option reference](https://ghostty.org/docs/config/reference) for the full list of available settings. If your config is elsewhere, set the path in **Settings → General → Ghostty Config**. -Macterm ships a thin defaults layer on top of Ghostty's own defaults. These are the values that differ: - -| Option | Macterm default | Ghostty default | -| -------------------------------------------------------------------------------------- | --------------- | --------------- | -| [`theme`](https://ghostty.org/docs/config/reference#theme) | `Rose Pine` | _(none)_ | -| [`font-size`](https://ghostty.org/docs/config/reference#font-size) | `16` | `12` | -| [`window-padding-x`](https://ghostty.org/docs/config/reference#window-padding-x) | `16` | `2` | -| [`window-padding-y`](https://ghostty.org/docs/config/reference#window-padding-y) | `16` | `2` | -| [`macos-option-as-alt`](https://ghostty.org/docs/config/reference#macos-option-as-alt) | `true` | `false` | - -Add any of these to your Ghostty config to override them. Macterm-specific settings (window opacity, blur style, quick terminal size, hotkeys) live in **Macterm → Settings**. +Macterm ships a thin layer of [first-launch defaults](https://github.com/thdxg/macterm/blob/main/Macterm/Config/MactermConfig.swift#L43-L47) (Rose Pine, a 16pt font, some padding) on top of Ghostty's own — add any of those keys to your Ghostty config to override them. Macterm-specific settings (window opacity, blur style, quick terminal size, hotkeys) live in **Macterm → Settings**. A few settings are overridden because Macterm handles that chrome itself: `background-opacity` and `background-blur` are forced to `0` (use **Settings → General → Window** instead), and titlebar, window decoration, split-divider, and quick-terminal settings are ignored. @@ -96,12 +86,24 @@ Ghostty keybinds work normally unless they conflict with a Macterm shortcut — Shell integration works standalone — no Ghostty.app needed. The one exception is the `ssh-env`, `ssh-terminfo`, and `path` features, which require the `ghostty` CLI; install Ghostty.app to enable them. The `ssh` features additionally need a Ghostty new enough to provide the `+ssh` action (Ghostty 1.4.0 / tip); against an older install they stay disabled and `ssh` runs normally. -## Declarative Project Layouts +## Usage + +### Command Palette + +Press `⌘P` to open the command palette — the fastest way to drive Macterm without leaving the keyboard. It searches across everything in one list: + +- **Commands** — split, close, and focus panes; create, rename, and reorder tabs; toggle window chrome; and more. Each row shows its current keybind. +- **Projects** — jump to any open project, or rename and remove them. + +To open a directory as a project, just start typing a path (anything beginning with `/` or `~`). The palette switches to path mode and autocompletes directories as you go; press return on a match to open it (or switch to it, if it's already a project). + +### Project Layouts -You can declare a project's tabs, split layout, and the process each pane runs in a `.macterm/layout.yaml` file at the project root. When a project has a layout file, Macterm builds its workspace from it on open — the committed layout takes precedence over any restored session for that project. You can also run **Save layout** from the command palette to write your current workspace out, or **Apply layout** to re-apply the file on demand. +Declare a project's tabs, split layout, and the process each pane runs in a committable `.macterm/layout.yaml` at the project root. When a project has a layout file, Macterm builds its workspace from it on open — the committed layout is the source of truth, taking precedence over any restored session for that project. Run **Save layout** from the palette to write your current workspace out, or **Apply layout** to re-apply the file on demand. ```yaml -name: "MyApp" # the project this layout is for (optional) +name: "MyApp" # the project name (optional; default to directory name) +shell: /bin/zsh # optional default shell for every pane tabs: # A single-pane tab is just a pane (no wrapper). - run: "npm run dev" @@ -121,11 +123,11 @@ tabs: second: {} # plain shell, no command ``` -A pane's `run` is typed into a normal shell (so you keep the prompt and history, and the pane survives when the command exits). The shell is the pane's `shell` if set, else the one from your Ghostty config. +A pane's `run` is typed into a normal shell, so you keep the prompt and history and the pane survives when the command exits. The shell is the per-pane `shell`, else the file-level `shell`, else the one from your Ghostty config. -**Save** records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (its foreground process — so a pane running `npm run dev` is saved with that `run:`, a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. If a pane is sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`), Save records it as `shell:`; a pane in your default shell records none, so the layout stays portable. If you apply a layout whose `name:` doesn't match the current project, Macterm asks you to confirm first. +**Save** records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. A pane sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`) is saved with that `shell:`; a pane in your default shell records none, so the layout stays portable. Applying a layout whose `name:` doesn't match the current project prompts for confirmation first. -**Apply** reconciles the live workspace toward the file with minimal disruption: a pane already running the declared `run` in the same directory is kept (only resized if its split ratio changed), and only panes that genuinely deviate are restarted or closed. When an apply would terminate any pane, Macterm asks for confirmation first. An invalid layout file is reported and never applied. +**Apply** reconciles the live workspace toward the file with minimal disruption: a pane already running the declared `run` in the same directory is kept (only resized if its split ratio changed), and only panes that genuinely deviate are restarted or closed. When an apply would terminate any pane, Macterm asks first. An invalid layout file is reported and never applied. **Editor support.** A [JSON schema](schemas/layout.schema.json) describes the format, giving completion and validation in editors that use the YAML Language Server (VS Code, Neovim, Zed, …). Saved files include the modeline that wires it up automatically; for a hand-authored file, add it to the top yourself: From 563eb8cdcf9e30d679ba70da6f6a393dda30a123 Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Sun, 7 Jun 2026 21:06:25 +0900 Subject: [PATCH 2/5] Fix README: there is no top-level shell field in the layout file shell is per-pane only; a pane with no shell uses the login shell. The file-level shell field was removed in #58. --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 38af6ee..3572d42 100644 --- a/README.md +++ b/README.md @@ -78,13 +78,13 @@ https://github.com/user-attachments/assets/1486ed55-e653-43ce-98aa-232a61d234a7 Macterm reads your `~/.config/ghostty/config` on launch — themes, fonts, palettes, keybinds, and everything else Ghostty supports works the same here. See the [Ghostty option reference](https://ghostty.org/docs/config/reference) for the full list of available settings. If your config is elsewhere, set the path in **Settings → General → Ghostty Config**. -Macterm ships a thin layer of [first-launch defaults](https://github.com/thdxg/macterm/blob/main/Macterm/Config/MactermConfig.swift#L43-L47) (Rose Pine, a 16pt font, some padding) on top of Ghostty's own — add any of those keys to your Ghostty config to override them. Macterm-specific settings (window opacity, blur style, quick terminal size, hotkeys) live in **Macterm → Settings**. +Macterm ships a thin layer of [first-launch defaults](https://github.com/thdxg/macterm/blob/main/Macterm/Config/MactermConfig.swift#L43-L47) on top of Ghostty's own — add any of those keys to your Ghostty config to override them. Macterm-specific settings (window opacity, blur style, quick terminal size, hotkeys) live in **Macterm → Settings**. A few settings are overridden because Macterm handles that chrome itself: `background-opacity` and `background-blur` are forced to `0` (use **Settings → General → Window** instead), and titlebar, window decoration, split-divider, and quick-terminal settings are ignored. Ghostty keybinds work normally unless they conflict with a Macterm shortcut — on conflict, Macterm wins. Every Macterm shortcut is rebindable in **Settings → Keymaps**. Note that Ghostty app-level actions (`new_split`, `new_tab`, etc.) do nothing in Macterm; use Macterm's own keybinds for those. -Shell integration works standalone — no Ghostty.app needed. The one exception is the `ssh-env`, `ssh-terminfo`, and `path` features, which require the `ghostty` CLI; install Ghostty.app to enable them. The `ssh` features additionally need a Ghostty new enough to provide the `+ssh` action (Ghostty 1.4.0 / tip); against an older install they stay disabled and `ssh` runs normally. +The `ssh-env`, `ssh-terminfo`, and `path` features require the `ghostty` CLI; install Ghostty.app to enable them. The `ssh` features additionally need a Ghostty new enough to provide the `+ssh` action (Ghostty 1.4.0 / tip); against an older install they stay disabled and `ssh` runs normally. ## Usage @@ -103,7 +103,6 @@ Declare a project's tabs, split layout, and the process each pane runs in a comm ```yaml name: "MyApp" # the project name (optional; default to directory name) -shell: /bin/zsh # optional default shell for every pane tabs: # A single-pane tab is just a pane (no wrapper). - run: "npm run dev" @@ -123,7 +122,7 @@ tabs: second: {} # plain shell, no command ``` -A pane's `run` is typed into a normal shell, so you keep the prompt and history and the pane survives when the command exits. The shell is the per-pane `shell`, else the file-level `shell`, else the one from your Ghostty config. +A pane's `run` is typed into a normal shell, so you keep the prompt and history and the pane survives when the command exits. The shell is the pane's `shell` if set, else your login shell. **Save** records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. A pane sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`) is saved with that `shell:`; a pane in your default shell records none, so the layout stays portable. Applying a layout whose `name:` doesn't match the current project prompts for confirmation first. From 4c6179800ee47a2a22023c36a42969625d365881 Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Sun, 7 Jun 2026 21:11:23 +0900 Subject: [PATCH 3/5] Refine README layout docs: inline schema modeline into example, tighten prose --- README.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3572d42..bd7734c 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,14 @@ To open a directory as a project, just start typing a path (anything beginning w Declare a project's tabs, split layout, and the process each pane runs in a committable `.macterm/layout.yaml` at the project root. When a project has a layout file, Macterm builds its workspace from it on open — the committed layout is the source of truth, taking precedence over any restored session for that project. Run **Save layout** from the palette to write your current workspace out, or **Apply layout** to re-apply the file on demand. ```yaml -name: "MyApp" # the project name (optional; default to directory name) +# .../myapp/.macterm/layout.yaml + +# yaml-language-server: $schema=https://raw.githubusercontent.com/thdxg/macterm/main/schemas/layout.schema.json +name: "MyApp" # the project name (optional; defaults to directory name) tabs: - # A single-pane tab is just a pane (no wrapper). + # A single-pane tab - run: "npm run dev" - # A tab can carry a `name`, and splits nest under `split:`. + # A tab with custom name and splits - name: "Dev" split: direction: horizontal # horizontal | vertical @@ -114,25 +117,19 @@ tabs: first: cwd: "./api" # project-relative working directory run: "npm run dev" # typed into the pane's shell on launch - shell: /bin/zsh # optional per-pane shell + shell: /bin/zsh # shell (optional; defaults to login shell) second: split: direction: vertical first: { cwd: "./api", run: "npm test -- --watch" } - second: {} # plain shell, no command + second: {} # plain shell pane ``` A pane's `run` is typed into a normal shell, so you keep the prompt and history and the pane survives when the command exits. The shell is the pane's `shell` if set, else your login shell. -**Save** records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. A pane sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`) is saved with that `shell:`; a pane in your default shell records none, so the layout stays portable. Applying a layout whose `name:` doesn't match the current project prompts for confirmation first. - -**Apply** reconciles the live workspace toward the file with minimal disruption: a pane already running the declared `run` in the same directory is kept (only resized if its split ratio changed), and only panes that genuinely deviate are restarted or closed. When an apply would terminate any pane, Macterm asks first. An invalid layout file is reported and never applied. - -**Editor support.** A [JSON schema](schemas/layout.schema.json) describes the format, giving completion and validation in editors that use the YAML Language Server (VS Code, Neovim, Zed, …). Saved files include the modeline that wires it up automatically; for a hand-authored file, add it to the top yourself: +**Save layout** command records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. A pane sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`) is saved with that `shell:`; a pane in your default shell records none, so the layout stays portable. Applying a layout whose `name:` doesn't match the current project prompts for confirmation first. -```yaml -# yaml-language-server: $schema=https://raw.githubusercontent.com/thdxg/macterm/main/schemas/layout.schema.json -``` +**Apply layout** command reconciles the live workspace toward the file with minimal disruption: a pane already running the declared `run` in the same directory is kept (only resized if its split ratio changed), and only panes that genuinely deviate are restarted or closed. When an apply would terminate any pane, Macterm asks first. An invalid layout file is reported and not applied. ## Contributing From 25d953399c40b044ef8ea39c265131b1aaff5dd3 Mon Sep 17 00:00:00 2001 From: Ethan Lee Date: Sun, 7 Jun 2026 21:19:54 +0900 Subject: [PATCH 4/5] improve readme format --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index bd7734c..9f632ca 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,12 @@ - **Vertical Project Sidebar**: Native macOS sidebar for organizing projects and tabs vertically. - **Persistent Multiplexing**: Projects, tabs, and panes are saved and restored automatically on relaunch. -- **Ghostty Config Compatibility**: Macterm reads your existing `~/.config/ghostty/config`. Theme, font, palette, keybinds — all of it just works. -- **Keyboard-first Navigation**: Customizable keybinds for navigating projects, tabs, and panes. +- **Declarative Layouts**: Define a `.macterm/layout.yaml` describing each project's tabs, splits, and the process every pane runs; apply or save it from the command palette. +- **Ghostty Config Compatibility**: Macterm reads your existing Ghostty config. Theme, font, palette, keybinds — all of it just works. - **Command Palette**: Versatile command palette to interact with multiplexing and manage projects - **Quick terminal**: Global terminal accessible from anywhere with a hotkey. -- **Declarative Layouts**: Commit a `.macterm/layout.yaml` describing each project's tabs, splits, and the process every pane runs; apply or save it from the command palette. - **Smart Tab Naming**: Tabs name themselves after the program running in the pane, making them easily identifiable in the sidebar. +- **Keyboard-driven Control**: Customizable keybinds for many actions including navigating projects, tabs, and panes. ## Install @@ -45,7 +45,7 @@ brew install --cask thdxg/tap/macterm ``` -The cask strips the Gatekeeper quarantine xattr on install, so the app launches without any extra prompts. Updates are delivered via Sparkle inside the app. +> The cask strips the Gatekeeper quarantine xattr on install, so the app launches without any extra prompts. ### From Releases @@ -99,7 +99,7 @@ To open a directory as a project, just start typing a path (anything beginning w ### Project Layouts -Declare a project's tabs, split layout, and the process each pane runs in a committable `.macterm/layout.yaml` at the project root. When a project has a layout file, Macterm builds its workspace from it on open — the committed layout is the source of truth, taking precedence over any restored session for that project. Run **Save layout** from the palette to write your current workspace out, or **Apply layout** to re-apply the file on demand. +Declare a project's tabs, split layout, and the process each pane runs in a `.macterm/layout.yaml` file at the project root. When a project has a layout file, Macterm builds its workspace from it on open — the committed layout is the source of truth, taking precedence over any restored session for that project. Run **Save layout** from the palette to write your current workspace out, or **Apply layout** to re-apply the file on demand. ```yaml # .../myapp/.macterm/layout.yaml @@ -127,9 +127,11 @@ tabs: A pane's `run` is typed into a normal shell, so you keep the prompt and history and the pane survives when the command exits. The shell is the pane's `shell` if set, else your login shell. -**Save layout** command records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. A pane sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`) is saved with that `shell:`; a pane in your default shell records none, so the layout stays portable. Applying a layout whose `name:` doesn't match the current project prompts for confirmation first. +Related commands: -**Apply layout** command reconciles the live workspace toward the file with minimal disruption: a pane already running the declared `run` in the same directory is kept (only resized if its split ratio changed), and only panes that genuinely deviate are restarted or closed. When an apply would terminate any pane, Macterm asks first. An invalid layout file is reported and not applied. +- **Save layout**: Records the project `name:`, each tab's split layout, every pane's working directory, and the command each pane is currently running (a pane idle at a prompt gets none). The captured command is the resolved process invocation (e.g. `node …/npm-cli.js run dev`), which you can tidy by hand. A pane sitting in a non-default shell (one you launched yourself, like `zsh` from your usual `nu`) is saved with that `shell:`; a pane in your default shell records none, so the layout stays portable. Applying a layout whose `name:` doesn't match the current project prompts for confirmation first. + +- **Apply layout**: Reconciles the live workspace toward the file with minimal disruption: a pane already running the declared `run` in the same directory is kept (only resized if its split ratio changed), and only panes that genuinely deviate are restarted or closed. When an apply would terminate any pane, Macterm asks first. An invalid layout file is reported and not applied. ## Contributing From 30a9b034bbff5028d8373a5e961718bdd33a3ba7 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 7 Jun 2026 05:31:22 -0700 Subject: [PATCH 5/5] fix: hide titlebar background view in native fullscreen to remove top bar artifact --- Macterm/Views/WindowAppearance.swift | 87 ++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/Macterm/Views/WindowAppearance.swift b/Macterm/Views/WindowAppearance.swift index 6a97d62..e7d489b 100644 --- a/Macterm/Views/WindowAppearance.swift +++ b/Macterm/Views/WindowAppearance.swift @@ -226,8 +226,10 @@ enum WindowAppearance { // Override the titlebar's private background layer so its color // matches the terminal background (or stays transparent when the // window is). Without this the titlebar paints its own material - // and you get a visible seam at y=titlebarHeight. - syncTitlebar(window: window, isTransparent: effectiveTransparent) + // and you get a visible seam at y=titlebarHeight. Native fullscreen + // gets forced opaque above, but its separate titlebar window still + // needs the background hidden or it draws a top-edge bar. + syncTitlebar(window: window, hideBackground: effectiveTransparent || forceOpaque) } /// Update the inactive-glass tint when the window gains/loses key status. @@ -307,9 +309,13 @@ enum WindowAppearance { return window.value(forKey: "_cornerRadius") as? CGFloat } - private static func syncTitlebar(window: NSWindow, isTransparent: Bool) { - guard let container = titlebarContainer(in: window) else { return } + private static func syncTitlebar(window: NSWindow, hideBackground: Bool) { + for container in titlebarContainers(for: window) { + syncTitlebarContainer(container, hideBackground: hideBackground) + } + } + private static func syncTitlebarContainer(_ container: NSView, hideBackground: Bool) { if let titlebarView = container.firstDescendant(withClassName: "NSTitlebarView") { titlebarView.wantsLayer = true // On Tahoe, the NavigationSplitView's sidebar is a liquid-glass @@ -322,16 +328,75 @@ enum WindowAppearance { } // NSTitlebarBackgroundView has subviews that force their own background - // colors; hide it only when transparent, so the default opaque-mode - // chrome stays intact. - container.firstDescendant(withClassName: "NSTitlebarBackgroundView")?.isHidden = isTransparent + // colors; hide it when transparent or when native fullscreen's + // companion titlebar window would otherwise paint a top-edge band. + container.firstDescendant(withClassName: "NSTitlebarBackgroundView")?.isHidden = hideBackground + } + + private static func titlebarContainers(for window: NSWindow) -> [NSView] { + var containers: [NSView] = [] + appendTitlebarContainer(in: window, to: &containers) + + guard window.styleMask.contains(.fullScreen) else { return containers } + + for fullscreenWindow in fullscreenTitlebarWindows(for: window) { + appendTitlebarContainer(in: fullscreenWindow, to: &containers) + } + return containers + } + + private static func appendTitlebarContainer(in window: NSWindow, to containers: inout [NSView]) { + guard let container = titlebarContainer(in: window) else { return } + guard !containers.contains(where: { $0 === container }) else { return } + containers.append(container) + } + + private static func fullscreenTitlebarWindows(for window: NSWindow) -> [NSWindow] { + var windows: [NSWindow] = [] + + for childWindow in window.childWindows ?? [] { + appendWindow(childWindow, to: &windows) + } + for accessory in window.titlebarAccessoryViewControllers { + guard let accessoryWindow = accessory.view.window else { continue } + appendWindow(accessoryWindow, to: &windows) + } + for appWindow in NSApplication.shared.windows { + appendWindow(appWindow, to: &windows) + } + + return windows.filter { candidate in + guard candidate !== window else { return false } + guard String(describing: type(of: candidate)) == "NSToolbarFullScreenWindow" else { return false } + guard titlebarContainer(in: candidate) != nil else { return false } + return fullscreenTitlebarWindow(candidate, belongsTo: window) + } + } + + private static func appendWindow(_ candidate: NSWindow, to windows: inout [NSWindow]) { + guard !windows.contains(where: { $0 === candidate }) else { return } + windows.append(candidate) + } + + private static func fullscreenTitlebarWindow(_ candidate: NSWindow, belongsTo window: NSWindow) -> Bool { + if window.childWindows?.contains(where: { $0 === candidate }) == true { return true } + if let screen = window.screen, let candidateScreen = candidate.screen { + return screen === candidateScreen + } + if let screen = window.screen { + return candidate.frame.intersects(screen.frame) + } + if let candidateScreen = candidate.screen { + return window.frame.intersects(candidateScreen.frame) + } + return candidate.frame.intersects(window.frame) } private static func titlebarContainer(in window: NSWindow) -> NSView? { - // The titlebar container lives on the window's content view's root in - // normal mode, and on a separate NSToolbarFullScreenWindow in native - // fullscreen. We don't support native fullscreen tab bars, so the - // first path suffices for Macterm. + // The titlebar container lives on the window's content view's root. + // In native fullscreen AppKit hosts another copy on a private + // NSToolbarFullScreenWindow; callers discover that companion window + // separately and run this same guarded lookup against it. guard let contentView = window.contentView else { return nil } var root: NSView = contentView while let s = root.superview {