Workspace Layers & Tab Groups

Group projects into switchable layers and tabbed groups

Two ways to organize related projects in ~/.lattices/workspace.json:

  • Layers — switchable contexts that focus and tile windows
  • Tab groups — related projects as tabs within a single terminal window

Both features are configured in the same workspace config and work together.

Tab Groups

Tab groups let you bundle related projects as tabs within a single terminal session (requires tmux). This is useful when you have a family of projects — like an iOS app, macOS app, website, and API — that you think of as one logical unit.

Configuration

Add groups to ~/.lattices/workspace.json:

{
  "name": "my-setup",
  "groups": [
    {
      "id": "vox",
      "label": "Vox",
      "tabs": [
        { "path": "/Users/you/dev/vox-ios", "label": "iOS" },
        { "path": "/Users/you/dev/vox-macos", "label": "macOS" },
        { "path": "/Users/you/dev/vox-web", "label": "Website" },
        { "path": "/Users/you/dev/vox-api", "label": "API" }
      ]
    }
  ]
}

Each tab’s pane layout comes from its own .lattices.json — no changes to per-project configs.

How it works

  • Session name follows the pattern lattices-group-<id> (e.g. lattices-group-vox)
  • 1 group = 1 tmux session. Each tab is a tmux window, and each window gets its own panes from that project’s .lattices.json
  • You can still launch projects independently: cd vox-ios && lattices creates its own standalone session as before

Tab group fields

FieldTypeDescription
idstringUnique identifier for the group
labelstringDisplay name shown in the UI
tabsarrayList of tab definitions
tabs[].pathstringAbsolute path to project directory
tabs[].labelstring?Tab name (defaults to directory name)

CLI commands

lattices groups             # List all groups with status
lattices group <id>         # Launch or attach to a group
lattices tab <group> [tab]  # Switch tab by label or index

Examples:

lattices group vox       # Launch all Vox tabs
lattices tab vox iOS     # Switch to the iOS tab
lattices tab vox 0       # Switch to first tab (by index)

Tab groups appear above the project list in the menu bar panel. Each group row shows:

  • Status indicator (running/stopped)
  • Tab count badge
  • Expand/collapse to see individual tabs
  • Launch/Attach and Kill buttons
  • Per-tab “Go” buttons to switch and focus a specific tab

The command palette also includes group commands:

CommandDescription
Launch groupStart the group session
Attach groupFocus the running group session
Group: TabSwitch to a specific tab in a group
Kill group GroupTerminate the group session

Layers

Layers let you group projects into switchable contexts. Define two or three layers and switch between them. The target layer’s windows come to the front and tile into position; the previous layer’s windows fall behind.

All tmux sessions stay alive across switches. Nothing is detached or killed. Layers only control which windows are focused.

Configuration

Add layers to ~/.lattices/workspace.json:

{
  "name": "my-setup",
  "layers": [
    {
      "id": "web",
      "label": "Web",
      "projects": [
        { "path": "/Users/you/dev/frontend", "tile": "left" },
        { "path": "/Users/you/dev/api", "tile": "right" }
      ]
    },
    {
      "id": "mobile",
      "label": "Mobile",
      "projects": [
        { "path": "/Users/you/dev/ios-app", "tile": "left" },
        { "path": "/Users/you/dev/backend", "tile": "right" }
      ]
    }
  ]
}

App windows in layers

Layers aren’t limited to terminal sessions. You can include any application window by using the app, title, url, and launch fields instead of path:

{
  "name": "hudson",
  "layers": [
    {
      "id": "main",
      "label": "Main",
      "projects": [
        { "app": "Google Chrome", "title": "GitHub", "tile": "left" },
        { "app": "Vox", "tile": "top-right", "launch": "open -a Vox" },
        { "path": "/Users/you/dev/frontend", "tile": "bottom-right" }
      ]
    },
    {
      "id": "docs",
      "label": "Docs",
      "projects": [
        { "app": "Google Chrome", "url": "https://docs.example.com", "tile": "left" },
        { "app": "Notes", "title": "Sprint Notes", "tile": "right" }
      ]
    }
  ]
}

When switching to a layer, lattices matches windows by app name and optionally filters by title substring or url prefix. If launch is provided and no matching window is found, the command is executed to open the app.

Using groups in layers

Layer projects can reference a tab group instead of a single path. This lets you tile a whole group into a screen position:

{
  "name": "my-setup",
  "groups": [
    {
      "id": "vox",
      "label": "Vox",
      "tabs": [
        { "path": "/Users/you/dev/vox-ios", "label": "iOS" },
        { "path": "/Users/you/dev/vox-web", "label": "Website" }
      ]
    }
  ],
  "layers": [
    {
      "id": "main",
      "label": "Main",
      "projects": [
        { "group": "vox", "tile": "top-left" },
        { "path": "/Users/you/dev/design-system", "tile": "right" }
      ]
    }
  ]
}

When switching to this layer, lattices launches (or focuses) the “vox” group session and tiles it to the top-left quarter, alongside the design-system project on the right.

Layer fields

