How to Add a New Panel to the Multi-Panel Layout System in Lifetrace

To add a new panel to Lifetrace’s multi-panel layout system, declare a unique PanelFeature identifier in the configuration, create a React component for your panel UI, and register it in the PanelContent switch statement so the layout engine can render it into any available slot.

Lifetrace uses a dynamic, slot-based workspace where each panel is identified by a feature name and occupies a specific slot (panelA, panelB, or panelC). The mapping between features and slots is managed by the Zustand-based UI store (useUiStore). Adding a new panel requires extending the feature registry, building the component, and wiring it into the layout rendering pipeline.

Understanding the Multi-Panel Architecture

Before modifying code, understand how the system routes features to visible UI slots:

Concern Implementation Source File
Feature enumeration & icons PanelFeature union type and FEATURE_ICON_MAP object. lib/config/panel-config.ts
Default layout state DEFAULT_PANEL_STATE defines initial slot assignments and widths. lib/store/ui-store/utils.ts
Runtime slot mapping useUiStore holds the active panelFeatureMap, open/close flags, and the setPanelFeature(position, feature) method. lib/store/ui-store/store.ts
Slot rendering <PanelRegion> determines visible slots based on viewport width and renders <PanelContainer> for each occupied slot. components/layout/PanelRegion.tsx
Content injection <PanelContent> receives a position prop, looks up the assigned feature, and renders the matching component via a switch statement. components/layout/PanelContent.tsx
User selection UI <PanelSelectorMenu> lists ALL_PANEL_FEATURES, filtering out already-assigned ones, and calls store.setPanelFeature on selection. components/layout/PanelSelectorMenu.tsx

Step-by-Step Guide to Adding a New Panel

Step 1 – Declare the Panel Feature

First, extend the type system so the store recognizes your new panel.

Open lib/config/panel-config.ts and add your feature name to the PanelFeature union:

export type PanelFeature =
  | "calendar"
  | "activity"
  | "chat"
  | "todos"
  | "audio"
  // NEW: add your feature identifier
  | "myNewPanel";

Import an icon from lucide-react and map it in FEATURE_ICON_MAP:

import { Star, … } from "lucide-react";

export const FEATURE_ICON_MAP: Record<PanelFeature, LucideIcon> = {
  // …existing mappings…
  audio: Mic,
  myNewPanel: Star, // NEW
};

Finally, expose it in the master list so the selector menu can display it:

export const ALL_PANEL_FEATURES: PanelFeature[] = [
  // …existing…
  "audio",
  "myNewPanel", // NEW
];

Step 2 – Create the Panel Component

Create a new React component that renders your panel’s UI. You can place it in components/panels/MyNewPanel.tsx or a feature-specific directory.

// components/panels/MyNewPanel.tsx
import React from "react";
import { useUiStore } from "@/lib/store/ui-store";

export default function MyNewPanel() {
  // Example: accessing store state if needed
  const isPinned = useUiStore((s) => s.pinnedPanels.panelC);

  return (
    <div className="h-full w-full overflow-auto p-4">
      <h2 className="mb-4 text-lg font-semibold">My New Panel</h2>
      <p>This panel is now integrated into the multi-panel layout system.</p>
      {isPinned && <span className="text-xs text-muted-foreground">(Pinned)</span>}
    </div>
  );
}

Ensure the component accepts no props (or only optional context props) because <PanelContent> renders it purely based on the slot assignment.

Step 3 – Wire the Component into PanelContent

Open components/layout/PanelContent.tsx (or locate the file containing the PanelContent component). Import your new component and add a case to the rendering logic.

// components/layout/PanelContent.tsx
import MyNewPanel from "@/components/panels/MyNewPanel";
// …other imports…

export function PanelContent({ position }: { position: PanelPosition }) {
  const feature = useUiStore((s) => s.getFeatureByPosition(position));

  switch (feature) {
    case "calendar":
      return <CalendarPanel />;
    case "activity":
      return <ActivityPanel />;
    case "chat":
      return <ChatPanel />;
    case "todos":
      return <TodosPanel />;
    case "audio":
      return <AudioPanel />;
    // NEW: wire the feature to the component
    case "myNewPanel":
      return <MyNewPanel />;
    default:
      return null;
  }
}

