Skip to content

Add get-tree and append-layout commands#1958

Open
glassbe wants to merge 4 commits intonikitabobko:mainfrom
glassbe:feature/get-tree-append-layout-pr
Open

Add get-tree and append-layout commands#1958
glassbe wants to merge 4 commits intonikitabobko:mainfrom
glassbe:feature/get-tree-append-layout-pr

Conversation

@glassbe
Copy link
Copy Markdown

@glassbe glassbe commented Feb 15, 2026

Summary

Adds two new commands inspired by i3's get_tree and append_layout:

  • get-tree — Serializes the workspace tree to JSON (containers with layout/orientation/weights, windows with IDs and bundle IDs)
  • append-layout — Reads a JSON layout spec from stdin and reconstructs the workspace tree, matching windows by window-id then app-bundle-id

Together they enable programmatic layout save/restore — a round-trip workflow:

# Save
aerospace get-tree --workspace 1 > layout.json
# Restore
cat layout.json | aerospace append-layout --workspace 1

Motivation

AeroSpace currently has no way to programmatically save and restore window arrangements. Users who want preset layouts (e.g., coding layout with specific accordion/tiles nesting) must resort to fragile shell scripts using flatten + move + join-with, which break due to unpredictable window ordering.

These commands fill the gap, similar to i3's i3-save-tree / append_layout workflow.

Related discussion: #1957

Implementation Details

  • get-tree recursively walks the workspace tree and outputs JSON with container layout/orientation/weight and window metadata
  • append-layout flattens the workspace, matches windows first by window-id (exact match) then by app-bundle-id (fallback), and rebuilds the container hierarchy from the JSON spec
  • Custom window sizes (weights) are preserved through the round-trip — a 70/30 split stays 70/30 after restore
  • Missing windows (apps not running) don't collapse the tree structure — only completely empty leaf containers are pruned
  • The CLI forwards stdin to the server for append-layout (same pattern as workspace --stdin)
  • append-layout accepts both raw container specs and full get-tree output (with "type": "workspace" wrapper)

Files Changed

New files (8):

  • docs/aerospace-get-tree.adoc — command documentation
  • docs/aerospace-append-layout.adoc — command documentation
  • Sources/Common/cmdArgs/impl/GetTreeCmdArgs.swift — command args
  • Sources/Common/cmdArgs/impl/AppendLayoutCmdArgs.swift — command args
  • Sources/AppBundle/command/impl/GetTreeCommand.swift — tree serialization with weights
  • Sources/AppBundle/command/impl/AppendLayoutCommand.swift — tree construction with window-id matching
  • Sources/AppBundleTests/command/GetTreeCommandTest.swift — 3 tests
  • Sources/AppBundleTests/command/AppendLayoutCommandTest.swift — 7 tests

Modified files (5):

  • Sources/Common/cmdArgs/cmdArgsManifest.swift — register new commands
  • Sources/Common/cmdHelpGenerated.swift — regenerated with new help strings
  • Sources/Cli/subcommandDescriptionsGenerated.swift — regenerated with new subcommand entries
  • Sources/AppBundle/command/cmdManifest.swift — command dispatch
  • Sources/AppBundle/model/Json.swift — add .double case for weight serialization
  • Sources/Cli/_main.swift — forward stdin for append-layout

Test Plan

  • All 113 existing tests pass, 10 new tests added (116 total on clean branch)
  • 3 get-tree tests: simple, nested, floating windows (including weight output)
  • 7 append-layout tests: simple, nested, unmatched windows, window-id matching, weight preservation, missing window structure preservation, full round-trip
  • Manual testing: save workspace layout → flatten → restore from saved JSON
  • generate.sh regeneration produces consistent output (.adoc source files included)

🤖 Generated with Claude Code

glassbe and others added 4 commits February 15, 2026 23:18
get-tree dumps the workspace container hierarchy as JSON, including
tiling containers (with layout and orientation) and windows (with IDs
and app bundle info). Floating windows are included in a separate array.

append-layout reads a JSON layout specification from stdin and
constructs the corresponding container tree on the target workspace.
Windows are matched to spec slots by app-bundle-id. Unmatched windows
remain as flat siblings in root. This enables preset layout tools to
apply layouts in a single command instead of the fragile
flatten/move/join-with/layout dance.

Together these bring i3-style programmatic tree access to AeroSpace:
- get-tree ≈ i3-msg -t get_tree
- append-layout ≈ i3-msg append_layout

Includes 7 tests covering JSON output structure, nested containers,
floating windows, round-trip serialization, and unmatched window
handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for append-layout to work end-to-end:

1. CLI stdin forwarding: the aerospace CLI only forwarded stdin for
   workspace/move-node-to-workspace commands. Added AppendLayoutCmdArgs
   to the stdin-reading condition so piped JSON reaches the server.

2. Workspace spec support: append-layout now accepts both a bare
   container spec and the full get-tree workspace output (extracts
   the "tiling" field automatically). This enables the round-trip:
   aerospace get-tree > layout.json && cat layout.json | aerospace append-layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iles

The generate.sh script produces cmdHelpGenerated.swift and
subcommandDescriptionsGenerated.swift from docs/aerospace-*.adoc files.
Without the .adoc source files, running generate.sh would strip the
help entries and CLI subcommand descriptions for the new commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…p structure

Three improvements to the layout round-trip:

1. Weights: get-tree now outputs "weight" for each child node.
   append-layout reads it back, preserving custom window sizes
   (e.g., 70/30 splits) instead of resetting everything to equal.

2. Window-id matching: append-layout now tries to match windows by
   window-id first before falling back to app-bundle-id. This ensures
   specific windows (e.g., two Ghostty terminals) end up in the
   correct slots when restoring a layout immediately.

3. Structure preservation: Replaced normalizeContainers() with a
   targeted removeEmptyLeafContainers() that only prunes containers
   with zero children. Single-child containers from the layout spec
   are now preserved, so missing windows don't collapse the tree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@unclecheese
Copy link
Copy Markdown

This is exactly what I've been looking for. The imperative workarounds I'm using have not worked out well.

Would it be possible to match by window-title regex as well, like we can do in the window detected event?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants