Unify panel sub-screen header (one morphing header) — P2

Eliminates the double-header on Settings-edit / Files-folder sub-screens
(they previously stacked the panel's app header + an inline text-xs back).
Now one sticky panel header morphs: app name + close-X at the top screen,
back-chevron + text-lg sub-title when a sub-screen registers itself.

- AppShell: SubHeaderContext + useSubHeader(active, title, onBack) hook;
  removed the dead SubViewHeader (nothing used it — my earlier restyle of it
  was moot; the apps had inline backs).
- DevicePanel: single header morphs on the registered sub-header; provides
  the context around AppRouter; resets on app change.
- SettingsApp (edit) + FilesApp (drive): register via useSubHeader, inline
  back buttons removed.

Verified by rendering the edit screen (single "‹ Edit profile" header).
typecheck/lint/86 unit/37 E2E+visual all green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-12 21:57:44 -05:00
co-authored by Claude Opus 4.8
parent 95df0f3a98
commit 65a0f1aab9
4 changed files with 78 additions and 62 deletions
@@ -1,12 +1,13 @@
"use client";
import { useRouter } from "next/navigation";
import { ChevronLeft, ChevronRight, Trash2 } from "lucide-react";
import { ChevronRight, Trash2 } from "lucide-react";
import { useState, type FormEvent } from "react";
import type { Agent } from "@/lib/api/schemas";
import { Avatar } from "@/components/ui/Avatar";
import { useFetchJson } from "@/lib/api/use-fetch";
import { useSubHeader } from "./AppShell";
interface SettingsPayload {
agent: Agent;
@@ -26,6 +27,8 @@ export default function SettingsApp({ agent }: { agent: Agent }) {
const [screen, setScreen] = useState<Screen>("main");
const [saving, setSaving] = useState(false);
const [confirmingDelete, setConfirmingDelete] = useState(false);
// The edit sub-screen drives the panel header (back-chevron + "Edit profile").
useSubHeader(screen === "edit", "Edit profile", () => setScreen("main"));
const settings = useFetchJson<SettingsPayload>(
`/api/claws/settings/full?clawId=${agent.id}`,
);
@@ -73,13 +76,6 @@ export default function SettingsApp({ agent }: { agent: Agent }) {
if (screen === "edit") {
return (
<form onSubmit={saveProfile} className="flex flex-col gap-3 p-3">
<button
type="button"
onClick={() => setScreen("main")}
className="flex items-center gap-1 self-start text-xs text-muted-foreground transition-colors hover:text-foreground"
>
<ChevronLeft aria-hidden size={14} /> Edit profile
</button>
<label className="flex flex-col gap-1 text-xs text-muted-foreground">
Name
<input