Now the layout engine can render your component whenever myNewPanel is assigned to a slot.

Step 4 – (Optional) Set Default Assignment

If you want the new panel to appear by default when the app loads, modify DEFAULT_PANEL_STATE in lib/store/ui-store/utils.ts.

// lib/store/ui-store/utils.ts
export const DEFAULT_PANEL_STATE: PanelState = {
  panelFeatureMap: {
    panelA: "todos",
    panelB: "chat",
    panelC: "myNewPanel", // NEW: assign to a default slot
  },
  pinnedPanels: {
    panelA: false,
    panelB: false,
    panelC: true, // Optional: pin the new panel by default
  },
  // …other default state…
};

Alternatively, leave it unassigned so users can add it manually via the Panel Selector Menu (which reads from ALL_PANEL_FEATURES).

Complete Working Example

Here is a consolidated, copy-pasteable implementation that adds a "Notes" panel to the system.

1. Configuration (lib/config/panel-config.ts):

export type PanelFeature = "calendar" | "activity" | "chat" | "todos" | "audio" | "notes";

import { StickyNote } from "lucide-react";

export const FEATURE_ICON_MAP: Record<PanelFeature, LucideIcon> = {
  calendar: Calendar,
  activity: Activity,
  chat: MessageSquare,
  todos: CheckSquare,
  audio: Mic,
  notes: StickyNote,
};

export const ALL_PANEL_FEATURES: PanelFeature[] = [
  "calendar", "activity", "chat", "todos", "audio", "notes"
];

2. Component (components/panels/NotesPanel.tsx):

export default function NotesPanel() {
  return (
    <div className="h-full w-full p-4">
      <h3 className="font-semibold">Quick Notes</h3>
      <textarea className="mt-2 w-full rounded border p-2" placeholder="Type here..." />
    </div>
  );
}

3. Wiring (components/layout/PanelContent.tsx):

import NotesPanel from "@/components/panels/NotesPanel";

// Inside the switch statement:
case "notes":
  return <NotesPanel />;

4. Default Assignment (optional, lib/store/ui-store/utils.ts):

panelFeatureMap: {
  panelA: "todos",
  panelB: "chat",
  panelC: "notes",
},

Summary

  • Declare the feature in panel-config.ts by extending PanelFeature, adding an icon to FEATURE_ICON_MAP, and listing it in ALL_PANEL_FEATURES.
  • Build a standard React component for your panel UI; it receives no special props from the layout system.
  • Wire the component into PanelContent.tsx by importing it and adding a case in the feature switch statement.
  • Assign the panel to a slot either programmatically via useUiStore.getState().setPanelFeature(position, feature) or by setting a default in DEFAULT_PANEL_STATE.

Frequently Asked Questions

How do I remove a panel from the available list?

To hide a panel from the selector menu without deleting its code, add its feature name to the DEV_IN_PROGRESS_FEATURES array in lib/config/panel-config.ts. This filters it out of ALL_PANEL_FEATURES at runtime while preserving the type definition.

Can I pass custom props to my panel component?

The PanelContent switch statement instantiates components without props. If you need data, import it directly inside your panel component using React context, Zustand selectors (e.g., useUiStore), or TanStack Query. Do not modify the PanelContent signature to accept extra props, as that breaks the generic slot architecture.

Why does my panel not appear after I added it to the config?

First, verify that you added the case statement in PanelContent.tsx and that the component is imported correctly. Second, check the browser console for validation errors from validatePanelFeatureMap in utils.ts, which runs on store hydration. Finally, ensure the panel is actually assigned to a slot via the UI selector or DEFAULT_PANEL_STATE; declaring the feature does not automatically render it.

How do I programmatically switch which panel is shown in a slot?

Use the setPanelFeature method exposed by the UI store:

const setPanelFeature = useUiStore((s) => s.setPanelFeature);
setPanelFeature("panelB", "myNewPanel");

This updates the panelFeatureMap and triggers a re-render of PanelContent for that slot. You can also toggle visibility with setPanelOpen(position, boolean).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →