- {/* Search */}
- {isSearching && (
-
- setSearchQuery(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Escape") {
- closeSearch();
- }
- }}
- className="w-full px-2 py-1.5 text-sm bg-background border border-border rounded-md outline-none focus:ring-1 focus:ring-ring"
- />
-
- )}
-
- {/* Search results */}
- {filteredDocuments ? (
- <>
-
-
- {t("sidebar.results")}
-
- {filteredDocuments.length === 0 ? (
-
- {t("sidebar.noPagesFound")}
-
- ) : (
- filteredDocuments.map((doc) => (
-
)}
+
+ {renderWorkspaceNavigation()}
+ {renderTrashSection()}
@@ -2624,12 +2530,42 @@ export function DocumentSidebar({
{/* Resize handle */}
- {onResize && (
+ {onResize && width !== undefined && (
{
+ let nextWidth: number;
+ switch (event.key) {
+ case "ArrowLeft":
+ nextWidth = width - 10;
+ break;
+ case "ArrowRight":
+ nextWidth = width + 10;
+ break;
+ case "Home":
+ if (minWidth === undefined) return;
+ nextWidth = minWidth;
+ break;
+ case "End":
+ if (maxWidth === undefined) return;
+ nextWidth = maxWidth;
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ onResize(nextWidth);
+ }}
onMouseDown={handleMouseDown}
/>
)}
diff --git a/templates/content/app/i18n-data.ts b/templates/content/app/i18n-data.ts
index 1e67e25336d..11c771852d6 100644
--- a/templates/content/app/i18n-data.ts
+++ b/templates/content/app/i18n-data.ts
@@ -3481,6 +3481,7 @@ const enUS = {
addChildTo: "Add child to {{title}}",
addSubPage: "Add sub-page",
collapse: "Collapse sidebar",
+ resize: "Resize sidebar",
completeStepsAboveFirst: "Complete steps above first",
connectWorkspace: "Connect workspace",
connected: "Connected",
@@ -10215,6 +10216,7 @@ export const messagesByLocale = {
newDatabase: "新建数据库",
noWorkspaces: "还没有工作区",
collapse: "折叠侧边栏",
+ resize: "调整侧边栏宽度",
expand: "展开侧边栏",
failedCreatePage: "创建页面失败",
failedDeletePage: "删除页面失败",
@@ -10416,6 +10418,7 @@ export const messagesByLocale = {
newDatabase: "Nueva base de datos",
noWorkspaces: "Aún no hay espacios de trabajo",
collapse: "Contraer barra lateral",
+ resize: "Cambiar ancho de la barra lateral",
expand: "Expandir barra lateral",
failedCreatePage: "No se pudo crear la página",
failedDeletePage: "No se pudo eliminar la página",
@@ -10619,6 +10622,7 @@ export const messagesByLocale = {
newDatabase: "Nouvelle base de données",
noWorkspaces: "Aucun espace de travail pour le moment",
collapse: "Réduire la barre latérale",
+ resize: "Redimensionner la barre latérale",
expand: "Développer la barre latérale",
failedCreatePage: "Échec de la création de la page",
failedDeletePage: "Échec de la suppression de la page",
@@ -10820,6 +10824,7 @@ export const messagesByLocale = {
newDatabase: "Neue Datenbank",
noWorkspaces: "Noch keine Arbeitsbereiche",
collapse: "Seitenleiste einklappen",
+ resize: "Seitenleiste skalieren",
expand: "Seitenleiste ausklappen",
failedCreatePage: "Seite konnte nicht erstellt werden",
failedDeletePage: "Seite konnte nicht gelöscht werden",
@@ -11020,6 +11025,7 @@ export const messagesByLocale = {
newDatabase: "新しいデータベース",
noWorkspaces: "ワークスペースはまだありません",
collapse: "サイドバーを折りたたむ",
+ resize: "サイドバーの幅を変更",
expand: "サイドバーを展開",
failedCreatePage: "ページを作成できませんでした",
failedDeletePage: "ページを削除できませんでした",
@@ -11209,6 +11215,7 @@ export const messagesByLocale = {
newDatabase: "새 데이터베이스",
noWorkspaces: "아직 워크스페이스가 없습니다",
collapse: "사이드바 접기",
+ resize: "사이드바 너비 조절",
expand: "사이드바 펼치기",
failedCreatePage: "페이지를 만들지 못했습니다",
failedDeletePage: "페이지를 삭제하지 못했습니다",
@@ -11410,6 +11417,7 @@ export const messagesByLocale = {
newDatabase: "Novo banco de dados",
noWorkspaces: "Ainda não há espaços de trabalho",
collapse: "Recolher barra lateral",
+ resize: "Redimensionar barra lateral",
expand: "Expandir barra lateral",
failedCreatePage: "Falha ao criar página",
failedDeletePage: "Falha ao excluir página",
@@ -11597,6 +11605,7 @@ export const messagesByLocale = {
newDatabase: "नया डेटाबेस",
noWorkspaces: "अभी कोई कार्यस्थान नहीं है",
collapse: "साइडबार संकुचित करें",
+ resize: "साइडबार का आकार बदलें",
expand: "साइडबार फैलाएं",
failedCreatePage: "पेज नहीं बन सका",
failedDeletePage: "पेज हटाया नहीं जा सका",
@@ -11787,6 +11796,7 @@ export const messagesByLocale = {
newDatabase: "قاعدة بيانات جديدة",
noWorkspaces: "لا توجد مساحات عمل بعد",
collapse: "طي الشريط الجانبي",
+ resize: "تغيير حجم الشريط الجانبي",
expand: "توسيع الشريط الجانبي",
failedCreatePage: "فشل إنشاء الصفحة",
failedDeletePage: "فشل حذف الصفحة",
diff --git a/templates/content/app/i18n/zh-TW.ts b/templates/content/app/i18n/zh-TW.ts
index d5699828d51..7014e21d9fa 100644
--- a/templates/content/app/i18n/zh-TW.ts
+++ b/templates/content/app/i18n/zh-TW.ts
@@ -1267,6 +1267,7 @@ const messages = {
addChildTo: "新增子項目至 {{title}}",
addSubPage: "新增子頁面",
collapse: "折疊側邊欄",
+ resize: "調整側邊欄寬度",
completeStepsAboveFirst: "請先完成上面的步驟",
connectWorkspace: "連線工作區",
connected: "已連線",
diff --git a/templates/content/app/root.tsx b/templates/content/app/root.tsx
index b59d965fa6a..176dd0819cc 100644
--- a/templates/content/app/root.tsx
+++ b/templates/content/app/root.tsx
@@ -40,6 +40,7 @@ import {
useCallback,
useEffect,
useMemo,
+ useRef,
useState,
} from "react";
import {
@@ -564,9 +565,27 @@ function ContentCommandMenu({
export default function Root() {
const [queryClient] = useState(() => createAgentNativeQueryClient());
const [cmdkOpen, setCmdkOpen] = useState(false);
+ const commandTrigger = useRef(null);
const location = useLocation();
const loaderData = useLoaderData();
- useCommandMenuShortcut(useCallback(() => setCmdkOpen(true), []));
+ useCommandMenuShortcut(
+ useCallback(() => {
+ commandTrigger.current =
+ document.activeElement instanceof HTMLElement
+ ? document.activeElement
+ : null;
+ setCmdkOpen(true);
+ }, []),
+ );
+ useEffect(() => {
+ if (cmdkOpen || !commandTrigger.current) return;
+ const trigger = commandTrigger.current;
+ commandTrigger.current = null;
+ const frame = window.requestAnimationFrame(() => {
+ if (trigger.isConnected) trigger.focus();
+ });
+ return () => window.cancelAnimationFrame(frame);
+ }, [cmdkOpen]);
// Public document paths (/p/*) SSR real content without the ClientOnly gate
// so crawlers and unauthenticated visitors receive full markup on first visit.
diff --git a/templates/content/changelog/2026-09-09-accessible-sidebar-controls.md b/templates/content/changelog/2026-09-09-accessible-sidebar-controls.md
new file mode 100644
index 00000000000..e2fa7a97929
--- /dev/null
+++ b/templates/content/changelog/2026-09-09-accessible-sidebar-controls.md
@@ -0,0 +1,6 @@
+---
+type: fixed
+date: 2026-09-09
+---
+
+Sidebar Search opens the command picker, icon controls have accessible names, and the sidebar can be resized with the keyboard.
diff --git a/templates/content/docs/product/capabilities/content.navigation.sidebar.md b/templates/content/docs/product/capabilities/content.navigation.sidebar.md
index 7c2a63ecb9f..f134d7d9968 100644
--- a/templates/content/docs/product/capabilities/content.navigation.sidebar.md
+++ b/templates/content/docs/product/capabilities/content.navigation.sidebar.md
@@ -26,6 +26,7 @@ proof_requirements:
evidence:
[
"../../../app/components/sidebar/document-sidebar-sections.test.ts",
+ "../../../app/components/sidebar/DocumentSidebar.layout.test.ts",
"../../../app/components/editor/database/sidebar.tsx",
]
superseded_by: null
@@ -75,6 +76,14 @@ Existing sidebar section tests and sidebar rendering show useful donor behavior.
not prove the full Reference/query, access, recovery, and personal-state contract; this
Capability remains `approved_shape`.
+The September 9, 2026 SB-01–03 controls pass exercised collapsed and expanded
+Search, Escape focus return, accessible control names, keyboard and pointer
+resizing within 240–480px, width persistence after reload, and a 390px mobile
+drawer through the local interface. The command picker uses the shared dialog
+stack so it remains visible above the drawer. This bounded controls evidence
+does not establish the remaining personal-reference, dynamic-section, access,
+or recovery contracts.
+
## Proof plan
1. Test pin, reorder, expand, query sections, reload, and stale-reference recovery.