@@ -208,13 +202,11 @@ function JournalReflection({
familiarId,
onNotice,
onError,
- standalone,
}: {
text: string;
familiarId: string | null;
onNotice: NoticeFn;
onError: (text: string) => void;
- standalone?: boolean;
}) {
// Memoize so `suggestions` keeps a stable identity across parent re-renders
// (e.g. the notice toast updating) — otherwise NextPaths' open-step effect
@@ -223,7 +215,7 @@ function JournalReflection({
return (
<>
>
);
}
@@ -232,18 +224,12 @@ export function JournalEntries({
familiars,
activeFamiliarId,
scopeFamiliarIds,
- standalone,
}: {
familiars: Familiar[];
activeFamiliarId: string | null;
/** Multiselect scope (empty = All) — the reflections list filters to days
* whose `reflectedBy` is in this set. */
scopeFamiliarIds?: ReadonlySet
;
- /** True when rendered outside the Workspace (Settings → Familiars studio tab).
- * The workspace event bus has no listeners there: "Run now" is hidden (its
- * chat handoff can't happen) and toast actions become real navigations via
- * the `?mode=` deep link. */
- standalone?: boolean;
}) {
useDateTimePrefs(); // subscribe: re-render when the date/time density pref changes
// One clock read per render — reused for `today`, list labels, and the detail
@@ -747,7 +733,6 @@ export function JournalEntries({
familiarId={selectedFamiliarId}
onNotice={showNotice}
onError={setError}
- standalone={standalone}
/>
)}
@@ -801,14 +786,9 @@ export function JournalEntries({
type="button"
className="journal-notice__act"
onClick={() => {
- if (standalone) {
- // No workspace on /settings — deep-link back into it.
- window.location.assign(`/?mode=${encodeURIComponent(notice.action!.mode)}`);
- } else {
- window.dispatchEvent(
- new CustomEvent("cave:navigate-mode", { detail: { mode: notice.action!.mode } }),
- );
- }
+ window.dispatchEvent(
+ new CustomEvent("cave:navigate-mode", { detail: { mode: notice.action!.mode } }),
+ );
setNotice(null);
}}
>
diff --git a/src/components/settings-action-buttons.test.ts b/src/components/settings-action-buttons.test.ts
index 01e473f0b..7b54849aa 100644
--- a/src/components/settings-action-buttons.test.ts
+++ b/src/components/settings-action-buttons.test.ts
@@ -11,7 +11,7 @@ const studioSources = [
"familiar-studio-brain-tab.tsx",
"familiar-studio-contract-tab.tsx",
"familiar-studio-identity-tab.tsx",
- "familiar-studio-lifecycle-tab.tsx",
+ "familiar-lifecycle-section.tsx",
"familiar-studio-look-tab.tsx",
"familiar-studio-projects-tab.tsx",
].map((fileName) => [fileName, readFileSync(new URL(`./${fileName}`, import.meta.url), "utf8")] as const);
@@ -95,7 +95,6 @@ assert.match(
for (const selector of [
".familiar-studio-picker__trigger",
".familiar-studio-picker__option",
- ".familiar-studio-lifecycle__row",
]) {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
assert.match(
diff --git a/src/components/settings-search.test.ts b/src/components/settings-search.test.ts
index c20cc2db3..72307aca9 100644
--- a/src/components/settings-search.test.ts
+++ b/src/components/settings-search.test.ts
@@ -42,13 +42,21 @@ assert.match(
/familiarTab\?: FamiliarStudioTab/,
"index entries can target a Familiars studio tab",
);
-for (const tab of ["identity", "look", "brain", "lifecycle", "memory", "projects", "vault", "journal"]) {
+for (const tab of ["identity", "brain", "memory", "projects", "vault"]) {
assert.match(
sections,
new RegExp(`familiarTab: "${tab}"`),
`the ${tab} studio tab is indexed for search`,
);
}
+// Look/Lifecycle/Journal merged or moved out — no rows target retired tabs.
+for (const gone of ["look", "lifecycle", "journal"]) {
+ assert.doesNotMatch(
+ sections,
+ new RegExp(`familiarTab: "${gone}"`),
+ `no search row targets the retired ${gone} tab`,
+ );
+}
// Picking a familiars entry activates the studio tab below the provider
// instead of scrolling to a SettingsGroup (the panel has none).
assert.match(shell, /if \(entry\.familiarTab\) \{[\s\S]*?setFamiliarsTabTarget\(entry\.familiarTab\)/, "goToSetting branches familiars entries to the tab target");
diff --git a/src/components/settings-sections.ts b/src/components/settings-sections.ts
index 17278189a..009d40f2f 100644
--- a/src/components/settings-sections.ts
+++ b/src/components/settings-sections.ts
@@ -59,12 +59,9 @@ export const SETTINGS_INDEX: SettingsIndexEntry[] = [
{ section: "daemon", group: "Connection", keywords: "daemon hub server executor private network tailscale remote multihost multi host" },
{ section: "daemon", group: "Info", keywords: "daemon info version socket pid api" },
{ section: "familiars", keywords: "familiars agents personas roster" },
- { section: "familiars", group: "Identity", familiarTab: "identity", keywords: "identity name role pronouns description rename" },
- { section: "familiars", group: "Look", familiarTab: "look", keywords: "look avatar image photo upload icon glyph color accent swatch palette" },
+ { section: "familiars", group: "Identity", familiarTab: "identity", keywords: "identity name role pronouns description rename look avatar image photo upload icon glyph color accent swatch palette backdrop lifecycle archive unarchive remove delete restore recently removed" },
{ section: "familiars", group: "Brain", familiarTab: "brain", keywords: "brain runtime harness model voice system prompt note capabilities" },
- { section: "familiars", group: "Lifecycle", familiarTab: "lifecycle", keywords: "lifecycle archive unarchive reorder roster order reset overrides" },
{ section: "familiars", group: "Memory", familiarTab: "memory", keywords: "memory memories daily notes recall" },
- { section: "familiars", group: "Journal", familiarTab: "journal", keywords: "journal daily reflection reflections diary entries generate" },
{ section: "familiars", group: "Projects", familiarTab: "projects", keywords: "projects access grants allow deny tool policy guard security audit requests permissions read write level" },
{ section: "familiars", group: "Access groups", keywords: "access groups group grants base projects read write level team role shared membership permissions" },
{ section: "familiars", group: "Vault", familiarTab: "vault", keywords: "vault secrets env environment keys tokens credentials 1password" },
diff --git a/src/components/workspace.tsx b/src/components/workspace.tsx
index 68572c6f9..fcaf49b55 100644
--- a/src/components/workspace.tsx
+++ b/src/components/workspace.tsx
@@ -351,8 +351,7 @@ export function Workspace() {
// Journal is now a tab inside the Grimoire surface. Every entry point
// (sidebar row, ⌘K palette, ?mode= deep link, cave:navigate-mode,
// dashboard links) funnels through setMode, so opening Grimoire on its
- // Journal tab here covers them all. (Per-familiar journals still live in
- // Settings → Familiars → Journal.)
+ // Journal tab here covers them all.
setGrimoireView("journal");
setModeRaw("grimoire");
return;
diff --git a/src/lib/familiar-studio-context.tsx b/src/lib/familiar-studio-context.tsx
index 6f1a808c5..79a918d07 100644
--- a/src/lib/familiar-studio-context.tsx
+++ b/src/lib/familiar-studio-context.tsx
@@ -11,10 +11,10 @@ import {
} from "react";
export type FamiliarStudioTab =
- | "identity" | "look" | "brain" | "lifecycle" | "memory" | "projects" | "contract" | "vault" | "journal";
+ | "identity" | "brain" | "memory" | "projects" | "contract" | "vault";
const STUDIO_TABS: readonly FamiliarStudioTab[] = [
- "identity", "look", "brain", "lifecycle", "memory", "projects", "contract", "vault", "journal",
+ "identity", "brain", "memory", "projects", "contract", "vault",
];
const TAB_STORAGE_KEY = "cave:familiar-studio-tab:v1";
@@ -33,7 +33,7 @@ export const BRAIN_STUDIO_FAMILIAR_KEY = "cave:brain-studio-familiar:v1";
* Hard-navigate to Settings → Familiars with an optional studio tab and
* familiar preselected. This is the single redirect path shared by the
* workspace-level provider (`redirectToSettings`) and workspace surfaces that
- * retired their own page (e.g. the Journal, now a studio tab).
+ * retired their own page in favor of the studio.
*/
export function openFamiliarStudioSettingsTab(tab?: FamiliarStudioTab, familiarId?: string): void {
if (typeof window === "undefined") return;
@@ -49,10 +49,9 @@ export function openFamiliarStudioSettingsTab(tab?: FamiliarStudioTab, familiarI
type Ctx = {
/** `null` means closed; a string id means open for a specific familiar. */
activeFamiliarId: string | null;
- /** `true` means open in no-familiar list view (Lifecycle tab only). */
- listView: boolean;
activeTab: FamiliarStudioTab;
openFamiliarStudio: (id: string, tab?: FamiliarStudioTab) => void;
+ /** Opens the familiars manager (Settings → Familiars) without forcing a tab. */
openFamiliarStudioListView: () => void;
closeFamiliarStudio: () => void;
setActiveTab: (tab: FamiliarStudioTab) => void;
@@ -76,7 +75,6 @@ export function FamiliarStudioProvider({
redirectToSettings?: boolean;
}) {
const [activeFamiliarId, setActiveFamiliarId] = useState(null);
- const [listView, setListView] = useState(false);
const [activeTab, setActiveTabState] = useState(DEFAULT_TAB);
// Restore last-used tab on mount.
@@ -102,38 +100,36 @@ export function FamiliarStudioProvider({
return;
}
setActiveFamiliarId(id);
- setListView(false);
if (tab) setActiveTab(tab);
},
[setActiveTab, redirectToSettings],
);
+ // "Manage familiars" entry point: with the roster manager retired, this just
+ // opens Settings → Familiars (keeping the last-used tab); the inline panel
+ // auto-selects a familiar.
const openFamiliarStudioListView = useCallback(() => {
if (redirectToSettings) {
- openFamiliarStudioSettingsTab("lifecycle");
+ openFamiliarStudioSettingsTab();
return;
}
setActiveFamiliarId(null);
- setListView(true);
- setActiveTab("lifecycle");
- }, [setActiveTab, redirectToSettings]);
+ }, [redirectToSettings]);
const closeFamiliarStudio = useCallback(() => {
setActiveFamiliarId(null);
- setListView(false);
}, []);
const value = useMemo(
() => ({
activeFamiliarId,
- listView,
activeTab,
openFamiliarStudio,
openFamiliarStudioListView,
closeFamiliarStudio,
setActiveTab,
}),
- [activeFamiliarId, listView, activeTab, openFamiliarStudio, openFamiliarStudioListView, closeFamiliarStudio, setActiveTab],
+ [activeFamiliarId, activeTab, openFamiliarStudio, openFamiliarStudioListView, closeFamiliarStudio, setActiveTab],
);
return {children};
diff --git a/src/lib/slash-commands.ts b/src/lib/slash-commands.ts
index 4f460f32b..7fab92765 100644
--- a/src/lib/slash-commands.ts
+++ b/src/lib/slash-commands.ts
@@ -44,7 +44,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
{ name: "/sessions", hint: "all sessions", description: "Open all sessions across familiars and runtimes.", section: "view" },
{ name: "/attach", hint: "open session", description: "Open a specific daemon session by id.", argPlaceholder: "session-id", section: "view" },
{ name: "/tui", hint: "open in Coven Code", description: "Open the current session in the external Coven Code TUI.", section: "view" },
- { name: "/journal", hint: "Journal", description: "Open your familiars' journal (Settings → Familiars → Journal).", section: "view" },
+ { name: "/journal", hint: "Journal", description: "Open your familiars' journal (Memories → Journal).", section: "view" },
{ name: "/canvas", hint: "sketch a UI", description: "Generate a UI artifact inline in chat.", argPlaceholder: "describe a UI…", section: "view" },
{ name: "/board", hint: "Tasks", description: "Open the Tasks kanban and table view.", section: "view" },
{ name: "/chats", hint: "Chats", description: "Switch back to the Chats view.", section: "view" },
diff --git a/src/lib/surface-warmup-registry.test.ts b/src/lib/surface-warmup-registry.test.ts
index 0b37d9ecf..0b762d080 100644
--- a/src/lib/surface-warmup-registry.test.ts
+++ b/src/lib/surface-warmup-registry.test.ts
@@ -53,7 +53,7 @@ test("warmup starts after paint and pauses work without mounting inactive surfac
test("roster membership changes publish the workspace familiar-refresh event", async () => {
for (const sourcePath of [
"../components/familiar-summoning-circle.tsx",
- "../components/familiar-studio-lifecycle-tab.tsx",
+ "../components/familiar-lifecycle-section.tsx",
]) {
const code = await readFile(new URL(sourcePath, here), "utf8");
assert.match(code, /window\.dispatchEvent\(new Event\("cave:familiars-refresh"\)\)/, `${sourcePath} invalidates warmed familiar consumers`);
diff --git a/src/styles/globals/shell-responsive.css b/src/styles/globals/shell-responsive.css
index d0bd008ff..a3899b4bb 100644
--- a/src/styles/globals/shell-responsive.css
+++ b/src/styles/globals/shell-responsive.css
@@ -998,21 +998,15 @@
}
}
.familiar-studio-lifecycle { display: flex; flex-direction: column; gap: 18px; }
+.familiar-studio-lifecycle__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.familiar-studio-lifecycle__section { display: flex; flex-direction: column; gap: 6px; }
.familiar-studio-lifecycle__heading { font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; margin: 0; }
.familiar-studio-lifecycle__hint { margin: 4px 0 8px; font-size: var(--text-2xs); line-height: 1.5; color: var(--text-muted); }
.familiar-studio-lifecycle__hint { font-size: 12px; color: var(--text-muted); line-height: 1.4; margin: 0; }
-.familiar-studio-lifecycle__row { display: flex; align-items: center; gap: 4px; padding: 4px; border-radius: var(--radius-control); }
-.familiar-studio-lifecycle__row:hover { background: var(--bg-raised); }
-.familiar-studio-lifecycle__row[data-dragging] { background: var(--bg-raised); box-shadow: var(--shadow-popover); opacity: 0.95; position: relative; z-index: 1; }
-.familiar-studio-lifecycle__grip { display: grid; place-items: center; width: 20px; height: 24px; flex-shrink: 0; background: transparent; border: none; color: var(--text-faint); cursor: grab; touch-action: none; }
-.familiar-studio-lifecycle__grip:hover { color: var(--text-secondary); }
-.familiar-studio-lifecycle__row[data-dragging] .familiar-studio-lifecycle__grip { cursor: grabbing; }
-.familiar-studio-lifecycle__row-main { display: flex; gap: 8px; align-items: center; flex: 1; background: transparent; border: none; padding: 4px 6px; cursor: pointer; color: var(--text-primary); font-size: 13px; }
/* Remove-confirm strip: detach semantics spelled out inline before scheduling
the undo-safe delete (danger tint recipe: solid text / 6% fill / 35% border). */
-.familiar-studio-lifecycle__confirm { display: flex; flex-direction: column; gap: 8px; margin: 2px 0 6px 28px; padding: 10px 12px; border: 1px solid color-mix(in oklch, var(--color-danger) 35%, transparent); border-radius: 8px; background: color-mix(in oklch, var(--color-danger) 6%, var(--bg-raised)); }
+.familiar-studio-lifecycle__confirm { display: flex; flex-direction: column; gap: 8px; margin: 2px 0 6px; padding: 10px 12px; border: 1px solid color-mix(in oklch, var(--color-danger) 35%, transparent); border-radius: 8px; background: color-mix(in oklch, var(--color-danger) 6%, var(--bg-raised)); }
.familiar-studio-lifecycle__confirm-title { margin: 0; font-size: 12px; font-weight: 600; color: var(--text-primary); }
.familiar-studio-lifecycle__confirm-copy { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-secondary); }
.familiar-studio-lifecycle__confirm-warn { color: var(--color-warning); }
@@ -1075,9 +1069,7 @@
.familiar-studio-look__custom:focus-visible,
.familiar-studio-look__avatar-zoom:focus-visible,
.familiar-studio-look__upload:focus-visible,
-.familiar-studio-brain__capabilities-summary:focus-visible,
-.familiar-studio-lifecycle__row-main:focus-visible,
-.familiar-studio-lifecycle__grip:focus-visible {
+.familiar-studio-brain__capabilities-summary:focus-visible {
outline: var(--ring-width) solid var(--ring-focus);
outline-offset: 1px;
}
diff --git a/src/styles/journal.css b/src/styles/journal.css
index 2a4691597..b8f37d550 100644
--- a/src/styles/journal.css
+++ b/src/styles/journal.css
@@ -489,13 +489,6 @@
}
.journal-entry__by b { color: var(--accent-presence); }
-/* ── Familiar Studio host ─────────────────────────────────────────────────────
- Inside Settings → Familiars the journal has no full-page flex parent, so the
- master-detail shell gets an explicit bounded height: the day rail and the
- detail pane scroll internally instead of growing the settings page. */
-.familiar-studio-journal { display: flex; min-width: 0; }
-.familiar-studio-journal .journal-list { height: min(65vh, 720px); }
-
/* The section labels are real headings now (cave-t1ou) — inherit the label
styling instead of UA heading defaults. */
.journal-entry__sec-heading {