Finish implementing all prototype features
diff --git a/packages/sdk/src/conduit.ts b/packages/sdk/src/conduit.ts
index ac735b2..e223540 100644
--- a/packages/sdk/src/conduit.ts
+++ b/packages/sdk/src/conduit.ts
@@ -17,7 +17,7 @@
     client: {
         plugin: () => Promise<PluginType>;
         panels: {
-            [Property in keyof PluginType["panels"]]: (args: IrisPluginConduitPanelArgs<PluginType["panels"][Property]["requiredArgs"]>) => Promise<IrisPluginConduitPanelLayout<PluginType>>;
+            [Property in keyof PluginType["panels"]]: (args: IrisPluginConduitPanelArgs<PluginType["panels"][Property]["requiredArgs"]>, state: IrisPluginConduitPanelState<PluginType>) => Promise<IrisPluginConduitPanelLayout<PluginType>>;
         };
         forms: {
             [Property in keyof PluginType["forms"]]: (data: IrisPluginConduitFieldData<PluginType["forms"][Property]["data"]>) => Promise<string>;
@@ -53,8 +53,19 @@
 
 export type IrisPluginConduitPanelArgs<RequiredArgsType extends string[]> = {
     [Property in RequiredArgsType[number]]: string;
-} & {
-    [arg: string]: string;
+};
+
+export type IrisPluginConduitPanelState<PluginType extends IrisPluginConduit> = {
+    sections: {
+        [id: string]: IrisPluginConduitPanelSectionState<PluginType> | undefined;
+    };
+};
+
+export type IrisPluginConduitPanelSectionState<PluginType extends IrisPluginConduit> = {
+    table?: {
+        search?: string;
+        offset?: number;
+    };
 };
 
 export type IrisPluginConduitForm = {
@@ -83,15 +94,19 @@
     };
 });
 
