The portal uses a split-view layout: a resizable side panel that slides in from the right, and a main panel that always shows the current page. The side panel is designed for AI chat threads, but it can host any content — and it remembers exactly where you left off. Main Panel Current URL / page content Thread (full-page view) ← back link · title · message history Input area (message box) Navigate independently side panel content is preserved Page reload State from localStorage drag to resize Side Panel + New ↺ Resume ↗ ✕ AI Chat Thread user message assistant response user message @ type a message… Agent selector Model selector Persisted in localStorage ↗ promote Split-view layout: the resizable side panel hosts AI chat threads while the main panel tracks the current URL — the two panels operate independently.


Opening and Closing

Action How
Open the side panel Click the Chat icon in the top-right header
Close the side panel Click the X button in the side panel header
Resize Drag the splitter bar between the two panels

State is preserved on close. Collapsing the panel does not destroy its content. Re-opening restores your previous view — scroll position, ongoing chat, and any typed text are all intact.

The side panel state (open/closed, active thread, size, and position) is persisted across page reloads via browser localStorage.


Side Panel Header

The header provides quick access to common actions, split across left and right zones.

Left side — title area:

Icon Action Description
+ New Thread Starts a fresh AI chat thread
Resume Thread Opens a searchable list of recent threads to pick up
Title Display only Shows the current thread name or "New Thread"

Right side — panel controls:

Icon Action Description
Open in main panel Promotes the current thread to the main panel and closes the side panel
X Close Collapses the side panel

Chat Threads

Starting a New Chat

Click + in the side panel header, or simply open the side panel when no thread is active. The input area offers:

Press Enter or click Send. A thread node is created automatically on the first message — no manual naming required.

Resuming a Thread

Click to open a searchable list of your recent threads. The list shows threads from your current namespace, sorted by last modified date. Click any thread to load it in the side panel without disturbing the main panel.

Promoting to Main Panel

Click to move the current thread from the side panel into the full main content area. The side panel closes and the browser navigates to the thread's full-page view, which adds a larger header with context links, full scrollable message history, and an input area at the bottom.

The full-page thread header is automatically hidden when the same thread is rendered inside the side panel — the panel's own compact header takes over so nothing is duplicated.


Opening the Side Panel From a Layout Area

Everything above is what the viewer does. An area can also open the side panel itself — and for an embedded chat that is almost always the right choice.

A layout area has two ways to send a viewer somewhere:

Call Effect
Host.NavigateTo(uri) Replaces the page — the viewer leaves where they are
Host.NavigateToSidePanel(uri) Opens uri beside the page — the viewer stays put

Both are available on LayoutAreaHost and, as extension methods, on the UiActionContext a click handler receives:

Controls.Button("Try it")
    .WithClickAction(click =>
    {
        var threadPath = StartTheExerciseThread(click);
        click.NavigateToSidePanel(threadPath);   // not NavigateTo — the lesson stays on screen
        return Task.CompletedTask;
    })

Use NavigateToSidePanel wherever a click creates or references a thread the viewer should see beside their work. An embedded exercise composer that navigates the whole page to the thread it just opened destroys the very page the exercise told the viewer to come back to. This was a real defect in a course's try-it exercise: sending a prompt replaced the lesson with the thread.

How it reaches the browser

NavigateToSidePanel posts a NavigationRequest carrying Target = "SidePanel" to the area's subscriber. From there the chain is the one the portal already had end to end — the portal application's handler, the navigation service, its side-panel event, and the portal layout opening the panel on that path. The API is the first link; nothing downstream was added for it.

Two design notes worth knowing


Main Panel

The main panel occupies the full width when the side panel is closed, and shares space via the resizable splitter when it is open. Its content always reflects the current URL.

Thread View in Main Panel

When a thread is opened in the main panel — either by direct navigation or by promotion from the side panel — it shows:


Interaction Between Panels

The two panels are deliberately independent: navigating in one does not disrupt the other.

Scenario Behavior
Open side panel while a thread is active in the main panel Thread moves to the side panel; main panel navigates to the thread's parent
Click "Open in main panel" in the side panel Thread moves to the main panel; side panel closes
Navigate to a different page in the main panel Side panel content is preserved independently
Page reload Side panel reopens with the same thread if it was open
Resume a thread from the list Thread loads in the side panel only; main panel is unaffected

Persisted State

The following side panel state is saved to browser localStorage and survives page reloads:

State Details
Open / closed Panel visibility on next load
Content path Which thread (or other content) is displayed
Position Right or bottom attachment
Size Width / height from drag resizing
Display title The title shown in the panel header

Live Demo

The cell below renders a small mock of the side panel's control strip so you can explore the layout controls used to build it:

MeshWeaver.Layout.Controls.Stack
    .WithView(MeshWeaver.Layout.Controls.Html("<b>Side Panel Header Controls</b>"))
    .WithView(
        MeshWeaver.Layout.Controls.Stack
            .WithView(MeshWeaver.Layout.Controls.Button("+ New Thread"))
            .WithView(MeshWeaver.Layout.Controls.Button("↺ Resume"))
    )
    .WithView(MeshWeaver.Layout.Controls.Html("<hr/>"))
    .WithView(MeshWeaver.Layout.Controls.Html("<i>Main panel and side panel share space via a resizable splitter.</i>"))
Reconnecting…
The server was updated. Reloading the page to pick up the latest version.