waveterm icon indicating copy to clipboard operation
waveterm copied to clipboard

feat: add tab-scoped base directory with smart auto-detection

Open sgeraldes opened this issue 1 month ago • 2 comments

Overview

This PR adds per-tab base directory context that allows all components within a tab (terminals, file previews, widgets) to inherit a common working directory. This creates a cohesive workspace experience where related work shares the same base path.

Problem

Currently, each terminal, file preview, and widget in Wave Terminal operates independently with its own working directory. This creates friction when:

  • Opening multiple terminals that should work in the same project
  • Switching between widgets and terminals that need to reference the same files
  • Maintaining consistent context across tab restarts

Solution

This PR introduces tab-level base directory metadata with:

  • Smart auto-detection from terminal directory changes
  • Visual indicators showing the current base directory
  • Lock/unlock mechanism for manual control
  • Automatic inheritance by new terminals and widgets

Features

1. Tab Metadata Fields

  • tab:basedir - Stores the current base directory path
  • tab:basedirlock - Controls whether auto-detection is enabled

2. Visual UI Indicators

Tab Base Directory UI

  • Folder icon with shortened path displayed on tabs
  • Clickable lock/unlock icons to control auto-detection
  • Tooltip showing full path on hover

3. Tab Context Menu

Right-click on any tab to:

  • Set base directory manually (file picker)
  • Choose from presets (defined in presets/tabvars.json)
  • Clear base directory
  • Toggle lock/unlock

4. Smart Auto-Detection

When auto-detection is enabled (unlocked):

  • Terminal cd commands automatically update the tab's base directory
  • Uses OSC 7 sequence (standard terminal protocol)
  • Only updates if base directory is unset or set to ~
  • Respects lock status to preserve manually-set directories

5. Component Integration

New Terminals:

// Inherits tab base directory as initial cmd:cwd
const tabBaseDir = tabData?.meta?.["tab:basedir"];
if (tabBaseDir) {
    termBlockDef.meta["cmd:cwd"] = tabBaseDir;
}

Widgets:

// Widgets inherit base directory for file operations
if (blockDef?.meta?.view === "term" && !blockDef.meta["cmd:cwd"]) {
    blockDef.meta["cmd:cwd"] = tabBaseDir;
}

File Previews:

// File preview defaults to tab base directory
if (blockDef?.meta?.view === "preview" && blockDef.meta.file === "~") {
    blockDef.meta.file = tabBaseDir;
}

6. Tab Variables Configuration

New configuration section in Wave Config:

  • Path: presets/tabvars.json
  • Allows defining preset base directories
  • JSON validation ensures correct structure

Example preset:

{
    "tabvar@my-project": {
        "tab:basedir": "~/Code/my-project",
        "tab:basedirlock": true
    }
}

Implementation Details

Backend Changes

pkg/waveobj/wtypemeta.go:

type WaveObjMeta struct {
    // ... existing fields ...
    
    TabBaseDir     string  `json:"tab:basedir,omitempty"`
    TabBaseDirLock bool    `json:"tab:basedirlock,omitempty"`
}

pkg/wconfig/defaultconfig/presets/tabvars.json:

  • Example presets for common project structures
  • Demonstrates locked vs unlocked directories

Frontend Changes

frontend/app/tab/tab.tsx:

  • Added visual base directory indicator below tab name
  • Lock/unlock icons with click handlers
  • Context menu items for base directory management
  • Path shortening for space efficiency (/very/long/path.../path)

frontend/app/tab/tab.scss:

.tab-basedir {
    display: flex;
    gap: 3px;
    font-size: 9px;
    opacity: 0.7;
    
    .tab-basedir-lock {
        cursor: pointer;
        &:hover {
            opacity: 1;
        }
    }
}

frontend/app/view/term/termwrap.ts:

  • Enhanced OSC 7 handler to update tab metadata
  • Checks lock status before auto-detection
  • Integrates with existing terminal directory tracking

frontend/app/store/keymodel.ts:

  • Modified getDefaultNewBlockDef() to check tab base directory
  • Falls back to focused block's cwd if no tab base directory

frontend/app/workspace/widgets.tsx:

  • Updated handleWidgetSelect() to inherit tab base directory
  • Handles both terminal and preview widgets

UI/UX Design Decisions

  1. Subtle Visual Indicators: Small font size and opacity prevent visual clutter while remaining informative

  2. Path Shortening: Display .../waveterm instead of full path to save horizontal space

  3. Clickable Lock Icons: Direct manipulation feels more intuitive than menu-only access

  4. Smart Auto-Detection Logic: Only updates when not locked AND (not set OR set to ~)

    • Respects user's manual choices
    • Provides helpful defaults for new tabs
  5. Context Menu Organization: Base Directory submenu keeps related actions grouped

Testing

Manual Testing

  1. Create a new tab
  2. Open a terminal and cd to a project directory
  3. Observe tab base directory auto-detection
  4. Click lock icon to disable auto-detection
  5. Create new terminal - should inherit locked base directory
  6. Right-click tab → Base Directory → Set manually
  7. Verify widgets use the base directory

Edge Cases Tested

  • Terminal size mismatch during restoration
  • Empty or invalid base directory paths
  • Switching between locked/unlocked states
  • Multiple terminals in same tab with different cwds

Screenshots

Tab with Base Directory Indicator

Show tab with folder icon and path

Lock/Unlock States

Show locked vs unlocked visual states

Context Menu

Show right-click menu with Base Directory options

Migration

No migration needed - this is a new feature with backward compatibility:

  • Existing tabs without tab:basedir continue to work normally
  • Auto-detection only activates when base directory is unset

Documentation

Updated CLAUDE.md with:

  • Feature description
  • Configuration examples
  • Development guidelines

Related Issues

Closes #XXXX (if applicable)

Checklist

  • [x] Code follows project conventions
  • [x] TypeScript types generated from Go types
  • [x] Visual design matches Wave Terminal aesthetic
  • [x] Context menu integration tested
  • [x] Auto-detection logic validated
  • [x] Lock/unlock mechanism works correctly
  • [x] Component inheritance tested (terminals, widgets, previews)

Co-Authored-By: Claude Sonnet 4.5 [email protected]

sgeraldes avatar Jan 15 '26 02:01 sgeraldes

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

CLAassistant avatar Jan 15 '26 02:01 CLAassistant

Walkthrough

This pull request introduces a tab-level base directory feature across frontend and backend components. The changes enable users to set a default working directory for each tab through UI controls, store this configuration as metadata (tab:basedir and tab:basedirlock), and apply it automatically when creating term blocks or handling terminal output. New term blocks inherit the tab's base directory as their working directory when not explicitly set. An OSC 7 handler can auto-detect and update the tab base directory from terminal responses if not locked. Configuration support includes type definitions, validators, and default presets for tab variables.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Details

Walkthrough context:

  • Frontend UI additions in tab.tsx include context menu options for setting/clearing base directories, toggling lock state, and displaying base directory information alongside tab names
  • Styling updates in tab.scss introduce a new tab-basedir block with folder icon and lock/unlock indicator
  • Backend type definitions and constants added to support the new metadata fields
  • Auto-detection logic in termwrap.ts updates tab base directory from OSC 7 terminal responses when conditions are met
  • Default block creation in keymodel.ts falls back to tab base directory for working directory when not specified
  • Widget selection logic in widgets.tsx applies base directory to newly selected term or preview blocks
  • Configuration schema and validator added for tab variable presets

Complexity drivers:

  • tab.tsx (+140 lines) contains multiple new state handlers, context menu expansions, and UI rendering logic that require careful review for correctness
  • tab.scss (+53 lines) introduces new styling structure with layout, typography, and hover behaviors
  • termwrap.ts conditional logic for OSC 7 handling requires understanding of the terminal protocol and metadata update conditions
  • Remaining files contain straightforward type/constant additions and configuration data following established patterns
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add tab-scoped base directory with smart auto-detection' accurately and concisely describes the main change—adding per-tab base directory functionality with auto-detection capabilities.
Description check ✅ Passed The description comprehensively explains the feature, problem statement, solution, implementation details, testing approach, and UI/UX decisions—all directly related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • [ ] 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Jan 15 '26 02:01 coderabbitai[bot]

Closing as superseded by #2788 which includes this feature along with security fixes, validation improvements, and VS Code style tab redesign.

sgeraldes avatar Jan 22 '26 06:01 sgeraldes