Skip to content
Color theme

Studio Features

A feature in Loopstack is an optional capability that a module opts into and that the Studio UI can render a dedicated surface for — typically a sidebar panel or a document widget. Built-in examples are the git, localFileExplorer, remoteFileExplorer, secrets, and handoff features. Features are an advanced extension point: most apps never need to create one.

When to Use a Feature

Use a feature when a module wants Studio to:

  • show an extra UI panel (e.g. a Git history panel) only when the app opts in
  • expose runtime config to the frontend (e.g. which environments a panel applies to)

If you only need workflows, tools, and documents, you don’t need a feature — those are surfaced automatically.

How It Works

  1. A feature module exposes a forFeature(config) static method that registers a tagged provider via registerFeature(id, config).
  2. At bootstrap, the framework walks the import graph of each @StudioApp module and collects every registered feature reachable from it.
  3. The Studio API returns the active features per app. The frontend has an AVAILABLE_FEATURES registry that maps each feature id to a UI surface (panel, widget, etc.).

The feature id on the backend must match the key in the frontend’s AVAILABLE_FEATURES registry.

Example — Enabling the Git Feature

import { Module } from '@nestjs/common';
import { StudioApp } from '@loopstack/common';
import { GitModule } from '@loopstack/git';
import { MyWorkflow } from './workflows/my.workflow';
@StudioApp({
title: 'My App',
workflows: [MyWorkflow],
})
@Module({
imports: [GitModule.forFeature({ enabled: true })],
providers: [MyWorkflow],
})
export class MyAppModule {}

Importing GitModule alone provides the Git tools. Calling GitModule.forFeature(...) additionally registers the git feature for this app, which makes Studio render the Git sidebar panel.

Example — Hand-offs to the User’s Machine

HandoffModule.forFeature() from @loopstack/handoff registers the handoff feature. Studio then shows a “Handoff” sidebar panel with every document tagged handoff for the run in view, and renders two document widgets:

Document Widget Content
HandoffDocument handoff title, description?, and a command to copy or a url to open
ChangedFilesDocument changed-files hostRoot (absolute path on the user’s machine) and the changed paths under it
import { HandoffDocument } from '@loopstack/handoff';
await this.documentStore.save(
HandoffDocument,
{ title: 'Open in Zed', command: 'zed "/home/me/project"' },
{ key: 'open_in_zed' },
);

The package also ships TerminalHandoffDocument, which drives the CLI’s terminal-handoff widget: under loopstack run it hands the terminal to a local command and fires the handoffDone transition when it exits. With the feature enabled, Studio renders the same document as a copy-the-command card with an “End session” button that fires handoffDone by hand, for a run nobody is following.

Defining a Custom Feature

A custom feature module uses registerFeature() inside forFeature():

import { DynamicModule, Module } from '@nestjs/common';
import { registerFeature } from '@loopstack/common';
@Module({
/* providers, controllers… */
})
export class MyFeatureModule {
static forFeature(config?: { enabled?: boolean } & Record<string, unknown>): DynamicModule {
return {
module: MyFeatureModule,
providers: [registerFeature('myFeature', config)],
};
}
}

To make the feature visible in Studio, the frontend must register a matching entry in its AVAILABLE_FEATURES registry under the same id (myFeature here). Without that frontend entry the feature is registered on the backend but has no UI surface.

Studio Extensions (advanced)

Some feature modules also contribute arbitrary config sections to a @StudioApp via registerStudioExtension(section, data). The collected payloads are grouped by section and exposed on the resolved StudioAppConfig.extensions[section]. This is an internal extension point (currently used by RemoteClientModule to register environment slots) and not a stable public API — refer to the JSDoc on registerStudioExtension for details before using it.

References

  • loopstack/packages/common/src/utils/feature-registration.ts — registerFeature() helper
  • loopstack/packages/core/src/workflow-processor/services/studio-discovery.service.ts — bootstrap-time feature discovery
  • loopstack/registry/git/src/git.module.ts — reference implementation