Skip to content

refactor(ui): remove ports dependency from UI layer #138

@dnlopes

Description

@dnlopes

Summary

The messages.go file in the UI layer imports from internal/ports, which violates the architectural principle stated in ARCHITECTURE.md: "CLI and UI only depend on Services - never on Ports or Adapters directly".

Problem

Location: internal/ui/messages.go:3

import "github.com/renato0307/rocha/internal/ports"

type AttachSessionMsg struct {
    Session *ports.TmuxSession
}

type AttachShellSessionMsg struct {
    Session *ports.TmuxSession
}

Architectural diagram:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   UI/CLI    │ ──▶ │  Services   │ ──▶ │   Ports     │
└─────────────┘     └─────────────┘     └─────────────┘
                                              │
                                              ▼
                                        ┌─────────────┐
                                        │  Adapters   │
                                        └─────────────┘
  • Ports define interfaces and types for infrastructure concerns (tmux, git, storage)
  • Services orchestrate business logic using those ports
  • UI should only know about domain concepts, not infrastructure details

Why It Matters

When UI imports from ports:

  • UI becomes coupled to infrastructure layer
  • Changes to ports.TmuxSession ripple into UI
  • Harder to test UI in isolation
  • Blurs architectural boundaries

Nuance

This is a borderline case because:

  • TmuxSession is a simple data struct, not an interface
  • UI isn't calling tmux methods, just passing data
  • The struct conceptually represents a domain entity (a session)

Proposed Solutions

Option 1: Move TmuxSession to domain package (recommended)

// internal/domain/tmux_session.go
type TmuxSession struct {
    Name     string
    Attached bool
    // ...
}

// internal/ports/tmux.go
type TmuxClient interface {
    ListSessions() ([]domain.TmuxSession, error)
    // ...
}

// internal/ui/messages.go
import "github.com/renato0307/rocha/internal/domain"

type AttachSessionMsg struct {
    Session *domain.TmuxSession
}

Option 2: Use session name only

Similar to other messages like KillSessionMsg, ArchiveSessionMsg:

type AttachSessionMsg struct {
    SessionName string
}

type AttachShellSessionMsg struct {
    SessionName string
}

Then the handler looks up the full session object. This is more consistent with other action messages.

Option 3: Document as acceptable exception

If the team decides the pragmatic tradeoff is worth it, add a comment in ARCHITECTURE.md explaining why this specific case is allowed.

Recommendation

Option 1 is the cleanest architectural solution. Option 2 is simpler and more consistent with existing patterns in the same file. Option 3 should be last resort.

Labels

  • enhancement
  • architecture

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions