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

> Learn how to add a new panel to the Lifetrace multi-panel layout system. Declare a PanelFeature, create a React component, and register it for seamless integration and rendering.

- Repository: [FreeU-group/lifetrace](https://github.com/freeu-group/lifetrace)
- Tags: how-to-guide
- Published: 2026-03-02

---

**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`](https://github.com/freeu-group/lifetrace/blob/main/lib/config/panel-config.ts) |
| **Default layout state** | `DEFAULT_PANEL_STATE` defines initial slot assignments and widths. | [`lib/store/ui-store/utils.ts`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/lib/config/panel-config.ts) and add your feature name to the `PanelFeature` union:

```typescript
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`:

```typescript
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:

```typescript
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`](https://github.com/freeu-group/lifetrace/blob/main/components/panels/MyNewPanel.tsx) or a feature-specific directory.

```tsx
// 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`](https://github.com/freeu-group/lifetrace/blob/main/components/layout/PanelContent.tsx) (or locate the file containing the `PanelContent` component). Import your new component and add a case to the rendering logic.

```tsx
// 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`](https://github.com/freeu-group/lifetrace/blob/main/lib/store/ui-store/utils.ts).

```typescript
// 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`](https://github.com/freeu-group/lifetrace/blob/main/lib/config/panel-config.ts)):

```typescript
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`](https://github.com/freeu-group/lifetrace/blob/main/components/panels/NotesPanel.tsx)):

```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`](https://github.com/freeu-group/lifetrace/blob/main/components/layout/PanelContent.tsx)):

```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`](https://github.com/freeu-group/lifetrace/blob/main/lib/store/ui-store/utils.ts)):

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

```

## Summary

- **Declare** the feature in [`panel-config.ts`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/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`](https://github.com/freeu-group/lifetrace/blob/main/PanelContent.tsx) and that the component is imported correctly. Second, check the browser console for validation errors from `validatePanelFeatureMap` in [`utils.ts`](https://github.com/freeu-group/lifetrace/blob/main/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:

```typescript
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)`.