-export type _IrisPluginConduitFieldDeepOptional<FieldSpec extends IrisPluginConduitField | undefined> = FieldSpec extends { type: "string" | "date" | "datetime" | "number" | "boolean" | "dropdown" } ? (
+export type _IrisPluginConduitFieldDeepOptional<FieldSpec extends IrisPluginConduitField | undefined, ArrayOfOptional extends boolean> = FieldSpec extends { type: "string" | "date" | "datetime" | "number" | "boolean" | "dropdown" } ? (
     Omit<FieldSpec, "optional"> & { optional: true; }
 ) : FieldSpec extends { type: "array" } ? (
-    Omit<FieldSpec, "of"> & { of: _IrisPluginConduitFieldDeepOptional<FieldSpec["of"]>; }
+    Omit<FieldSpec, "of" | "optional"> & {
+        of: ArrayOfOptional extends true ? _IrisPluginConduitFieldDeepOptional<FieldSpec["of"], ArrayOfOptional> : FieldSpec["of"];
+        optional: true;
+    }
 ) : FieldSpec extends { type: "object" } ? (
-    Omit<FieldSpec, "struct"> & {
+    Omit<FieldSpec, "struct" | "optional"> & {
         struct: {
-            [Property in keyof FieldSpec["struct"]]: _IrisPluginConduitFieldDeepOptional<FieldSpec["struct"][Property]>;
+            [Property in keyof FieldSpec["struct"]]: _IrisPluginConduitFieldDeepOptional<FieldSpec["struct"][Property], ArrayOfOptional>;
         };
+        optional: true;
     }
 ) : FieldSpec extends undefined ? (
     undefined
@@ -116,7 +131,7 @@
 ) : FieldSpec extends { type: "boolean" } ? (
     boolean
 ) : FieldSpec extends { type: "dropdown" } ? (
-    FieldSpec["options"][number]
+    FieldSpec["options"][number]["value"]
 ) : FieldSpec extends { type: "array" } ? (
     IrisPluginConduitFieldData<FieldSpec["of"]>[]
 ) : FieldSpec extends { type: "object" } ? (
@@ -141,7 +156,7 @@
     sections?: IrisPluginConduitPanelLayoutSection<PluginType>[];
 };
 
-export type IrisPluginConduitPanelLayoutSection<PluginType extends IrisPluginConduit> = IrisPluginConduitPanelLayoutSectionProperties<PluginType> | IrisPluginConduitPanelLayoutSectionTable<PluginType> | IrisPluginConduitPanelLayoutSectionRef<PluginType>;
+export type IrisPluginConduitPanelLayoutSection<PluginType extends IrisPluginConduit> = IrisPluginConduitPanelLayoutSectionProperties<PluginType> | IrisPluginConduitPanelLayoutSectionTable<PluginType> | IrisPluginConduitPanelLayoutSectionPanel<PluginType> | IrisPluginConduitPanelLayoutSectionForm<PluginType>;
 
 export type IrisPluginConduitPanelLayoutSectionCommon = {
     id: string;
@@ -168,16 +183,25 @@
         links?: {
             [key: string]: IrisPluginConduitPanelRef<PluginType>;
         };
+        forms?: IrisPluginConduitFormRef<PluginType>[];
     }[];
-    firstRow: number;
-    totalRows: number;
+    canSearch?: boolean;
+    pagination?: {
+        rowsPerPage: number;
+        totalRows: number;
+    };
 };
 
-export type IrisPluginConduitPanelLayoutSectionRef<PluginType extends IrisPluginConduit> = IrisPluginConduitPanelLayoutSectionCommon & {
-    type: "ref";
+export type IrisPluginConduitPanelLayoutSectionPanel<PluginType extends IrisPluginConduit> = IrisPluginConduitPanelLayoutSectionCommon & {
+    type: "panel";
     panel: IrisPluginConduitPanelRef<PluginType>;
 };
 
+export type IrisPluginConduitPanelLayoutSectionForm<PluginType extends IrisPluginConduit> = IrisPluginConduitPanelLayoutSectionCommon & {
+    type: "form";
+    form: IrisPluginConduitFormRef<PluginType>;
+};
+
 export type IrisPluginConduitPanelRef<PluginType extends IrisPluginConduit> = {
     id: string;
     args?: unknown;
@@ -186,6 +210,7 @@
 export type IrisPluginConduitFormRef<PluginType extends IrisPluginConduit> = {
     id: string;
     data?: unknown;
+    defaults?: unknown;
 };
 
 export type IrisPluginConduitPanelLayoutField = {
@@ -198,8 +223,11 @@
  * Helper utility for implementing an Iris Conduit server with strong type safety.
  */
 export function createIrisConduitServerImpl<PluginType extends IrisPluginConduit>(): {
-    panelRef: <PanelId extends keyof PluginType["panels"]>(id: PanelId, args?: Partial<IrisPluginConduitPanelArgs<PluginType["panels"][PanelId]["requiredArgs"]>>) => IrisPluginConduitPanelRef<PluginType>;
-    formRef: <FormId extends keyof PluginType["forms"]>(id: FormId, args?: IrisPluginConduitFieldData<_IrisPluginConduitFieldDeepOptional<PluginType["forms"][FormId]["data"]>>) => IrisPluginConduitFormRef<PluginType>;
+    panelRef: <PanelId extends keyof PluginType["panels"]>(id: PanelId, args: IrisPluginConduitPanelArgs<PluginType["panels"][PanelId]["requiredArgs"]>) => IrisPluginConduitPanelRef<PluginType>;
+    formRef: <FormId extends keyof PluginType["forms"]>(id: FormId, data?: {
+        data?: IrisPluginConduitFieldData<_IrisPluginConduitFieldDeepOptional<PluginType["forms"][FormId]["data"], false>>;
+        defaults?: IrisPluginConduitFieldData<_IrisPluginConduitFieldDeepOptional<PluginType["forms"][FormId]["data"], true>>;
+    }) => IrisPluginConduitFormRef<PluginType>;
 } {
     return {
         panelRef(id, args) {
@@ -208,10 +236,11 @@
                 args
             }
         },
-        formRef(id, data) {
+        formRef(id, { data, defaults } = {}) {
             return {
                 id: id as string,
-                data
+                data,
+                defaults
             }
         }
     };
diff --git a/packages/webui/src/app/app.css b/packages/webui/src/app/app.css
index f5f776b..8da88a0 100644
--- a/packages/webui/src/app/app.css
+++ b/packages/webui/src/app/app.css
@@ -2,10 +2,26 @@
 @config "../../tailwind.config.js";
 @source "../../../../node_modules/@ffxp/react-dom/lib";
 
-body, html {
+body,
+html {
     height: 100%;
 }
 
-button,[role=button] {
+button,
+[role=button] {
     cursor: pointer
+}
+
+@keyframes loadingImgAnimation {
+    from {
+        opacity: 0.9;
+    }
+
+    to {
+        opacity: 0.6;
+    }
+}
+
+#loadingImg {
+    animation: loadingImgAnimation 0.35s ease-in alternate infinite;
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/components/ErrorScreen.tsx b/packages/webui/src/app/components/ErrorScreen.tsx
new file mode 100644
index 0000000..d9e5cba
--- /dev/null
+++ b/packages/webui/src/app/components/ErrorScreen.tsx
@@ -0,0 +1,40 @@
+// Iris
+// Copyright (c) jottocraft 2025. All rights reserved.
+
+import { Icon } from "@ffxp/react-dom";
+import { YggdrasilStatus } from "@jottocraft/yggdrasil";
+
+import { useError } from "~/app/data/error";
+
+export function ErrorBox({ error, big }: { error: string | YggdrasilStatus<unknown>; big?: boolean; }) {
+    const message = error instanceof YggdrasilStatus ? error.statusMessage : error;
+    return (
+        <div className="p-4 roundness-xl bg-red-200 dark:bg-red-950 space-y-4">
+            <div className="flex flex-row items-center space-x-4">
+                <Icon className="text-2xl flex-shrink-0">error</Icon>
+                {error ? (
+                    <div className="space-y-2">
+                        {message ? <p className="text-lg font-medium">{message}</p> : null}
+                        {error instanceof YggdrasilStatus ? <p className="text-type-30">Error {error.status} {error.statusCode}</p> : null}
+                    </div>
+                ) : null}
+            </div>
+            {(error instanceof YggdrasilStatus) && error.statusCtx ? (
+                <pre className="text-type-60 text-xs font-mono">{JSON.stringify(error.statusCtx, undefined, 2)}</pre>
+            ) : null}
+        </div>
+    );
+}
+
+export default function ErrorScreen({ children }: { children?: React.ReactNode; }) {
+    const error = useError();
+    if (!error) return children ?? null;
+
+    return (
+        <div className="relative w-full h-full min-h-128 p-8">
+            <div className="w-full h-full roundness-3xl bg-linear-to-b from-red-500/5 to-ui-0 flex items-center justify-center">
+                <ErrorBox error={error} big />
+            </div>
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/packages/webui/src/app/components/Form/Field.tsx b/packages/webui/src/app/components/Form/Field.tsx
index 122654c..75a60a2 100644
--- a/packages/webui/src/app/components/Form/Field.tsx
+++ b/packages/webui/src/app/components/Form/Field.tsx
@@ -4,40 +4,42 @@
 import { Button, Dropdown, DropdownItem, Icon, Switch, TextInput } from "@ffxp/react-dom";
 import type { IrisPluginConduitField } from "@jottocraft/iris-sdk";
 
-export function createDefaultFieldData(spec: IrisPluginConduitField, refData?: any, forceRequired?: boolean) {
-    if (spec.optional && !forceRequired) return undefined;
+export function createInitialFieldData(spec: IrisPluginConduitField, refData?: any, refDefaults?: any, forceRequired?: boolean) {
+    const refInitialData = refData ?? refDefaults;
+
+    if (spec.optional && !forceRequired && !refInitialData) return undefined;
 
     if (spec.type === "string") {
-        return refData ?? "";
+        return refInitialData ?? "";
     } else if (spec.type === "number") {
-        return refData ?? 0;
+        return refInitialData ?? 0;
     } else if (spec.type === "date") {
-        return refData ?? new Date().toISOString().split("T")[0];
+        return refInitialData ?? new Date().toISOString().split("T")[0];
     } else if (spec.type === "datetime") {
-        return refData ?? new Date().toISOString();
+        return refInitialData ?? new Date().toISOString();
     } else if (spec.type === "boolean") {
-        return refData ?? false;
+        return refInitialData ?? false;
     } else if (spec.type === "dropdown") {
-        return refData ?? spec.options[0].value;
+        return refInitialData ?? spec.options[0].value;
     } else if (spec.type === "array") {
-        return refData ? refData.map((refItem: any) => createDefaultFieldData(spec.of, refItem)) : [];
+        return refInitialData ? refInitialData.map((refItem: any) => createInitialFieldData(spec.of, refData ? refItem : undefined, refData ? undefined : refItem, false)) : [];
     } else if (spec.type === "object") {
-        const defaultData: any = {};
+        const initialData: any = {};
         for (const key in spec.struct) {
-            defaultData[key] = createDefaultFieldData(spec.struct[key], refData ? refData[key] : undefined);
+            initialData[key] = createInitialFieldData(spec.struct[key], refData ? refData[key] : undefined, refDefaults ? refDefaults[key] : undefined, false);
         }
-        return defaultData;
+        return initialData;
     }
 }
 
-export default function Field({ path, data, refData, spec, patchFormData, controls }: { path: string; data: any; refData: any; spec: IrisPluginConduitField; patchFormData: (path: string, value: unknown) => void; controls?: React.ReactNode; }) {
+export default function Field({ path, data, refData, refDefaults, spec, patchFormData, controls }: { path: string; data: any; refData: any; refDefaults: any; spec: IrisPluginConduitField; patchFormData: (path: string, value: unknown) => void; controls?: React.ReactNode; }) {
     let fieldInput: React.ReactNode = null;
 
     if ((refData !== undefined) && (spec.type !== "object")) return null;
 
     if (data === undefined) {
         fieldInput = (
-            <Button onClick={() => patchFormData(path, createDefaultFieldData(spec, undefined, true))} iconLeading="add">Set value</Button>
+            <Button onClick={() => patchFormData(path, createInitialFieldData(spec, undefined, undefined, true))} iconLeading="add">Set value</Button>
         );
     } else if ((spec.type === "string") || (spec.type === "number") || (spec.type === "date") || (spec.type === "datetime")) {
         fieldInput = (
@@ -109,10 +111,10 @@
                                     patchFormData(path, newValue);
                                 }} iconLeading="delete" size="small" />
                             </div>
-                        )} path={`${path}[${i}]`} data={value} refData={refData !== undefined ? refData[i] : undefined} spec={spec.of} patchFormData={patchFormData} />
+                        )} path={`${path}[${i}]`} data={value} refData={refData !== undefined ? refData[i] : undefined} refDefaults={refDefaults !== undefined ? refDefaults[i] : undefined} spec={spec.of} patchFormData={patchFormData} />
                     );
                 })}
-                <Button iconLeading="add" onClick={() => patchFormData(path, [...data, createDefaultFieldData(spec.of)])}>Add item</Button>
+                <Button iconLeading="add" onClick={() => patchFormData(path, [...data, createInitialFieldData(spec.of)])}>Add item</Button>
             </div>
         );
     } else if (spec.type === "object") {
@@ -120,7 +122,7 @@
             <div className="space-y-6 border border-ui-20 p-4 roundness-xl">
                 {Object.keys(spec.struct).map(key => {
                     return (
-                        <Field path={`${path}.${key}`} data={data[key]} refData={refData !== undefined ? refData[key] : undefined} spec={spec.struct[key]} patchFormData={patchFormData} />
+                        <Field path={`${path}.${key}`} data={data[key]} refData={refData !== undefined ? refData[key] : undefined} refDefaults={refDefaults !== undefined ? refDefaults[key] : undefined} spec={spec.struct[key]} patchFormData={patchFormData} />
                     );
                 })}
             </div>
@@ -142,12 +144,14 @@
                         {spec.displayName ? <div>{spec.displayName}</div> : null}
                     </div>
                     <div className="flex flex-row items-center space-x-8">
-                        {spec.optional && (data !== undefined) ? (
-                            <Button onClick={() => patchFormData(path, undefined)} iconLeading="delete" size="small">Clear value</Button>
-                        ) : spec.optional ? (
-                            <div className="text-xs px-2 py-1 bg-ui-10 roundness">
-                                Optional
-                            </div>
+                        {spec.optional && (refData === undefined) ? (
+                            data !== undefined ? (
+                                <Button onClick={() => patchFormData(path, undefined)} iconLeading="delete" size="small">Clear value</Button>
+                            ) : (
+                                <div className="text-xs px-2 py-1 bg-ui-10 roundness">
+                                    Optional
+                                </div>
+                            )
                         ) : null}
                         {controls}
                     </div>
diff --git a/packages/webui/src/app/components/Form/index.tsx b/packages/webui/src/app/components/Form/index.tsx
index b323de0..0b54300 100644
--- a/packages/webui/src/app/components/Form/index.tsx
+++ b/packages/webui/src/app/components/Form/index.tsx
@@ -1,27 +1,27 @@
 // Iris
 // Copyright (c) jottocraft 2025
 
-import type { IrisPluginConduitField } from "@jottocraft/iris-sdk";
+import { YggdrasilStatus } from "@jottocraft/yggdrasil";
 import { Button, Icon } from "@ffxp/react-dom";
 import { useCallback, useMemo, useState } from "react";
 import { createPortal } from "react-dom";
 import set from "lodash/set";
 import clone from "lodash/clone";
 
-import Field, { createDefaultFieldData } from "~/app/components/Form/Field";
+import Field, { createInitialFieldData } from "~/app/components/Form/Field";
 import { usePanelNavigation } from "~/app/data/panels";
 import { usePlugins } from "~/app/data/root";
-import { YggdrasilStatus } from "@jottocraft/yggdrasil";
+import { ErrorBox } from "~/app/components/ErrorScreen";
 
-export default function Form({ ref, onFinished }: { ref: IrisWebuiFormRef; onFinished: () => void; }) {
+export default function Form({ inline, ref, onFinished }: { inline?: boolean; ref: IrisWebuiFormRef; onFinished: () => void; }) {
     const plugins = usePlugins()!;
     const panelNavigation = usePanelNavigation() as IrisWebuiPanelNavigationCtxActive;
     const form = plugins[panelNavigation.pluginId].plugin.forms[ref.id];
 
-    const defaultData = useMemo(() => form.data ? createDefaultFieldData(form.data, ref.data) : undefined, [form, ref]);
+    const initialData = useMemo(() => form.data ? createInitialFieldData(form.data, ref.data, ref.defaults, false) : undefined, [form, ref]);
 
     const [formData, setFormData] = useState<{ data: unknown; }>({
-        data: clone(defaultData)
+        data: clone(initialData)
     });
 
     const patchFormData = useCallback((path: string, value: unknown) => {
@@ -38,13 +38,51 @@
             try {
                 const data = await plugins[panelNavigation.pluginId].client().forms[ref.id](formData.data as any);
                 setFormResult(data);
-            } catch(e) {
+            } catch (e) {
                 if (!(e instanceof YggdrasilStatus)) throw e;
                 setFormResult(e);
             }
         })();
     }, [panelNavigation, ref, formData]);
 
+    console.log(ref, initialData, formData);
+
+    const formJsx = (
+        <div className="space-y-8">
+            {formResult ? (
+                <>
+                    <div>
+                        {formResult instanceof YggdrasilStatus ? (
+                            <ErrorBox error={formResult} />
+                        ) : (
+                            <p>{formResult}</p>
+                        )}
+                    </div>
+                    <div>
+                        <Button onClick={() => onFinished()}>Close</Button>
+                    </div>
+                </>
+            ) : (
+                <>
+                    <div>
+                        {form.data ? (
+                            <Field path="data" data={formData.data} refData={ref.data} refDefaults={ref.defaults} spec={form.data} patchFormData={patchFormData} />
+                        ) : (
+                            <p>This form doesn't take any input.</p>
+                        )}
+                    </div>
+                    <div>
+                        <Button onClick={submitForm} variant="primary">Submit</Button>
+                    </div>
+                </>
+            )}
+        </div>
+    );
+
+    if (inline) {
+        return formJsx;
+    }
+
     return createPortal((
         <div className="w-full h-full py-10 absolute top-0 left-0 z-99 bg-black/90 flex items-center justify-center">
             <div className="max-h-full overflow-auto w-2xl bg-ui-0 p-4 roundness-xl shadow-xl space-y-8">
@@ -57,25 +95,8 @@
                         <Button variant="borderless" onClick={() => onFinished()} iconLeading="close" size="small" />
                     </div>
                 </div>
-                {formResult ? (
-                    <div>
-                        <p>{formResult instanceof YggdrasilStatus ? formResult.statusCode : formResult}</p>
-                    </div>
-                ) : (
-                    <>
-                        <div>
-                            {form.data ? (
-                                <Field path="data" data={formData.data} refData={ref.data} spec={form.data} patchFormData={patchFormData} />
-                            ) : (
-                                <p>This form doesn't take any input.</p>
-                            )}
-                        </div>
-                        <div>
-                            <Button onClick={submitForm} variant="primary">Submit</Button>
-                        </div>
-                    </>
-                )}
-            </div >
+                {formJsx}
+            </div>
         </div >
     ), document.body);
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/components/Loading.tsx b/packages/webui/src/app/components/Loading.tsx
new file mode 100644
index 0000000..cc40064
--- /dev/null
+++ b/packages/webui/src/app/components/Loading.tsx
@@ -0,0 +1,31 @@
+// Iris
+// Copyright (c) jottocraft 2025
+
+import React, { useEffect, useState } from "react";
+
+function LoadingTextDots() {
+    let [numDots, setNumDots] = useState(0);
+    useEffect(() => {
+        const dotInt = setInterval(() => {
+            numDots++;
+            if (numDots > 3) numDots = 0;
+            setNumDots(numDots);
+        }, 500);
+        return () => clearInterval(dotInt);
+    }, []);
+
+    return <pre className="inline-block">{numDots === 1 ? ".  " : numDots === 2 ? ".. " : numDots === 3 ? "..." : "   "}</pre>;
+}
+
+export default function Loading({ children }: { children: string; }) {
+    return (
+        <div className="relative w-full h-full min-h-128 p-8">
+            <div className="w-full h-full roundness-3xl bg-linear-to-b from-ui-5 to-ui-0">
+                <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-30 w-32 h-32">
+                    <img id="loadingImg" className="w-full h-full" src="/webui/assets/favicon.png" />
+                </div>
+                {children ? <div className="flex flex-row items-center font-medium text-2xl absolute top-3/4 left-1/2 -translate-x-1/2 z-30"><span>{children}</span><LoadingTextDots /></div> : null}
+            </div>
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/packages/webui/src/app/components/Panel/index.tsx b/packages/webui/src/app/components/Panel/index.tsx
index bafc717..4a847d0 100644
--- a/packages/webui/src/app/components/Panel/index.tsx
+++ b/packages/webui/src/app/components/Panel/index.tsx
@@ -1,46 +1,69 @@
 // Iris
 // Copyright (c) jottocraft 2025
 
-import { Button, Icon } from "@ffxp/react-dom";
-import type { IrisPluginConduit, IrisPluginConduitPanelArgs, IrisPluginConduitPanelLayout } from "@jottocraft/iris-sdk";
+import { Button, Icon, Pivot } from "@ffxp/react-dom";
+import type { IrisPluginConduit, IrisPluginConduitPanelArgs, IrisPluginConduitPanelLayout, IrisPluginConduitPanelLayoutSection } from "@jottocraft/iris-sdk";
 import { useEffect, useState } from "react";
 import { useSearchParams } from "react-router";
+import { YggdrasilStatus } from "@jottocraft/yggdrasil";
 
 import { usePlugins } from "~/app/data/root";
 import PanelSection from "~/app/components/PanelSection";
 import { usePanelNavigation } from "~/app/data/panels";
 import Form from "~/app/components/Form";
+import Loading from "~/app/components/Loading";
+import { useError } from "~/app/data/error";
+import ErrorScreen from "~/app/components/ErrorScreen";
 
 export default function Panel() {
     const panelNavigation = usePanelNavigation()!;
     const plugins = usePlugins()!;
     const [activeForm, setActiveForm] = useState<IrisWebuiFormRef | null>(null);
+    const [selectedSection, setSelectedSection] = useState<string | null>(null);
+    const error = useError();
+
+    useEffect(() => {
+        setActiveForm(null);
+        setSelectedSection(null);
+    }, [panelNavigation.activeRef]);
+
+    if (error) {
+        return (
+            <ErrorScreen />
+        );
+    }
 
     if (!panelNavigation.activeRef) {
-        return (
-            <p>Please select a panel to open from the sidebar.</p>
-        );
+        return null;
     }
 
     if (!panelNavigation.layout) {
         return (
-            <p>Loading panel {panelNavigation.activeRef.id}...</p>
+            <Loading>
+                {`Loading panel ${panelNavigation.activeRef.id}`}
+            </Loading>
         );
     }
 
     const plugin = plugins[panelNavigation.pluginId];
     const layout = panelNavigation.layout;
+    const activeSection = layout.sections?.length === 1 ? layout.sections[0] : (layout.sections?.find(section => (section.id === selectedSection)) ?? null);
 
     return (
         <>
             {activeForm ? (
-                <Form ref={activeForm} onFinished={() => setActiveForm(null)} />
+                <Form ref={activeForm} onFinished={() => {
+                    setActiveForm(null);
+                    panelNavigation.refreshLayout();
+                }} />
             ) : null}
-            <div className="space-y-8">
-                <div className="flex flex-row items-center space-x-3 text-4xl font-semibold">
-                    {layout.icon ? <Icon>{layout.icon}</Icon> : null}
-                    <h1>{layout.displayName}</h1>
-                </div>
+            <div className={panelNavigation.root ? "space-y-8" : "space-y-4"}>
+                {panelNavigation.root ? (
+                    <div className="flex flex-row items-center space-x-3 text-4xl font-semibold">
+                        {layout.icon ? <Icon>{layout.icon}</Icon> : null}
+                        <h1>{layout.displayName}</h1>
+                    </div>
+                ) : null}
                 {layout.forms ? (
                     <div>
                         {layout.forms?.map((ref) => {
@@ -48,32 +71,44 @@
 
                             return (
                                 <div className="inline-block m-1">
-                                    <Button onClick={() => setActiveForm(ref)} iconLeading={form.icon}>{form.displayName}</Button>
+                                    <Button size={panelNavigation.root ? "default" : "small"} onClick={() => setActiveForm(ref)} iconLeading={form.icon}>{form.displayName}</Button>
                                 </div>
                             );
                         })}
                     </div>
                 ) : null}
                 {layout.main ? (
-                    <div>
+                    <div className="flex flex-row items-center flex-wrap">
                         {layout.main.map(item => (
-                            <div className="inline-block space-y-2 m-1 p-4 bg-ui-5 roundness-xl">
-                                <div className="flex flex-row items-center space-x-2 text-xl font-medium">
+                            <div className={"min-h-24 m-1 bg-ui-5 " + (panelNavigation.root ? "p-4 space-y-2 roundness-xl" : "p-3 space-y-1 roundness-lg")}>
+                                <div className={"flex flex-row items-center space-x-2 font-medium " + (panelNavigation.root ? "text-xl" : "text-lg")}>
                                     {item.icon ? <Icon>{item.icon}</Icon> : null}
                                     <div>{item.displayName}</div>
                                 </div>
-                                <div>
-                                    <div>{item.value}</div>
-                                </div>
+                                <div className={panelNavigation.root ? "text-base" : "text-sm"}>{item.value}</div>
                             </div>
                         ))}
                     </div>
                 ) : null}
                 {layout.sections ? (
                     <div>
-                        {layout.sections.map(section => (
-                            <PanelSection section={section} />
-                        ))}
+                        {layout.sections.length > 1 ? (
+                            <Pivot tabs={layout.sections.map(section => ({
+                                title: section.displayName,
+                                icon: section.icon,
+                                active: selectedSection === section.id,
+                                onClick: () => {
+                                    if (selectedSection === section.id) {
+                                        setSelectedSection(null);
+                                    } else {
+                                        setSelectedSection(section.id);
+                                    }
+                                }
+                            }))} />
+                        ) : null}
+                        {activeSection ? (
+                            <PanelSection section={activeSection} />
+                        ) : null}
                     </div>
                 ) : null}
             </div>
diff --git a/packages/webui/src/app/components/PanelNavigator.tsx b/packages/webui/src/app/components/PanelNavigator.tsx
index 16bcb25..d42f820 100644
--- a/packages/webui/src/app/components/PanelNavigator.tsx
+++ b/packages/webui/src/app/components/PanelNavigator.tsx
@@ -1,33 +1,50 @@
 // Iris
 // Copyright (c) jottocraft 2025
 
-import type { IrisPluginConduit, IrisPluginConduitPanelLayout } from "@jottocraft/iris-sdk";
+import type { IrisPluginConduitPanelState, IrisPluginConduit, IrisPluginConduitPanelLayout, IrisPluginConduitPanelSectionState } from "@jottocraft/iris-sdk";
+import { YggdrasilStatus } from "@jottocraft/yggdrasil";
 import { useCallback, useEffect, useState } from "react";
 import { useNavigate } from "react-router";
 
 import { panelNavigationCtx, panelRefHref } from "~/app/data/panels";
 import { usePlugins } from "~/app/data/root";
+import { ErrorProvider } from "../data/error";
 
-export default function PanelNavigator({ children }: { children?: React.ReactNode; }) {
+export default function PanelNavigator({ root, initialPluginId, initialRef, children }: { initialPluginId?: string; initialRef?: IrisWebuiPanelRef; root: boolean; children?: React.ReactNode; }) {
     const routerNavigate = useNavigate();
     const plugins = usePlugins()!;
 
-    const [pluginId, setPluginId] = useState<string | null>(null);
-    const [activeRef, setActiveRef] = useState<IrisWebuiPanelRef | null>(null);
+    const [pluginId, setPluginId] = useState<string | null>(initialPluginId ?? null);
+    const [activeRef, setActiveRef] = useState<IrisWebuiPanelRef | null>(initialRef ?? null);
+    const [activeState, setActiveState] = useState<IrisPluginConduitPanelState<IrisPluginConduit> | null>(null);
     const [layout, setLayout] = useState<IrisPluginConduitPanelLayout<IrisPluginConduit> | null>(null);
     const [breadcrumbs, setBreadcrumbs] = useState<IrisWebuiPanelBreadcrumb[]>([]);
+    const [error, setError] = useState<null | string | YggdrasilStatus<unknown>>(null);
 
     useEffect(() => {
-        if (!activeRef) return;
+        if (!activeRef || !root) return;
         routerNavigate(panelRefHref(pluginId!, activeRef));
-    }, [pluginId, activeRef, routerNavigate]);
+    }, [pluginId, activeRef, routerNavigate, root]);
 
     const fetchLayout = useCallback(() => {
+        setLayout(null);
+        setError(null);
+
         (async () => {
-            const data = await plugins[pluginId!].client().panels[activeRef!.id]((activeRef!.args ?? {}) as { [key: string]: string; });
-            setLayout(data);
+            try {
+                if (!(pluginId! in plugins))  {
+                    setError("Unknown plugin " + pluginId);
+                    return;
+                }
+
+                const data = await plugins[pluginId!].client().panels[activeRef!.id]((activeRef!.args ?? {}) as { [key: string]: string; }, activeState!);
+                setLayout(data);
+            } catch (e) {
+                if (!(e instanceof YggdrasilStatus)) throw e;
+                setError(e);
+            }
         })();
-    }, [plugins, pluginId, activeRef]);
+    }, [plugins, pluginId, activeRef, activeState]);
 
     useEffect(() => {
         if (!activeRef) return;
@@ -38,12 +55,13 @@
     const navigate = useCallback((ref: IrisWebuiPanelRef, e?: React.MouseEvent) => {
         e?.preventDefault();
 
+        setActiveRef(ref);
+        setActiveState({ sections: {} });
         setBreadcrumbs([...breadcrumbs, {
             displayName: layout?.displayName ?? activeRef!.id,
             icon: layout?.icon ?? undefined,
             ref: activeRef!
         }]);
-        setActiveRef(ref);
     }, [activeRef, layout, breadcrumbs]);
 
     const back = useCallback((e?: React.MouseEvent) => {
@@ -51,6 +69,7 @@
         if (!breadcrumbs.length) return;
 
         setActiveRef(breadcrumbs[breadcrumbs.length - 1].ref);
+        setActiveState({ sections: {} });
         setBreadcrumbs(breadcrumbs.slice(0, -1));
     }, [breadcrumbs]);
 
@@ -59,36 +78,59 @@
         if (!breadcrumbs.length) return;
 
         setActiveRef(breadcrumbs[index].ref);
+        setActiveState({ sections: {} });
         setBreadcrumbs(breadcrumbs.slice(0, index));
     }, [breadcrumbs]);
 
     const startNavigation = useCallback((pluginId: string, ref: IrisWebuiPanelRef, e?: React.MouseEvent) => {
         setPluginId(pluginId);
+
         setActiveRef(ref);
+        setActiveState({ sections: {} });
         setBreadcrumbs([]);
     }, []);
 
-    const refreshLayout = useCallback(() => {
-        setLayout(null);
-        fetchLayout();
-    }, [fetchLayout]);
+    useEffect(() => {
+        if (!initialPluginId || !initialRef) return;
+        startNavigation(initialPluginId, initialRef);
+    }, [initialPluginId, initialRef, startNavigation]);
+
+    const patchActiveSectionState = useCallback((sectionId: string, state: IrisPluginConduitPanelSectionState<IrisPluginConduit>) => {
+        setActiveState({
+            ...activeState,
+            sections: {
+                ...activeState?.sections,
+                [sectionId]: {
+                    ...activeState?.sections?.[sectionId],
+                    ...state
+                }
+            }
+        });
+    }, [activeState]);
 
     return (
-        <panelNavigationCtx.Provider value={activeRef ? {
-            activeRef,
-            layout,
-            pluginId: pluginId!,
-            breadcrumbs,
-            navigate,
-            back,
-            goToBreadcrumb,
-            startNavigation,
-            refreshLayout
-        } : {
-            activeRef,
-            startNavigation
-        }}>
-            {children}
-        </panelNavigationCtx.Provider>
+        <ErrorProvider error={error}>
+            <panelNavigationCtx.Provider value={activeRef ? {
+                activeRef,
+                activeState: activeState!,
+                layout,
+                pluginId: pluginId!,
+                breadcrumbs,
+                navigate,
+                back,
+                goToBreadcrumb,
+                startNavigation,
+                refreshLayout: fetchLayout,
+                setActiveState,
+                patchActiveSectionState,
+                root
+            } : {
+                activeRef,
+                startNavigation,
+                root
+            }}>
+                {children}
+            </panelNavigationCtx.Provider>
+        </ErrorProvider>
     );
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/components/PanelSection/PanelSectionForm.tsx b/packages/webui/src/app/components/PanelSection/PanelSectionForm.tsx
new file mode 100644
index 0000000..c851c5b
--- /dev/null
+++ b/packages/webui/src/app/components/PanelSection/PanelSectionForm.tsx
@@ -0,0 +1,19 @@
+// Iris
+// Copyright (c) jottocraft 2025
+
+import type { IrisPluginConduit, IrisPluginConduitPanelLayoutSectionForm } from "@jottocraft/iris-sdk";
+
+import Form from "~/app/components/Form";
+import { usePanelNavigation } from "~/app/data/panels";
+
+export default function PanelSectionForm({ section }: { section: IrisPluginConduitPanelLayoutSectionForm<IrisPluginConduit>; }) {
+    const panelNavigation = usePanelNavigation() as IrisWebuiPanelNavigationCtxActive;
+
+    return (
+        <div className="py-8">
+            <Form inline ref={section.form} onFinished={() => {
+                panelNavigation.refreshLayout();
+            }} />
+        </div>
+    );
+}
\ No newline at end of file
diff --git a/packages/webui/src/app/components/PanelSection/PanelSectionPanel.tsx b/packages/webui/src/app/components/PanelSection/PanelSectionPanel.tsx
new file mode 100644
index 0000000..a998bdd
--- /dev/null
+++ b/packages/webui/src/app/components/PanelSection/PanelSectionPanel.tsx
@@ -0,0 +1,33 @@
+// Iris
+// Copyright (c) jottocraft 2025
+
+import type { IrisPluginConduit, IrisPluginConduitPanelLayoutSectionPanel } from "@jottocraft/iris-sdk";
+import { useEffect } from "react";
+
+import { usePanelNavigation } from "~/app/data/panels";
+import PanelNavigator from "~/app/components/PanelNavigator";
+import Panel from "~/app/components/Panel";
+import PanelBreadcrumbs from "~/app/components/PanelBreadcrumbs";
+
+export default function PanelSectionPanel({ section }: { section: IrisPluginConduitPanelLayoutSectionPanel<IrisPluginConduit>; }) {
+    const panelNavigation = usePanelNavigation() as IrisWebuiPanelNavigationCtxActive;
+
+    useEffect(() => {
+        if (!panelNavigation.root) {
+            panelNavigation.navigate(section.panel);
+        }
+    }, [panelNavigation.root, section.panel]);
+
+    if (!panelNavigation.root) {
+        return null;
+    }
+
+    return (
+        <PanelNavigator root={false} initialPluginId={panelNavigation.pluginId} initialRef={section.panel}>
+            <div className="py-2 space-y-4">
+                <PanelBreadcrumbs />
+                <Panel />
+            </div>
+        </PanelNavigator>
+    );
+}
\ No newline at end of file
diff --git a/packages/webui/src/app/components/PanelSection/PanelSectionProperties.tsx b/packages/webui/src/app/components/PanelSection/PanelSectionProperties.tsx
index 1668d1c..4f05e9c 100644
--- a/packages/webui/src/app/components/PanelSection/PanelSectionProperties.tsx
+++ b/packages/webui/src/app/components/PanelSection/PanelSectionProperties.tsx
@@ -1,10 +1,29 @@
 // Iris
 // Copyright (c) jottocraft 2025
 
+import { Icon } from "@ffxp/react-dom";
 import type { IrisPluginConduit, IrisPluginConduitPanelLayoutSectionProperties } from "@jottocraft/iris-sdk";
 
 export default function PanelSectionProperties({ section }: { section: IrisPluginConduitPanelLayoutSectionProperties<IrisPluginConduit>; }) {
     return (
-        <p>TODO</p>
+        <div className="w-full overflow-auto">
+            <table>
+                <tbody>
+                    {section.properties.map(property => (
+                        <tr>
+                            <td className="p-2">
+                                <div className="flex flex-row flex-shrink-0 items-center space-x-1 text-type-10 font-medium">
+                                    {property.icon ? <Icon>{property.icon}</Icon> : null}
+                                    <div className="whitespace-nowrap">{property.displayName}</div>
+                                </div>
+                            </td>
+                            <td className="p-2">
+                                <div className="text-sm text-type-20">{property.value}</div>
+                            </td>
+                        </tr>
+                    ))}
+                </tbody>
+            </table>
+        </div>
     );
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/components/PanelSection/PanelSectionRef.tsx b/packages/webui/src/app/components/PanelSection/PanelSectionRef.tsx
deleted file mode 100644
index c889178..0000000
--- a/packages/webui/src/app/components/PanelSection/PanelSectionRef.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-// Iris
-// Copyright (c) jottocraft 2025
-
-import type { IrisPluginConduit, IrisPluginConduitPanelLayoutSectionRef } from "@jottocraft/iris-sdk";
-
-export default function PanelSectionRef({ section }: { section: IrisPluginConduitPanelLayoutSectionRef<IrisPluginConduit>; }) {
-    return (
-        <p>TODO</p>
-    );
-}
\ No newline at end of file
diff --git a/packages/webui/src/app/components/PanelSection/PanelSectionTable.tsx b/packages/webui/src/app/components/PanelSection/PanelSectionTable.tsx
index 26229e4..50c40d1 100644
--- a/packages/webui/src/app/components/PanelSection/PanelSectionTable.tsx
+++ b/packages/webui/src/app/components/PanelSection/PanelSectionTable.tsx
@@ -1,46 +1,165 @@
 // Iris
 // Copyright (c) jottocraft 2025
 
+import { Button, Icon, Link, TextInput } from "@ffxp/react-dom";
 import type { IrisPluginConduit, IrisPluginConduitPanelLayoutSectionTable } from "@jottocraft/iris-sdk";
-import { Link, useParams } from "react-router";
+import { useEffect, useState } from "react";
+import { Link as RouterLink } from "react-router";
 
 import { panelRefHref, usePanelNavigation } from "~/app/data/panels";
+import { usePlugins } from "~/app/data/root";
+import Form from "~/app/components/Form";
 
 export default function PanelSectionTable({ section }: { section: IrisPluginConduitPanelLayoutSectionTable<IrisPluginConduit>; }) {
-    const params = useParams();
     const panelNavigation = usePanelNavigation() as IrisWebuiPanelNavigationCtxActive;
+    const plugins = usePlugins()!;
+    const [activeForm, setActiveForm] = useState<IrisWebuiFormRef | null>(null);
+
+    useEffect(() => {
+        setActiveForm(null);
+    }, [section]);
+
+    const plugin = plugins[panelNavigation.pluginId];
+
+    let pagination;
+    if (section.pagination && section.rows.length) {
+        const offset = panelNavigation.activeState.sections[section.id]?.table?.offset ?? 0;
+        const totalPages = Math.ceil(section.pagination.totalRows / section.pagination.rowsPerPage);
+        const activePage = (offset / section.pagination.rowsPerPage) + 1;
+        const firstVisiblePage = Math.max(activePage - 2, 1);
+        const lastVisiblePage = Math.min(activePage + 2, totalPages);
+        const visiblePages: number[] = [];
+        for (let i = firstVisiblePage; i <= lastVisiblePage; ++i) visiblePages.push(i);
+
+        pagination = {
+            firstRow: offset,
+            rowsPerPage: section.pagination.rowsPerPage,
+            totalRows: section.pagination.totalRows,
+            totalPages,
+            activePage,
+            visiblePages
+        };
+    } else {
+        pagination = null;
+    }
 
     return (
-        <div>
-            <table>
-                <thead>
-                    <tr>
-                        {section.columns.map(column => (
-                            <th>
-                                {column.displayName}
-                            </th>
-                        ))}
-                    </tr>
-                </thead>
-                <tbody>
-                    {section.rows.map(row => (
-                        <tr>
-                            {section.columns.map(column => {
-                                const link = row.links?.[column.key];
-                                return (
-                                    <td>
-                                        {link ? (
-                                            <Link to={panelRefHref(panelNavigation.pluginId, link)} onClick={(e) => panelNavigation.navigate(link, e)}>{row.vals[column.key] ?? null}</Link>
-                                        ) : (
-                                            row.vals[column.key] ?? null
-                                        )}
-                                    </td>
-                                );
-                            })}
-                        </tr>
-                    ))}
-                </tbody>
-            </table>
-        </div>
+        <>
+            {activeForm ? (
+                <Form ref={activeForm} onFinished={() => {
+                    setActiveForm(null);
+                    panelNavigation.refreshLayout();
+                }} />
+            ) : null}
+            <div className="space-y-4">
+                {section.canSearch ? (
+                    <div className="flex flex-row items-center justify-end">
+                        <TextInput defaultValue={panelNavigation.activeState.sections[section.id]?.table?.search ?? ""} onKeyDown={e => {
+                            const search = e.currentTarget.value || undefined;
+                            if (e.key === "Enter") {
+                                panelNavigation.patchActiveSectionState(section.id, {
+                                    table: {
+                                        ...panelNavigation.activeState.sections[section.id]?.table,
+                                        search,
+                                        offset: 0
+                                    }
+                                });
+                            }
+                        }} placeholder="Search" icon="search" />
+                    </div>
+                ) : null}
+                <div className="w-full overflow-auto">
+                    <table className="w-full">
+                        <thead>
+                            <tr className="border-b border-ui-30 text-left">
+                                {section.columns.map(column => (
+                                    <th className="p-2 whitespace-nowrap font-medium">
+                                        <div className="flex flex-row items-center flex-nowrap space-x-1">
+                                            {column.icon ? <Icon>{column.icon}</Icon> : null}
+                                            <div>{column.displayName}</div>
+                                        </div>
+                                    </th>
+                                ))}
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {section.rows.map((row, i) => (
+                                <tr className={i % 2 === 0 ? undefined : "bg-ui-5"}>
+                                    {section.columns.map(column => {
+                                        const link = row.links?.[column.key];
+                                        return (
+                                            <td className="p-2 whitespace-nowrap text-sm text-type-20">
+                                                {link ? (
+                                                    <RouterLink to={panelRefHref(panelNavigation.pluginId, link)} onClick={(e) => panelNavigation.navigate(link, e)}>
+                                                        <Link>{row.vals[column.key] ?? null}</Link>
+                                                    </RouterLink>
+                                                ) : (
+                                                    row.vals[column.key] ?? null
+                                                )}
+                                            </td>
+                                        );
+                                    })}
+                                    {row.forms ? (
+                                        <td>
+                                            {row.forms.map(ref => {
+                                                const form = plugin.plugin.forms[ref.id];
+
+                                                return (
+                                                    <div className="inline-block m-1">
+                                                        <Button size="small" onClick={() => setActiveForm(ref)} iconLeading={form.icon}>{form.displayName}</Button>
+                                                    </div>
+                                                );
+                                            })}
+                                        </td>
+                                    ) : null}
+                                </tr>
+                            ))}
+                        </tbody>
+                    </table>
+                </div>
+                {pagination ? (
+                    <div className="flex flex-row items-center justify-between">
+                        <div className="text-sm text-type-30">Showing rows {pagination.firstRow + 1}-{pagination.firstRow + section.rows.length} of {pagination.totalRows}</div>
+                        <div className="flex flex-row items-center">
+                            {(pagination.visiblePages[0] !== 1) ? (
+                                <>
+                                    <Icon className="text-type-60">more_horiz</Icon>
+                                    <div className="px-1" />
+                                </>
+                            ) : null}
+                            {pagination.visiblePages.map(page => (
+                                <Button onClick={() => {
+                                    panelNavigation.patchActiveSectionState(section.id, {
+                                        table: {
+                                            ...panelNavigation.activeState.sections[section.id]?.table,
+                                            offset: (page - 1) * pagination.rowsPerPage
+                                        }
+                                    });
+                                }} variant={page === pagination.activePage ? "primary" : "borderless"} size="small">{String(page)}</Button>
+                            ))}
+                            {(pagination.visiblePages[pagination.visiblePages.length - 1] !== pagination.totalPages) ? (
+                                <>
+                                    <div className="px-1" />
+                                    <Icon className="text-type-60">more_horiz</Icon>
+                                </>
+                            ) : null}
+                            <div className="px-4" />
+                            <TextInput onKeyDown={e => {
+                                const val = Number(e.currentTarget.value);
+                                if ((e.key === "Enter") && !isNaN(val) && (val >= 1) && (val <= pagination.totalPages)) {
+                                    panelNavigation.patchActiveSectionState(section.id, {
+                                        table: {
+                                            ...panelNavigation.activeState.sections[section.id]?.table,
+                                            offset: (val - 1) * pagination.rowsPerPage
+                                        }
+                                    });
+                                    e.currentTarget.value = "";
+                                }
+                            }} type="number" placeholder="Jump to" width={100} />
+                        </div>
+                    </div>
+                ) : null}
+            </div>
+        </>
     );
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/components/PanelSection/index.tsx b/packages/webui/src/app/components/PanelSection/index.tsx
index af483f0..f79aaae 100644
--- a/packages/webui/src/app/components/PanelSection/index.tsx
+++ b/packages/webui/src/app/components/PanelSection/index.tsx
@@ -5,7 +5,8 @@
 
 import PanelSectionProperties from "~/app/components/PanelSection/PanelSectionProperties";
 import PanelSectionTable from "~/app/components/PanelSection/PanelSectionTable";
-import PanelSectionRef from "~/app/components/PanelSection/PanelSectionRef";
+import PanelSectionPanel from "~/app/components/PanelSection/PanelSectionPanel";
+import PanelSectionForm from "~/app/components/PanelSection/PanelSectionForm";
 
 export default function PanelSection({ section }: { section: IrisPluginConduitPanelLayoutSection<IrisPluginConduit>; }) {
     if (section.type === "properties") {
@@ -16,9 +17,13 @@
         return (
             <PanelSectionTable section={section} />
         );
-    } else if (section.type === "ref") {
+    } else if (section.type === "panel") {
         return (
-            <PanelSectionRef section={section} />
+            <PanelSectionPanel section={section} />
+        );
+    } else  if (section.type === "form") {
+        return (
+            <PanelSectionForm section={section} />
         );
     } else {
         throw new Error("Unrecognized panel layout section type " + (section as any).type);
diff --git a/packages/webui/src/app/components/Shell.tsx b/packages/webui/src/app/components/Shell.tsx
index 62267db..f2e04ce 100644
--- a/packages/webui/src/app/components/Shell.tsx
+++ b/packages/webui/src/app/components/Shell.tsx
@@ -2,7 +2,7 @@
 // Copyright (c) jottocraft 2025
 
 import { Button, NavbarLayout, NavbarTitle, SidebarHeader, SidebarItem, SidebarItemGroup, SidebarItems, SidebarLayout, SidebarTitle, useThemeEngine } from "@ffxp/react-dom";
-import { useEffect } from "react";
+import { useEffect, useMemo } from "react";
 import { Link, Outlet, useLocation, useParams, useSearchParams } from "react-router";
 
 import IRIS_SITE from "~/IRIS_SITE";
@@ -10,26 +10,15 @@
 import { isAtPanel, panelRefHref, usePanelNavigation } from "~/app/data/panels";
 import PanelNavigator from "~/app/components/PanelNavigator";
 import PanelBreadcrumbs from "~/app/components/PanelBreadcrumbs";
+import Loading from "~/app/components/Loading";
+import { useError } from "~/app/data/error";
+import ErrorScreen from "~/app/components/ErrorScreen";
 
 function ShellLayout({ children }: { children?: React.ReactNode; }) {
     const theme = useThemeEngine();
     const plugins = usePlugins()!;
     const auth = useAuth();
     const panelNavigation = usePanelNavigation()!;
-    const routerParams = useParams();
-    const [searchParams] = useSearchParams();
-
-    useEffect(() => {
-        if (!panelNavigation.activeRef && (routerParams.pluginId && routerParams.panelId)) {
-            const args: { [key: string]: string } = {};
-            for (const [k, v] of searchParams.entries()) args[k] = v;
-
-            panelNavigation.startNavigation(routerParams.pluginId, {
-                id: routerParams.panelId,
-                args
-            });
-        }
-    }, [routerParams, searchParams, panelNavigation]);
 
     return (
         <NavbarLayout left={(
@@ -44,7 +33,7 @@
         )}
             right={(
                 <>
-                    <Button onClick={() => {
+                    <Button variant="borderless" onClick={() => {
                         if (window.confirm("Do you want to sign out?")) {
                             window.localStorage.removeItem("jwt");
                             window.location.reload();
@@ -88,6 +77,34 @@
 export default function Shell({ children }: { children?: React.ReactNode; }) {
     const location = useLocation();
     const plugins = usePlugins();
+    const error = useError();
+
+    const routerParams = useParams();
+    const [searchParams] = useSearchParams();
+
+    const { initialPluginId, initialRef } = useMemo((): {
+        initialPluginId?: string;
+        initialRef?: IrisWebuiPanelRef;
+    } => {
+        if (!routerParams.pluginId || !routerParams.panelId) return {};
+
+        const args: { [key: string]: string } = {};
+        for (const [k, v] of searchParams.entries()) args[k] = v;
+
+        return {
+            initialPluginId: routerParams.pluginId,
+            initialRef: {
+                id: routerParams.panelId,
+                args
+            }
+        }
+    }, []);
+
+    if (error) {
+        return (
+            <ErrorScreen />
+        );
+    }
 
     if (location.pathname === "/login") {
         return (
@@ -97,12 +114,14 @@
 
     if (!plugins) {
         return (
-            <p>Loading plugins...</p>
+            <Loading>
+                Loading plugins
+            </Loading>
         );
     }
 
     return (
-        <PanelNavigator>
+        <PanelNavigator initialPluginId={initialPluginId} initialRef={initialRef} root={true}>
             <ShellLayout>
                 {children}
             </ShellLayout>
diff --git a/packages/webui/src/app/data/error.tsx b/packages/webui/src/app/data/error.tsx
new file mode 100644
index 0000000..94e06db
--- /dev/null
+++ b/packages/webui/src/app/data/error.tsx
@@ -0,0 +1,19 @@
+// Iris
+// Copyright (c) jottocraft 2025
+
+import type { YggdrasilStatus } from "@jottocraft/yggdrasil";
+import { createContext, useContext } from "react";
+
+export const errorCtx = createContext<YggdrasilStatus<unknown> | string | null>(null);
+export function useError() {
+    return useContext(errorCtx);
+}
+
+export function ErrorProvider({ error, children }: { error?: YggdrasilStatus<unknown> | string | null; children?: React.ReactNode; }) {
+    if (!error) return children;
+    return (
+        <errorCtx.Provider value={error}>
+            {children}
+        </errorCtx.Provider>
+    );
+}
\ No newline at end of file
diff --git a/packages/webui/src/app/data/panels.ts b/packages/webui/src/app/data/panels.ts
index 26e99f0..1f03d36 100644
--- a/packages/webui/src/app/data/panels.ts
+++ b/packages/webui/src/app/data/panels.ts
@@ -14,7 +14,7 @@
     if (ref.args) {
         const linkParams = new URLSearchParams();
         for (const arg in ref.args) {
-            if (ref.args[arg]) linkParams.set(arg, ref.args[arg]);
+            if ((ref.args as { [key: string]: string; })[arg]) linkParams.set(arg, (ref.args as { [key: string]: string; })[arg]);
         }
         linkHref = linkHref + "?" + linkParams.toString();
     }
diff --git a/packages/webui/src/app/root.tsx b/packages/webui/src/app/root.tsx
index c96602f..a157f47 100644
--- a/packages/webui/src/app/root.tsx
+++ b/packages/webui/src/app/root.tsx
@@ -3,7 +3,7 @@
 
 import { BodyWrapper, Container, HeadWrapper, HTMLWrapper, Link, Root, ThemeEngine } from "@ffxp/react-dom";
 import type { IrisAuthCtx, IrisPluginConduit, IrisPluginConduitConduitServerTypes, IrisPluginCtx } from "@jottocraft/iris-sdk";
-import { createConduitClientSideClient } from "@jottocraft/yggdrasil";
+import { createConduitClientSideClient, YggdrasilStatus } from "@jottocraft/yggdrasil";
 import { useEffect, useState } from "react";
 import {
     isRouteErrorResponse,
@@ -20,6 +20,7 @@
 import Shell from "~/app/components/Shell";
 import { authContext, pluginContext } from "~/app/data/root";
 import IRIS_SITE from "~/IRIS_SITE";
+import { ErrorProvider } from "./data/error";
 
 export default function App() {
     const location = useLocation();
@@ -67,6 +68,7 @@
     }, [auth, location.pathname]);
 
     const [plugins, setPlugins] = useState<null | IrisPluginCtx>(null);
+    const [error, setError] = useState<null | YggdrasilStatus<unknown>>(null);
     useEffect(() => {
         if (!auth) return;
 
@@ -91,7 +93,12 @@
             }
         })).then(() => {
             setPlugins(plugins);
-        }).catch(e => { throw e; });
+            setError(null);
+        }).catch(e => {
+            if (!(e instanceof YggdrasilStatus)) throw e;
+            setPlugins(null);
+            setError(e);
+        });
     }, [auth]);
 
     return (
@@ -132,18 +139,20 @@
                         <Links />
                     </HeadWrapper>
                     <BodyWrapper>
-                        <authContext.Provider value={auth}>
-                            <pluginContext.Provider value={plugins}>
-                                <Shell>
-                                    <Outlet />
-                                </Shell>
-                            </pluginContext.Provider>
-                        </authContext.Provider>
+                        <ErrorProvider error={error}>
+                            <authContext.Provider value={auth}>
+                                <pluginContext.Provider value={plugins}>
+                                    <Shell>
+                                        <Outlet />
+                                    </Shell>
+                                </pluginContext.Provider>
+                            </authContext.Provider>
+                        </ErrorProvider>
                         <ScrollRestoration />
                         <Scripts />
                     </BodyWrapper>
                 </HTMLWrapper>
             </ThemeEngine>
-        </Root>
+        </Root >
     );
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/routes/index.tsx b/packages/webui/src/app/routes/index.tsx
index 89f6ddd..85e5dd5 100644
--- a/packages/webui/src/app/routes/index.tsx
+++ b/packages/webui/src/app/routes/index.tsx
@@ -2,9 +2,5 @@
 // Copyright (c) jottocraft 2025
 
 export default function Home() {
-    return (
-        <div>
-            <p>Hello world!</p>
-        </div>
-    );
+    return null;
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/routes/login.tsx b/packages/webui/src/app/routes/login.tsx
index f63ce03..79359ec 100644
--- a/packages/webui/src/app/routes/login.tsx
+++ b/packages/webui/src/app/routes/login.tsx
@@ -5,6 +5,9 @@
 import { useEffect, useState } from "react";
 
 import IRIS_SITE from "~/IRIS_SITE";
+import ErrorScreen from "~/app/components/ErrorScreen";
+import Loading from "~/app/components/Loading";
+import { ErrorProvider } from "~/app/data/error";
 
 export default function Login() {
     const [error, setError] = useState<null | string>(null);
@@ -54,15 +57,14 @@
     }, []);
 
     return (
-        <div>
+        <>
             {error ? (
-                <div>
-                    <h1>Authentication error</h1>
-                    <p>{error}</p>
-                </div>
+                <ErrorProvider error={"Authentication error: " + error}>
+                    <ErrorScreen />
+                </ErrorProvider>
             ) : (
-                <p>Signing in...</p>
+                <Loading>Signing in</Loading>
             )}
-        </div>
+        </>
     );
 }
\ No newline at end of file
diff --git a/packages/webui/src/app/routes/p/$.tsx b/packages/webui/src/app/routes/p/$.tsx
deleted file mode 100644
index 1837210..0000000
--- a/packages/webui/src/app/routes/p/$.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-// Iris
-// Copyright (c) jottocraft 2025
-
-export default function Home() {
-    return (
-        <div>
-            <p>Hello plugin!</p>
-        </div>
-    );
-}
\ No newline at end of file
diff --git a/packages/webui/src/app/routes/p/$pluginId/$panelId.tsx b/packages/webui/src/app/routes/p/$pluginId/$panelId.tsx
index 76e2f75..fdcc298 100644
--- a/packages/webui/src/app/routes/p/$pluginId/$panelId.tsx
+++ b/packages/webui/src/app/routes/p/$pluginId/$panelId.tsx
@@ -8,7 +8,7 @@
 
 export default function PanelRoute() {
     return (
-        <div className="py-12">
+        <div className="h-full py-12">
             <Panel />
         </div >
     );
diff --git a/packages/webui/src/webui.d.ts b/packages/webui/src/webui.d.ts
index 869b1fc..d17ec39 100644
--- a/packages/webui/src/webui.d.ts
+++ b/packages/webui/src/webui.d.ts
@@ -17,11 +17,13 @@
 declare type IrisWebuiPanelNavigationCtx = IrisWebuiPanelNavigationCtxActive | IrisWebuiPanelNavigationCtxInactive;
 
 declare type IrisWebuiPanelNavigationCtxCommon = {
+    root: boolean;
     startNavigation: (pluginId: string, ref: IrisWebuiPanelRef, e?: React.MouseEvent) => void;
 };
 
 declare type IrisWebuiPanelNavigationCtxActive = IrisWebuiPanelNavigationCtxCommon & {
     activeRef: IrisWebuiPanelRef;
+    activeState: import("@jottocraft/iris-sdk").IrisPluginConduitPanelState<import("@jottocraft/iris-sdk").IrisPluginConduit>;
     layout: null | import("@jottocraft/iris-sdk").IrisPluginConduitPanelLayout<import("@jottocraft/iris-sdk").IrisPluginConduit>;
     pluginId: string;
     breadcrumbs: IrisWebuiPanelBreadcrumb[];
@@ -29,6 +31,8 @@
     back: (e?: React.MouseEvent) => void;
     goToBreadcrumb: (index: number, e?: React.MouseEvent) => void;
     refreshLayout: () => void;
+    setActiveState: (state: import("@jottocraft/iris-sdk").IrisPluginConduitPanelState<import("@jottocraft/iris-sdk").IrisPluginConduit>) => void;
+    patchActiveSectionState: (sectionId: string, state: import("@jottocraft/iris-sdk").IrisPluginConduitPanelSectionState<import("@jottocraft/iris-sdk").IrisPluginConduit>) => void;
 };
 
 declare type IrisWebuiPanelNavigationCtxInactive = IrisWebuiPanelNavigationCtxCommon & {