FieldTypeDescription
namestringWorkspace name (for your reference)
layersarrayList of layer definitions
layers[].idstringUnique identifier (e.g. "web")
layers[].labelstringDisplay name shown in the UI
layers[].projectsarrayProjects in this layer
projects[].pathstring?Absolute path to project directory
projects[].groupstring?Group ID (alternative to path)
projects[].appstring?Application name (for non-terminal windows)
projects[].titlestring?Window title substring to match
projects[].urlstring?URL prefix to match (browser windows)
projects[].launchstring?Shell command to launch the app if not found
projects[].tilestring?Tile position (optional, see below)

Each project entry must have either path, group, or app — pick one.

Tile values

Any tile position from the config reference works: left, right, top, bottom, top-left, top-right, bottom-left, bottom-right, left-third, center-third, right-third, maximize, center.

Switching layers

Four ways to switch:

MethodHow
HotkeyCmd+Option+1, Cmd+Option+2, Cmd+Option+3…
Layer barClick a layer pill in the menu bar panel
Command paletteSearch “Switch to Layer” in Cmd+Shift+M
CLIlattices layer <name|index>

When you switch to a layer:

  1. Each project’s window is raised and focused
  2. App windows are matched by app / title / url
  3. If a project isn’t running yet, it gets launched automatically
  4. Windows with a tile value are tiled to that position
  5. The previous layer’s windows stay open behind the new ones

The app remembers which layer was last active across restarts.

Named layer switching

You can switch layers by name from the CLI:

lattices layer hudson     # Switch to the layer named "hudson"
lattices layer 0          # Switch to the first layer (by index)

This is useful for scripting — you don’t need to know the index, just the layer’s id or label.

Window tagging

You can manually assign any window to a layer, even if it’s not declared in workspace.json. This is useful for ad-hoc windows that you want to move with a layer:

lattices window assign <wid> <layer>   # Tag a window to a layer
lattices window map                    # Show all window→layer assignments

Tagged windows behave like declared ones — they’re raised and tiled when their layer activates. Remove a tag by reassigning or with:

# Via the agent API
await daemonCall('window.removeLayer', { wid: 1234 })

Layer bezel

When you switch layers via hotkey, a translucent HUD pill appears briefly at the top of the screen showing the new layer’s name. This provides instant visual feedback without interrupting your flow.

Programmatic switching

Agents and scripts can switch layers via the agent API:

import { daemonCall } from '@lattices/cli'

// List available layers
const { layers, active } = await daemonCall('layers.list')
console.log(`Active: ${layers[active].label}`)

// Switch to a layer by index
await daemonCall('layer.switch', { index: 0 })

// Switch to a layer by name
await daemonCall('layer.switch', { name: 'hudson' })

The layer.switch call focuses and tiles all windows in the target layer, just like the hotkey or command palette. A layer.switched event is broadcast to all connected clients.

More methods in the Agent API reference.

Layer bar

When a workspace config is loaded, a layer bar appears between the header and search field in the menu bar panel:

 lattices  2 sessions              [↔] [⟳]
┌────────────────────────────────────────┐
│  ● Web          ○ Mobile               │
│  ⌥1             ⌥2                     │
└────────────────────────────────────────┘
 Search projects...
  • Active layer: filled green dot
  • Inactive layers: dim outline dot
  • Hotkey hints shown below each label

Layout examples

Single project

{
  "projects": [
    { "path": "/Users/you/dev/vox" }
  ]
}

No tile — just focuses the window wherever it is.

Two-project split

{
  "projects": [
    { "path": "/Users/you/dev/app", "tile": "left" },
    { "path": "/Users/you/dev/api", "tile": "right" }
  ]
}

Mixed: apps + terminals

{
  "projects": [
    { "app": "Google Chrome", "title": "GitHub", "tile": "left" },
    { "path": "/Users/you/dev/api", "tile": "right" }
  ]
}

Group + project

{
  "projects": [
    { "group": "vox", "tile": "left" },
    { "path": "/Users/you/dev/api", "tile": "right" }
  ]
}

Four quadrants

{
  "projects": [
    { "path": "/Users/you/dev/frontend", "tile": "top-left" },
    { "path": "/Users/you/dev/backend", "tile": "top-right" },
    { "path": "/Users/you/dev/mobile", "tile": "bottom-left" },
    { "path": "/Users/you/dev/infra", "tile": "bottom-right" }
  ]
}

Tips

  • Projects don’t need a .lattices.json config to be in a layer — any directory path works. If the project has a config, lattices uses it; if not, it opens a plain terminal in that directory.
  • App windows don’t need any config at all — just specify app and optionally title or url to match the right window.
  • You can have up to 9 layers (Cmd+Option+1 through Cmd+Option+9).
  • Edit workspace.json by hand — the app re-reads it on launch. Use the Refresh Projects button or restart the app to pick up changes.
  • The tile field is optional. Omit it if you just want the window focused without repositioning.
  • Tab groups and standalone projects can coexist in the same workspace. Use groups for related project families, standalone paths for individual projects.