Skip to content

Configurable terminal orientation (top/bottom/left/right) with drag-to-resize #1237

@jrf0110

Description

@jrf0110

Parent

Part of #204 (Phase 4: Hardening)

Problem

The terminal bar (Status, Mayor, and Agent tabs) is hardcoded as a fixed-position panel at the bottom of the viewport with a non-resizable 300px content area. Users can't:

  • Move it to the top, left, or right
  • Resize it by dragging
  • Have the page content adjust dynamically (the layout uses a static pb-[340px] that doesn't respond to collapse state, wasting ~300px of vertical space when collapsed)

For users on wide monitors, a vertical orientation (terminal on the right) would be far more natural — similar to VS Code's panel system. Users on smaller screens might want a shorter terminal. The current one-size-fits-all layout doesn't serve anyone well.

Current Architecture

The terminal bar is position: fixed; right: 0; bottom: 0 with a dynamic left that tracks the sidebar width. Height toggles between 38px (collapsed) and 338px (expanded) via two hardcoded constants. Page content reserves space via a static pb-[340px] Tailwind class that never changes regardless of collapse state — meaning ~300px of dead space when collapsed.

Key files:

  • src/components/gastown/TerminalBar.tsx — the entire terminal bar component (position, tabs, content panes)
  • src/components/gastown/TerminalBarContext.tsx — state management (tabs, activeTab, collapsed)
  • src/app/(app)/gastown/[townId]/layout.tsx — static pb-[340px] padding
  • src/app/(app)/organizations/[id]/gastown/[townId]/layout.tsx — same static padding

Solution

1. Configurable orientation and position

Users can place the terminal bar in any of four positions:

Position Orientation Terminal attaches to Content adjusts
Bottom (default) Horizontal Bottom edge of viewport padding-bottom
Top Horizontal Top edge of viewport (below any app header) padding-top
Right Vertical Right edge of viewport padding-right
Left Vertical Left edge of viewport (right of sidebar) padding-left

A position picker (4-way toggle or dropdown) in the terminal bar's tab row lets users switch. Preference persisted to localStorage.

2. Drag-to-resize

A resize handle at the inner edge of the terminal bar (the edge facing the page content):

  • Bottom: horizontal handle at the top edge, cursor ns-resize
  • Top: horizontal handle at the bottom edge, cursor ns-resize
  • Right: vertical handle at the left edge, cursor ew-resize
  • Left: vertical handle at the right edge, cursor ew-resize

Drag events update the size in context. The xterm.js ResizeObserver in useXtermPty.ts already auto-fits the terminal when its container resizes — no additional xterm handling needed.

Min/max constraints to prevent the terminal from consuming the entire viewport:

  • Horizontal: min 100px, max 70% of viewport height
  • Vertical: min 200px, max 50% of viewport width

3. Dynamic page padding

Replace the static pb-[340px] with a dynamic inline style that responds to position, size, and collapse state:

const padding = collapsed ? COLLAPSED_SIZE : size + COLLAPSED_SIZE;
const style = {
  paddingBottom: position === 'bottom' ? `${padding}px` : undefined,
  paddingTop: position === 'top' ? `${padding}px` : undefined,
  paddingRight: position === 'right' ? `${padding}px` : undefined,
  paddingLeft: position === 'left' ? `${padding}px` : undefined,
};

This requires the layout's padding wrapper to consume TerminalBarContext. The layout is currently a Server Component — the padding div needs to be extracted into a client component wrapper.

When collapsed, only COLLAPSED_SIZE (the tab bar strip) is reserved — no dead space.

4. New state in TerminalBarContext

Add to context:

  • position: 'bottom' | 'top' | 'right' | 'left' (default: 'bottom')
  • size: number (px — height for horizontal, width for vertical; default: 300)
  • setPosition(position) — updates position, persists to localStorage
  • setSize(size) — updates size, persists to localStorage

5. Vertical orientation adaptations

When the terminal is vertical (left/right), several sub-components need layout adjustments:

Component Horizontal layout Vertical layout
Tab bar Horizontal flex row, overflow-x-auto Vertical flex-col stack, overflow-y-auto
Tab items Compact horizontal labels Rotated text or icon-only with tooltip
Status pane (AlarmStatusPane) Two-column layout (340px left + flex-1 right) Single-column stacked layout
Terminal content Full width, fixed height Fixed width, full height
Collapse toggle Up/down chevron Left/right chevron

6. Sidebar conflict for left position

Placing the terminal on the left conflicts with the existing sidebar. The terminal should be positioned inside SidebarInset (the content area right of the sidebar), not outside it. For left position: left: sidebarWidth, same as the current bottom position calculates. This means the terminal pushes content right, not the sidebar.

7. DrawerStack interaction

The stackable drawers (DrawerStack.tsx) are fixed top-0 right-0 bottom-0 z-[61]. When the terminal is on the right, drawers need to respect the terminal's width — their right offset should be terminalSize + COLLAPSED_SIZE when the terminal is on the right. The terminal bar is z-50, drawers are z-61+, so layering is already correct.

Acceptance Criteria

  • Terminal bar can be positioned at bottom, top, right, or left
  • Position picker UI in the terminal bar
  • Drag-to-resize handle on the inner edge, with min/max constraints
  • Page content adjusts dynamically — no content hidden behind the terminal
  • Collapsed state only reserves the tab bar strip, not the full expanded size
  • Position and size persisted to localStorage
  • Vertical orientation: tab bar stacks vertically, status pane stacks single-column
  • DrawerStack respects terminal position when terminal is on the right
  • xterm.js auto-refits on resize (already handled by existing ResizeObserver)

Notes

  • No data migration needed
  • The existing ResizeObserver + fitAddon.fit() debounce in useXtermPty.ts means xterm terminals will auto-adapt to any resize — this is already working
  • The MayorTerminalPane duplicated xterm setup (identified in Fix terminal stability — WebSocket reconnection, resize debounce, control frame filtering #1195) should ideally be deduplicated before or alongside this work to avoid applying orientation changes in two places
  • Consider a keyboard shortcut to toggle collapse (e.g., Cmd+J like VS Code's panel toggle)
  • The position picker could use a visual icon showing the four options (like VS Code's panel position buttons)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestkilo-auto-fixAuto-generated label by Kilokilo-triagedAuto-generated label by Kilo

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions