{t('pets.title')}
diff --git a/ui/src/components/settings/PieceEditor.tsx b/ui/src/components/settings/PieceEditor.tsx
index 2554479..9c1fa7c 100644
--- a/ui/src/components/settings/PieceEditor.tsx
+++ b/ui/src/components/settings/PieceEditor.tsx
@@ -18,11 +18,19 @@ export interface PieceEditorProps {
* so a deep link without pieceSource still renders correctly for non-admins.
*/
source?: 'builtin' | 'user-custom' | 'global-custom';
+ /**
+ * When set, the editor operates on the space folder's custom pieces
+ * (`data/spaces/{id}/pieces/`). The host (space settings) manages selection
+ * via local state, so `onDeleted` is used to clear it instead of URL state.
+ */
+ spaceId?: string;
+ /** Called after a successful delete so the host can clear its selection. */
+ onDeleted?: () => void;
}
-export function PieceEditor({ name, isAdmin = true, source }: PieceEditorProps) {
+export function PieceEditor({ name, isAdmin = true, source, spaceId, onDeleted }: PieceEditorProps) {
const { t } = useTranslation('settings');
- const { data: fetchResult, isLoading, error } = usePiece(name, source);
+ const { data: fetchResult, isLoading, error } = usePiece(name, source, spaceId);
// Use the server-resolved source as the authoritative value; fall back to the
// prop only while the fetch hasn't completed yet (avoids flicker on known paths).
const piece = fetchResult?.piece ?? null;
@@ -175,9 +183,9 @@ export function PieceEditor({ name, isAdmin = true, source }: PieceEditorProps)
setSaving(true);
try {
- await updatePiece(name, saveData, effectiveSource);
- await queryClient.invalidateQueries({ queryKey: ['piece', name] });
- await queryClient.invalidateQueries({ queryKey: ['pieces'] });
+ await updatePiece(name, saveData, effectiveSource, spaceId);
+ await queryClient.invalidateQueries({ queryKey: ['piece', spaceId ?? null, name] });
+ await queryClient.invalidateQueries({ queryKey: ['pieces', spaceId ?? null] });
setIsDirty(false);
if (editMode === 'yaml') {
setDraft(saveData);
@@ -193,9 +201,13 @@ export function PieceEditor({ name, isAdmin = true, source }: PieceEditorProps)
const handleDelete = async () => {
if (!confirm(t('pieceEditor.confirmDelete', { name }))) return;
try {
- await deletePiece(name, effectiveSource);
- await queryClient.invalidateQueries({ queryKey: ['pieces'] });
- setUrlState((prev) => ({ ...prev, piece: undefined, section: 'provider' as any }));
+ await deletePiece(name, effectiveSource, spaceId);
+ await queryClient.invalidateQueries({ queryKey: ['pieces', spaceId ?? null] });
+ if (spaceId) {
+ onDeleted?.();
+ } else {
+ setUrlState((prev) => ({ ...prev, piece: undefined, section: 'provider' as any }));
+ }
} catch (e: any) {
showToast(t('pieceEditor.toastError', { msg: e.message }), { isError: true, duration: 3000 });
}
diff --git a/ui/src/components/settings/ServerTlsForm.tsx b/ui/src/components/settings/ServerTlsForm.tsx
index 1bceb4d..464f2dc 100644
--- a/ui/src/components/settings/ServerTlsForm.tsx
+++ b/ui/src/components/settings/ServerTlsForm.tsx
@@ -95,6 +95,20 @@ export function ServerTlsForm({ config, onChange }: SectionFormProps) {
/>
{t('serverTls.httpRedirect')}
+ {t('serverTls.httpRedirectHelp')}
+
+
+ {/* HSTS (opt-in) */}
+
+
+ {t('serverTls.hstsHelp')}
{/* HTTP redirect port(s) — accepts a single port or a comma-separated list */}
diff --git a/ui/src/components/settings/SettingsSidebar.tsx b/ui/src/components/settings/SettingsSidebar.tsx
index 6a4a598..402e8a6 100644
--- a/ui/src/components/settings/SettingsSidebar.tsx
+++ b/ui/src/components/settings/SettingsSidebar.tsx
@@ -20,12 +20,16 @@ interface SettingsSidebarProps {
* their new homes by `LEGACY_SECTION_REDIRECT` in this file. This keeps
* old bookmarks/links working through the transition.
*/
-const CONFIG_GROUPS = [
+export const CONFIG_GROUPS = [
{
- label: 'User',
+ // Per-user personal settings. Labeled "Preference" so per-user items
+ // (mascot Pets, notifications, reflection history) are clearly grouped
+ // and discoverable — users previously could not find Pets under "User".
+ label: 'Preference',
sections: [
{ id: 'preferences', label: 'Preferences' },
{ id: 'notifications', label: '🔔 Notifications' },
+ { id: 'pets', label: '◉ Pets' },
{ id: 'memory-learning', label: '🧠 Reflection history', labelKey: 'memoryLearning.navLabel' },
],
},
@@ -61,7 +65,6 @@ const CONFIG_GROUPS = [
{ id: 'context', label: 'Context' },
{ id: 'safety', label: 'Safety' },
{ id: 'reflection', label: 'Reflection' },
- { id: 'notes', label: 'Notes Injection' },
],
},
{
@@ -73,7 +76,6 @@ const CONFIG_GROUPS = [
{ id: 'tools-media', label: 'Media & Documents' },
{ id: 'tools-external', label: 'External Services' },
{ id: 'search-filter', label: 'Search Filter' },
- { id: 'tools-legacy-knowledge', label: 'Legacy Knowledge' },
],
},
{
@@ -141,7 +143,7 @@ export function SettingsSidebar({ activeSection, onSelectSection, isAdmin }: Set
{group.label}