Skip to content

Latest commit

 

History

History
153 lines (108 loc) · 6.46 KB

File metadata and controls

153 lines (108 loc) · 6.46 KB

Loft Modules

Loft is built as a multi-module developer toolbox. This doc explains the module model, the current roadmap, and how to add a new one.

Module philosophy

Each module is:

  • Focused. One workflow, end to end. Storage Explainer doesn't try to be a tree visualizer and a duplicate finder and a tag manager — it does one thing (explain disk usage) and does it well.
  • Native. SwiftUI surfaces, macOS-native interactions (Reveal in Finder, NSWorkspace, TCC permissions). No Electron, no embedded browser.
  • Independently testable. A module's models / services / scanners are unit-testable without the UI.
  • Failure-isolated. A module's errors don't bring the shell down. Internal subsystems (scanners) catch their own errors and report them as data.
  • Safe by default. Anything destructive goes through the macOS Trash + a confirmation + a log entry. Read-only is the default for anything ambiguous.
  • Polished. Loft's design system is part of the value proposition. Modules use Theme.swift tokens — gradients, shadows, typography — not ad-hoc styling.

Anatomy of a module

A typical module under Loft/Modules/<YourModule>/ looks like:

Modules/
└── ProcessManager/             # example module
    ├── Models/
    │   ├── ProcessInfo.swift
    │   └── ProcessGroup.swift
    ├── Services/
    │   ├── ProcessSampler.swift     # actor that reads /proc-like info
    │   └── ProcessKiller.swift      # actor wrapping kill(2)
    ├── ViewModels/
    │   └── ProcessViewModel.swift   # @MainActor
    └── Views/
        ├── ProcessDashboardView.swift
        └── ProcessRowView.swift

Today the Storage module lives at the top of Loft/ (not nested under Modules/) because it's the only one. The second module should land under Modules/ and we'll likely migrate Storage there too once the seam is clearer.

Required interfaces

A module needs to expose, at minimum:

  1. A root view — the SwiftUI view shown when the user selects the module from the sidebar.
  2. A view model@MainActor ObservableObject holding the module's state.
  3. An enum case in AppShell.Module plus a sidebar icon row and a content branch.

Optional but encouraged:

  • Per-module preferences (UserDefaults keys prefixed with module.<name>.).
  • An "Ask AI" entry point if the module surfaces ambiguous data the user might want explained.
  • Reveal-in-Finder / Reveal-in-app actions on any concrete item.

How to add a module

Concrete steps. Replace MyModule with your name.

1. Open a proposal first

Use the new module proposal template. Pin the scope before writing code — a 30-line issue is much cheaper than a 3,000-line PR that needs reshaping.

2. Add a sidebar slot

In Loft/Views/AppShell.swift:

enum Module: String, CaseIterable, Hashable {
    case storage
    case myModule   // <-- add
}

Then add an icon row in Sidebar.body:

ModuleIcon(
    systemName: "cpu",
    isActive: selection == .myModule,
    gradient: Theme.primaryGradient,
    label: "My Module",
    onTap: { selection = .myModule }
)

And a content branch in AppShell.content:

case .myModule:
    NavigationStack {
        MyModuleDashboardView(vm: myModuleVM)
    }
    .tint(Theme.verdant)

You'll need to add the matching @StateObject private var myModuleVM = MyModuleViewModel() (or pass it in from ContentView).

3. Build out the module

Under Loft/Modules/MyModule/ create the model / service / view model / views. Follow the same patterns as Storage:

  • Use design tokens from Theme.swift.
  • Wrap I/O in an actor.
  • Make services Sendable.
  • Catch errors at the service boundary; surface them as data.
  • Write tests under LoftTests/MyModule/.

4. Regenerate the project

xcodegen generate

xcodegen reads project.yml and picks up new files under recursive source paths automatically.

5. Add to the roadmap

Update the roadmap in README.md, README.ko.md, and the table below.

6. PR

Open a PR using the template. Include a short screen recording — modules are visual, and motion conveys what a static screenshot misses.

Roadmap

Module Status Description
Storage Explainer shipping Disk usage breakdown, drill-down, safe delete, Ask AI.
Process Manager proposed Visualize + kill heavy processes. Activity Monitor reimagined for developers (group by project / dev tool, sort by CPU/memory, kill with confirmation).
Load Average proposed Historical CPU / memory / load with sensible defaults. A persistent strip in the corner you can glance at.
Network Inspector proposed Active connections, listening ports, per-process bandwidth. Quick "what's hogging my network" view.
Git Lens proposed Multi-repo status across your code directories — uncommitted, behind/ahead, stale branches.
Dev Server Manager proposed Start / stop / log local services (Postgres, Redis, custom processes from a config file).
Environment Manager proposed Inspect and edit shell environment variables, per-shell and global.

"Proposed" means we've sketched the value but nothing's started. Pick one up by opening a new module proposal issue and starting the conversation.

Design considerations for new modules

When designing a new module, think about:

  • What's the equivalent of "Reveal in Finder"? Every module should have a one-click escape hatch into the user's familiar tools.
  • What's the safety story? What can the module destroy or change? How does it confirm? Where does it log?
  • What's the empty / loading state? Loft cares about polish in those states.
  • What's the failure-mode messaging? If a permission is missing or a system call fails, the user should know in human language.
  • Does it deserve "Ask AI"? If the module shows opaque names the user might want explained (process names, kernel extensions, etc.), wire up PromptBuilder + AskAIMenu.

What's intentionally not a module

Things Loft will probably never ship:

  • Network ad blockers, VPN clients, content blockers — out of scope.
  • Anything modifying other apps' internals via injection.
  • Anything operating on the user's behalf without explicit action.

If you're unsure, propose it. We're early enough that the boundary is still being drawn.