From 107f13fb66b19b18adb38f382aac1ccdd94321e5 Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Thu, 12 Jun 2025 05:43:19 +0930
Subject: [PATCH 01/11] feat: remove content picker tab bar when only 1 visible
 tab

---
 src/library-authoring/LibraryAuthoringPage.tsx | 18 ++++++++++--------
 .../component-picker/ComponentPicker.test.tsx  |  9 ++++-----
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx
index a49224ad25..1d11dbae98 100644
--- a/src/library-authoring/LibraryAuthoringPage.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.tsx
@@ -298,14 +298,16 @@ const LibraryAuthoringPage = ({
               headerActions={<HeaderActions />}
               hideBorder
             />
-            <Tabs
-              variant="tabs"
-              activeKey={activeKey}
-              onSelect={handleTabChange}
-              className="my-3"
-            >
-              {visibleTabsToRender}
-            </Tabs>
+            {visibleTabs.length > 1 && (
+              <Tabs
+                variant="tabs"
+                activeKey={activeKey}
+                onSelect={handleTabChange}
+                className="my-3"
+              >
+                {visibleTabsToRender}
+              </Tabs>
+            )}
             <ActionRow className="my-3">
               <SearchKeywordsField className="mr-3" />
               <FilterByTags />
diff --git a/src/library-authoring/component-picker/ComponentPicker.test.tsx b/src/library-authoring/component-picker/ComponentPicker.test.tsx
index 668b8e8692..dc028f7580 100644
--- a/src/library-authoring/component-picker/ComponentPicker.test.tsx
+++ b/src/library-authoring/component-picker/ComponentPicker.test.tsx
@@ -377,16 +377,15 @@ describe('<ComponentPicker />', () => {
     expect(screen.getByRole('tab', { name: /units/i })).toBeInTheDocument();
   });
 
-  it('should display only unit tab', async () => {
+  it('should display only units', async () => {
     render(<ComponentPicker visibleTabs={[ContentType.units]} />);
 
     expect(await screen.findByText('Test Library 1')).toBeInTheDocument();
     fireEvent.click(screen.getByDisplayValue(/lib:sampletaxonomyorg1:tl1/i));
 
-    expect(await screen.findByRole('tab', { name: /units/i })).toBeInTheDocument();
-    expect(screen.queryByRole('tab', { name: /all content/i })).not.toBeInTheDocument();
-    expect(screen.queryByRole('tab', { name: /collections/i })).not.toBeInTheDocument();
-    expect(screen.queryByRole('tab', { name: /components/i })).not.toBeInTheDocument();
+    expect(await screen.findByText('Published Test Unit')).toBeInTheDocument();
+    // No tabs shown when only one tab is visible
+    expect(screen.queryByRole('tab')).not.toBeInTheDocument();
   });
 
   it('should not display never published filter', async () => {

From 4f17d8bb24231335aa83155065343d91d9597177 Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Sat, 14 Jun 2025 00:18:07 +0930
Subject: [PATCH 02/11] refactor: simplify AddContent

to make it easier to show or hide the "add new component" buttons.

Also removes the need to convert between Chapter => Section,
Sequential => Subsection, and Vertical => Unit.
---
 .../add-content/AddContent.tsx                | 162 ++++++++++--------
 .../create-container/CreateContainerModal.tsx |  19 +-
 2 files changed, 99 insertions(+), 82 deletions(-)

diff --git a/src/library-authoring/add-content/AddContent.tsx b/src/library-authoring/add-content/AddContent.tsx
index 489ac79bc2..c9bd612791 100644
--- a/src/library-authoring/add-content/AddContent.tsx
+++ b/src/library-authoring/add-content/AddContent.tsx
@@ -37,7 +37,7 @@ import { ContainerType } from '../../generic/key-utils';
 
 type ContentType = {
   name: string,
-  disabled: boolean,
+  disabled?: boolean,
   icon?: React.ComponentType,
   blockType: string,
 };
@@ -64,8 +64,7 @@ type AddAdvancedContentViewProps = {
 const AddContentButton = ({ contentType, onCreateContent } : AddContentButtonProps) => {
   const {
     name,
-    disabled,
-    icon,
+    disabled = false,
     blockType,
   } = contentType;
   return (
@@ -73,7 +72,7 @@ const AddContentButton = ({ contentType, onCreateContent } : AddContentButtonPro
       variant="outline-primary"
       disabled={disabled}
       className="m-2"
-      iconBefore={icon || getItemIcon(blockType)}
+      iconBefore={getItemIcon(blockType)}
       onClick={() => onCreateContent(blockType)}
     >
       {name}
@@ -96,97 +95,126 @@ const AddContentView = ({
     insideSubsection,
   } = useLibraryRoutes();
 
-  const collectionButtonData = {
-    name: intl.formatMessage(messages.collectionButton),
-    disabled: false,
-    blockType: 'collection',
-  };
+  const collectionButton = (
+    <AddContentButton
+      key="collection"
+      contentType={{
+        name: intl.formatMessage(messages.collectionButton),
+        blockType: 'collection',
+      }}
+      onCreateContent={onCreateContent}
+    />
+  );
 
-  const unitButtonData = {
-    name: intl.formatMessage(messages.unitButton),
-    disabled: false,
-    blockType: 'vertical',
-  };
+  const unitButton = (
+    <AddContentButton
+      key="unit"
+      contentType={{
+        name: intl.formatMessage(messages.unitButton),
+        blockType: 'unit',
+      }}
+      onCreateContent={onCreateContent}
+    />
+  );
 
-  const sectionButtonData = {
-    name: intl.formatMessage(messages.sectionButton),
-    disabled: false,
-    blockType: 'chapter',
-  };
+  const sectionButton = (
+    <AddContentButton
+      key="section"
+      contentType={{
+        name: intl.formatMessage(messages.sectionButton),
+        blockType: 'section',
+      }}
+      onCreateContent={onCreateContent}
+    />
+  );
 
-  const subsectionButtonData = {
-    name: intl.formatMessage(messages.subsectionButton),
-    disabled: false,
-    blockType: 'sequential',
-  };
+  const subsectionButton = (
+    <AddContentButton
+      key="subsection"
+      contentType={{
+        name: intl.formatMessage(messages.subsectionButton),
+        blockType: 'subsection',
+      }}
+      onCreateContent={onCreateContent}
+    />
+  );
 
-  const libraryContentButtonData = {
-    name: intl.formatMessage(messages.libraryContentButton),
-    disabled: false,
-    blockType: 'libraryContent',
-  };
+  const existingContentButton = (
+    <AddContentButton
+      key="libraryContent"
+      contentType={{
+        name: intl.formatMessage(messages.libraryContentButton),
+        blockType: 'libraryContent',
+      }}
+      onCreateContent={onCreateContent}
+    />
+  );
 
-  /** List container content types that should be displayed based on current path */
-  const visibleContentTypes = useMemo(() => {
+  /* Note: for MVP we are hiding the unsupported types, not just disabling them. */
+  const componentButtons = contentTypes.filter(ct => !ct.disabled).map((contentType) => (
+    <AddContentButton
+      key={`add-content-${contentType.blockType}`}
+      contentType={contentType}
+      onCreateContent={onCreateContent}
+    />
+  ));
+  const separator = (
+    <hr className="w-100 bg-gray-500" />
+  );
+
+  /** List buttons that should be displayed based on current path */
+  const visibleButtons = useMemo(() => {
     if (insideCollection) {
-      // except for add collection button, show everthing.
+      // except for add collection button, show everything.
       return [
-        libraryContentButtonData,
-        sectionButtonData,
-        subsectionButtonData,
-        unitButtonData,
+        existingContentButton,
+        sectionButton,
+        subsectionButton,
+        unitButton,
+        separator,
+        ...componentButtons,
       ];
     }
     if (insideUnit) {
-      // Only show libraryContentButton
-      return [libraryContentButtonData];
+      // Only show existing content button + component buttons
+      return [
+        existingContentButton,
+        separator,
+        ...componentButtons,
+      ];
     }
     // istanbul ignore if
     if (insideSection) {
       // Should only allow adding subsections
       throw new Error('Not implemented');
-      // return [subsectionButtonData];
+      // return [subsectionButton];
     }
     // istanbul ignore if
     if (insideSubsection) {
       // Should only allow adding units
       throw new Error('Not implemented');
-      // return [unitButtonData];
+      // return [unitButton];
     }
-    // except for libraryContentButton, show everthing.
+    // except for existing content, show everything.
     return [
-      collectionButtonData,
-      sectionButtonData,
-      subsectionButtonData,
-      unitButtonData,
+      collectionButton,
+      sectionButton,
+      subsectionButton,
+      unitButton,
+      separator,
+      ...componentButtons,
     ];
-  }, [insideCollection, insideUnit, insideSection, insideSubsection]);
+  }, [componentButtons, insideCollection, insideUnit, insideSection, insideSubsection]);
 
   return (
     <>
-      {visibleContentTypes.map((contentType) => (
-        <AddContentButton
-          key={contentType.blockType}
-          contentType={contentType}
-          onCreateContent={onCreateContent}
-        />
-      ))}
-      {componentPicker && visibleContentTypes.includes(libraryContentButtonData) && (
-        /// Show the "Add Library Content" button for units and collections
+      {visibleButtons}
+      {componentPicker && visibleButtons.includes(existingContentButton) && (
         <PickLibraryContentModal
           isOpen={isAddLibraryContentModalOpen}
           onClose={closeAddLibraryContentModal}
         />
       )}
-      <hr className="w-100 bg-gray-500" />
-      {/* Note: for MVP we are hiding the unuspported types, not just disabling them. */}
-      {contentTypes.filter(ct => !ct.disabled).map((contentType) => (
-        <AddContentButton
-          key={`add-content-${contentType.blockType}`}
-          contentType={contentType}
-          onCreateContent={onCreateContent}
-        />
-      ))}
     </>
   );
 };
@@ -423,9 +451,9 @@ const AddContent = () => {
     } else if (blockType === 'advancedXBlock') {
       showAdvancedList();
     } else if ([
-      ContainerType.Vertical,
-      ContainerType.Chapter,
-      ContainerType.Sequential,
+      ContainerType.Unit,
+      ContainerType.Subsection,
+      ContainerType.Section,
     ].includes(blockType as ContainerType)) {
       setCreateContainerModalType(blockType as ContainerType);
     } else {
diff --git a/src/library-authoring/create-container/CreateContainerModal.tsx b/src/library-authoring/create-container/CreateContainerModal.tsx
index a5fd56b610..743350b534 100644
--- a/src/library-authoring/create-container/CreateContainerModal.tsx
+++ b/src/library-authoring/create-container/CreateContainerModal.tsx
@@ -32,7 +32,7 @@ const CreateContainerModal = () => {
 
   /** labels based on the type of modal open, i.e., section, subsection or unit */
   const labels = React.useMemo(() => {
-    if (createContainerModalType === ContainerType.Chapter) {
+    if (createContainerModalType === ContainerType.Section) {
       return {
         modalTitle: intl.formatMessage(messages.createSectionModalTitle),
         validationError: intl.formatMessage(messages.createSectionModalNameInvalid),
@@ -42,7 +42,7 @@ const CreateContainerModal = () => {
         errorMsg: intl.formatMessage(messages.createSectionError),
       };
     }
-    if (createContainerModalType === ContainerType.Sequential) {
+    if (createContainerModalType === ContainerType.Subsection) {
       return {
         modalTitle: intl.formatMessage(messages.createSubsectionModalTitle),
         validationError: intl.formatMessage(messages.createSubsectionModalNameInvalid),
@@ -65,21 +65,10 @@ const CreateContainerModal = () => {
   /** Call close for section, subsection and unit as the operation is idempotent */
   const handleClose = () => setCreateContainerModalType(undefined);
 
-  /** Calculate containerType based on type of open modal */
-  const containerType = React.useMemo(() => {
-    if (createContainerModalType === ContainerType.Chapter) {
-      return ContainerType.Section;
-    }
-    if (createContainerModalType === ContainerType.Sequential) {
-      return ContainerType.Subsection;
-    }
-    return ContainerType.Unit;
-  }, [createContainerModalType]);
-
   const handleCreate = React.useCallback(async (values) => {
     try {
       const container = await create.mutateAsync({
-        containerType,
+        containerType: createContainerModalType,
         ...values,
       });
       // link container to parent
@@ -94,7 +83,7 @@ const CreateContainerModal = () => {
     } finally {
       handleClose();
     }
-  }, [containerType, labels, handleClose, navigateTo]);
+  }, [createContainerModalType, labels, handleClose, navigateTo]);
 
   return (
     <ModalDialog

From 509dbe8d3229a45d920cfa636e21e83b7edd2a3a Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Mon, 16 Jun 2025 01:04:45 +0930
Subject: [PATCH 03/11] feat: implements "add container to parent container"

---
 .../add-content/AddContent.tsx                | 16 +++++---
 .../create-container/CreateContainerModal.tsx | 41 +++++++++++++++----
 2 files changed, 43 insertions(+), 14 deletions(-)

diff --git a/src/library-authoring/add-content/AddContent.tsx b/src/library-authoring/add-content/AddContent.tsx
index c9bd612791..fd3b326a38 100644
--- a/src/library-authoring/add-content/AddContent.tsx
+++ b/src/library-authoring/add-content/AddContent.tsx
@@ -185,15 +185,19 @@ const AddContentView = ({
     }
     // istanbul ignore if
     if (insideSection) {
-      // Should only allow adding subsections
-      throw new Error('Not implemented');
-      // return [subsectionButton];
+      // Only allow adding subsections
+      return [
+        existingContentButton,
+        subsectionButton,
+      ];
     }
     // istanbul ignore if
     if (insideSubsection) {
-      // Should only allow adding units
-      throw new Error('Not implemented');
-      // return [unitButton];
+      // Only allow adding units
+      return [
+        existingContentButton,
+        unitButton,
+      ];
     }
     // except for existing content, show everything.
     return [
diff --git a/src/library-authoring/create-container/CreateContainerModal.tsx b/src/library-authoring/create-container/CreateContainerModal.tsx
index 743350b534..cff7304352 100644
--- a/src/library-authoring/create-container/CreateContainerModal.tsx
+++ b/src/library-authoring/create-container/CreateContainerModal.tsx
@@ -10,7 +10,11 @@ import * as Yup from 'yup';
 import FormikControl from '../../generic/FormikControl';
 import { useLibraryContext } from '../common/context/LibraryContext';
 import messages from './messages';
-import { useAddItemsToCollection, useCreateLibraryContainer } from '../data/apiHooks';
+import {
+  useAddItemsToCollection,
+  useAddItemsToContainer,
+  useCreateLibraryContainer,
+} from '../data/apiHooks';
 import { ToastContext } from '../../generic/toast-context';
 import LoadingButton from '../../generic/loading-button';
 import { ContainerType } from '../../generic/key-utils';
@@ -21,13 +25,20 @@ const CreateContainerModal = () => {
   const intl = useIntl();
   const {
     collectionId,
+    containerId,
     libraryId,
     createContainerModalType,
     setCreateContainerModalType,
   } = useLibraryContext();
-  const { navigateTo, insideCollection } = useLibraryRoutes();
+  const {
+    navigateTo,
+    insideCollection,
+    insideSection,
+    insideSubsection,
+  } = useLibraryRoutes();
   const create = useCreateLibraryContainer(libraryId);
-  const updateItemsMutation = useAddItemsToCollection(libraryId, collectionId);
+  const addItemsToCollection = useAddItemsToCollection(libraryId, collectionId);
+  const addItemsToContainer = useAddItemsToContainer(containerId);
   const { showToast } = React.useContext(ToastContext);
 
   /** labels based on the type of modal open, i.e., section, subsection or unit */
@@ -71,19 +82,33 @@ const CreateContainerModal = () => {
         containerType: createContainerModalType,
         ...values,
       });
-      // link container to parent
-      if (collectionId && insideCollection) {
-        await updateItemsMutation.mutateAsync([container.id]);
-      }
       // Navigate to the new container
       navigateTo({ containerId: container.id });
+
+      // Link container to parent after navigating -- we may still show an
+      // error if this linking fails, but at least the user can see that
+      // the container was created.
+      if (collectionId && insideCollection) {
+        await addItemsToCollection.mutateAsync([container.id]);
+      }
+      if (containerId && (insideSection || insideSubsection)) {
+        await addItemsToContainer.mutateAsync([container.id]);
+      }
+
       showToast(labels.successMsg);
     } catch (error) {
       showToast(labels.errorMsg);
     } finally {
       handleClose();
     }
-  }, [createContainerModalType, labels, handleClose, navigateTo]);
+  }, [
+    addItemsToCollection,
+    addItemsToContainer,
+    createContainerModalType,
+    handleClose,
+    labels,
+    navigateTo,
+  ]);
 
   return (
     <ModalDialog

From 5df245992f08cf30526772fb2fb13c7e4171f6d5 Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Mon, 16 Jun 2025 01:16:38 +0930
Subject: [PATCH 04/11] fix: allow "add new" button to open new container modal

instead of just opening the sidebar, when adding a container to another
container.

This saves the user a click and makes this consistent with the
"add new component" button on unit page.
---
 src/library-authoring/containers/FooterActions.tsx | 14 ++++++++++++--
 .../section-subsections/LibrarySectionPage.tsx     |  2 ++
 .../section-subsections/LibrarySubsectionPage.tsx  |  2 ++
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/library-authoring/containers/FooterActions.tsx b/src/library-authoring/containers/FooterActions.tsx
index 7f6de80b1e..2fd3767e0b 100644
--- a/src/library-authoring/containers/FooterActions.tsx
+++ b/src/library-authoring/containers/FooterActions.tsx
@@ -1,21 +1,31 @@
 import { Button, useToggle } from '@openedx/paragon';
 import { Add } from '@openedx/paragon/icons';
+import { type ContainerType } from '../../generic/key-utils';
 import { PickLibraryContentModal } from '../add-content';
 import { useLibraryContext } from '../common/context/LibraryContext';
 import { useSidebarContext } from '../common/context/SidebarContext';
 
 interface FooterActionsProps {
+  addContentType?: ContainerType;
   addContentBtnText: string;
   addExistingContentBtnText: string;
 }
 
 export const FooterActions = ({
+  addContentType,
   addContentBtnText,
   addExistingContentBtnText,
 }: FooterActionsProps) => {
   const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
   const { openAddContentSidebar } = useSidebarContext();
-  const { readOnly } = useLibraryContext();
+  const { readOnly, setCreateContainerModalType } = useLibraryContext();
+  const addContent = () => {
+    if (addContentType) {
+      setCreateContainerModalType(addContentType);
+    } else {
+      openAddContentSidebar();
+    }
+  };
   return (
     <div className="d-flex">
       <div className="w-100 mr-2">
@@ -23,7 +33,7 @@ export const FooterActions = ({
           className="ml-2"
           iconBefore={Add}
           variant="outline-primary rounded-0"
-          onClick={openAddContentSidebar}
+          onClick={addContent}
           disabled={readOnly}
           block
         >
diff --git a/src/library-authoring/section-subsections/LibrarySectionPage.tsx b/src/library-authoring/section-subsections/LibrarySectionPage.tsx
index 95da17add4..63055a2c21 100644
--- a/src/library-authoring/section-subsections/LibrarySectionPage.tsx
+++ b/src/library-authoring/section-subsections/LibrarySectionPage.tsx
@@ -8,6 +8,7 @@ import { useContainer, useContentLibrary } from '../data/apiHooks';
 import Loading from '../../generic/Loading';
 import NotFoundAlert from '../../generic/NotFoundAlert';
 import ErrorAlert from '../../generic/alert-error';
+import { ContainerType } from '../../generic/key-utils';
 import Header from '../../header';
 import SubHeader from '../../generic/sub-header/SubHeader';
 import { SubHeaderTitle } from '../LibraryAuthoringPage';
@@ -105,6 +106,7 @@ export const LibrarySectionPage = () => {
           <Container className="px-4 py-4">
             <LibraryContainerChildren containerKey={containerId} />
             <FooterActions
+              addContentType={ContainerType.Subsection}
               addContentBtnText={intl.formatMessage(sectionMessages.addContentButton)}
               addExistingContentBtnText={intl.formatMessage(sectionMessages.addExistingContentButton)}
             />
diff --git a/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx b/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx
index 1b60d5d3a3..04feb83db0 100644
--- a/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx
+++ b/src/library-authoring/section-subsections/LibrarySubsectionPage.tsx
@@ -11,6 +11,7 @@ import { useContentFromSearchIndex, useContentLibrary } from '../data/apiHooks';
 import Loading from '../../generic/Loading';
 import NotFoundAlert from '../../generic/NotFoundAlert';
 import ErrorAlert from '../../generic/alert-error';
+import { ContainerType } from '../../generic/key-utils';
 import Header from '../../header';
 import SubHeader from '../../generic/sub-header/SubHeader';
 import { SubHeaderTitle } from '../LibraryAuthoringPage';
@@ -154,6 +155,7 @@ export const LibrarySubsectionPage = () => {
           <Container className="px-4 py-4">
             <LibraryContainerChildren containerKey={containerId} />
             <FooterActions
+              addContentType={ContainerType.Unit}
               addContentBtnText={intl.formatMessage(subsectionMessages.addContentButton)}
               addExistingContentBtnText={intl.formatMessage(subsectionMessages.addExistingContentButton)}
             />

From 9aa7b70d0ee6031c9e703b27816f9e8580542ede Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Mon, 16 Jun 2025 09:20:52 +0930
Subject: [PATCH 05/11] test: adds tests for adding children to container

---
 .../add-content/AddContent.test.tsx           |  32 +++++-
 .../LibrarySectionSubsectionPage.test.tsx     | 101 ++++++++++++++++++
 2 files changed, 132 insertions(+), 1 deletion(-)

diff --git a/src/library-authoring/add-content/AddContent.test.tsx b/src/library-authoring/add-content/AddContent.test.tsx
index 1ed18593f0..5171eeb6eb 100644
--- a/src/library-authoring/add-content/AddContent.test.tsx
+++ b/src/library-authoring/add-content/AddContent.test.tsx
@@ -19,6 +19,7 @@ import {
   getLibraryPasteClipboardUrl,
   getXBlockFieldsApiUrl,
 } from '../data/api';
+import { getBlockType } from '../../generic/key-utils';
 import { mockClipboardEmpty, mockClipboardHtml } from '../../generic/data/api.mock';
 import { LibraryProvider } from '../common/context/LibraryContext';
 import AddContent from './AddContent';
@@ -50,8 +51,9 @@ const render = (collectionId?: string) => {
 };
 const renderWithContainer = (containerId: string) => {
   const params: { libraryId: string, containerId?: string } = { libraryId, containerId };
+  const containerType = containerId ? getBlockType(containerId) : 'unit';
   return baseRender(<AddContent />, {
-    path: '/library/:libraryId/unit/:containerId?',
+    path: `/library/:libraryId/${containerType}/:containerId?`,
     params,
     extraWrapper: ({ children }) => (
       <LibraryProvider
@@ -394,4 +396,32 @@ describe('<AddContent />', () => {
 
     expect(mockShowToast).toHaveBeenCalledWith('There was an error linking the content to this container.');
   });
+
+  it('should show unit buttons when add content in subsection', async () => {
+    const subsectionId = 'lct:org1:lib1:subsection:test-1';
+    renderWithContainer(subsectionId);
+
+    expect(await screen.findByRole('button', { name: /existing library content/i })).toBeInTheDocument();
+    expect(await screen.findByRole('button', { name: 'Unit' })).toBeInTheDocument();
+
+    // other container, collection, and component buttons are not shown
+    expect(screen.queryByRole('button', { name: 'Subsection' })).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Section' })).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Collection' })).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Text' })).not.toBeInTheDocument();
+  });
+
+  it('should show subsection buttons when add content in section', async () => {
+    const sectionId = 'lct:org1:lib1:section:test-1';
+    renderWithContainer(sectionId);
+
+    expect(await screen.findByRole('button', { name: /existing library content/i })).toBeInTheDocument();
+    expect(await screen.findByRole('button', { name: 'Subsection' })).toBeInTheDocument();
+
+    // other container, collection, and component buttons are not shown
+    expect(screen.queryByRole('button', { name: 'Unit' })).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Section' })).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Collection' })).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Text' })).not.toBeInTheDocument();
+  });
 });
diff --git a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
index 727773c914..553a74cf22 100644
--- a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
+++ b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
@@ -12,6 +12,7 @@ import {
 import {
   getLibraryContainerApiUrl,
   getLibraryContainerChildrenApiUrl,
+  getLibraryContainersApiUrl,
 } from '../data/api';
 import {
   mockContentLibrary,
@@ -377,5 +378,105 @@ describe('<LibrarySectionPage / LibrarySubsectionPage />', () => {
       expect((await screen.findAllByText(new RegExp(`Test ${childType}`, 'i')))[0]).toBeInTheDocument();
       expect(await screen.findByRole('button', { name: new RegExp(`${childType} Info`, 'i') })).toBeInTheDocument();
     });
+
+    it(`${cType} sidebar should render "new ${childType}" and "existing library content" buttons`, async () => {
+      renderLibrarySectionPage(undefined, undefined, cType);
+      const addChild = await screen.findByRole('button', { name: new RegExp(`add ${childType}`, 'i') });
+      userEvent.click(addChild);
+      const addNew = await screen.findByRole('button', { name: new RegExp(`^${childType}$`, 'i') });
+      const addExisting = await screen.findByRole('button', { name: /existing library content/i });
+
+      // Clicking "add new" shows create container modal (tested below)
+      userEvent.click(addNew);
+      expect(await screen.findByLabelText(new RegExp(`name your ${childType}`, 'i'))).toBeInTheDocument();
+      fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
+
+      // Clicking "add existing" shows content picker modal
+      userEvent.click(addExisting);
+      expect(await screen.findByRole('dialog')).toBeInTheDocument();
+      expect(await screen.findByRole('button', { name: new RegExp(`add to ${cType}`, 'i') })).toBeInTheDocument();
+    });
+
+    it(`"add new" button should add ${childType} to the ${cType}`, async () => {
+      const { libraryId } = mockContentLibrary;
+      const containerId = cType === ContainerType.Section
+        ? mockGetContainerMetadata.sectionId
+        : mockGetContainerMetadata.subsectionId;
+      const childId = cType === ContainerType.Section
+        ? mockGetContainerMetadata.subsectionId
+        : mockGetContainerMetadata.unitId;
+
+      axiosMock
+        .onPost(getLibraryContainersApiUrl(libraryId))
+        .reply(200, { id: childId });
+      axiosMock
+        .onPost(getLibraryContainerChildrenApiUrl(containerId))
+        .reply(200);
+      renderLibrarySectionPage(containerId, libraryId, cType);
+
+      const addChild = await screen.findByRole('button', { name: new RegExp(`add new ${childType}`, 'i') });
+      userEvent.click(addChild);
+      const textBox = await screen.findByLabelText(new RegExp(`name your ${childType}`, 'i'));
+      fireEvent.change(textBox, { target: { value: `New ${childType} Title` } });
+      fireEvent.click(screen.getByRole('button', { name: /create/i }));
+      await waitFor(() => {
+        expect(axiosMock.history.post.length).toEqual(2);
+      });
+      expect(axiosMock.history.post[0].data).toEqual(JSON.stringify({
+        container_type: childType,
+        display_name: `New ${childType} Title`,
+      }));
+      expect(axiosMock.history.post[1].data).toEqual(JSON.stringify({ usage_keys: [childId] }));
+      expect(textBox).not.toBeInTheDocument();
+      const childTypeTitle = childType.charAt(0).toUpperCase() + childType.slice(1);
+      expect(mockShowToast).toHaveBeenCalledWith(`${childTypeTitle} created successfully`);
+    });
+
+    it(`"add new" button should show error when adding ${childType} to the ${cType}`, async () => {
+      const { libraryId } = mockContentLibrary;
+      const containerId = cType === ContainerType.Section
+        ? mockGetContainerMetadata.sectionId
+        : mockGetContainerMetadata.subsectionId;
+      const childId = cType === ContainerType.Section
+        ? mockGetContainerMetadata.subsectionId
+        : mockGetContainerMetadata.unitId;
+
+      axiosMock
+        .onPost(getLibraryContainersApiUrl(libraryId))
+        .reply(200, { id: childId });
+      axiosMock
+        .onPost(getLibraryContainerChildrenApiUrl(containerId))
+        .reply(500);
+      renderLibrarySectionPage(containerId, libraryId, cType);
+
+      const addChild = await screen.findByRole('button', { name: new RegExp(`add new ${childType}`, 'i') });
+      userEvent.click(addChild);
+      const textBox = await screen.findByLabelText(new RegExp(`name your ${childType}`, 'i'));
+      fireEvent.change(textBox, { target: { value: `New ${childType} Title` } });
+      fireEvent.click(screen.getByRole('button', { name: /create/i }));
+      await waitFor(() => {
+        expect(axiosMock.history.post.length).toEqual(2);
+      });
+      expect(axiosMock.history.post[0].data).toEqual(JSON.stringify({
+        container_type: childType,
+        display_name: `New ${childType} Title`,
+      }));
+      expect(axiosMock.history.post[1].data).toEqual(JSON.stringify({ usage_keys: [childId] }));
+      expect(textBox).not.toBeInTheDocument();
+      expect(mockShowToast).toHaveBeenCalledWith(`There is an error when creating the library ${childType}`);
+    });
+
+    it(`"add existing ${childType}" button should load ${cType} content picker modal`, async () => {
+      renderLibrarySectionPage(undefined, undefined, cType);
+
+      expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
+
+      const addChild = await screen.findByRole('button', { name: new RegExp(`add existing ${childType}`, 'i') });
+      userEvent.click(addChild);
+
+      // Content picker loaded (modal behavior is tested elsewhere)
+      expect(await screen.findByRole('dialog')).toBeInTheDocument();
+      expect(await screen.findByRole('button', { name: new RegExp(`add to ${cType}`, 'i') })).toBeInTheDocument();
+    });
   });
 });

From b68de23c0f6fcdd24a6ce44e12bbbfa6cb15342e Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Tue, 17 Jun 2025 00:00:22 +0930
Subject: [PATCH 06/11] test: ensure Unit Add Content sidebar is opened on "add
 new content"

---
 src/library-authoring/units/LibraryUnitPage.test.tsx | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/library-authoring/units/LibraryUnitPage.test.tsx b/src/library-authoring/units/LibraryUnitPage.test.tsx
index e9584a8491..b5ad52ca1e 100644
--- a/src/library-authoring/units/LibraryUnitPage.test.tsx
+++ b/src/library-authoring/units/LibraryUnitPage.test.tsx
@@ -430,4 +430,14 @@ describe('<LibraryUnitPage />', () => {
     userEvent.click(component.parentElement!.parentElement!.parentElement!, undefined, { clickCount: 2 });
     expect(await screen.findByRole('dialog', { name: 'Editor Dialog' })).toBeInTheDocument();
   });
+
+  it('"Add New Content" button should open "Add Content" sidebar', async () => {
+    renderLibraryUnitPage();
+    const addContent = await screen.findByRole('button', { name: /add new content/i });
+    userEvent.click(addContent);
+
+    expect(await screen.findByRole('button', { name: /existing library content/i })).toBeInTheDocument();
+    expect(await screen.findByRole('button', { name: /text/i })).toBeInTheDocument();
+    expect(await screen.findByRole('button', { name: /problem/i })).toBeInTheDocument();
+  });
 });

From 2e5020925f2fbde50cca20dedd94d49b1e4e5921 Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Thu, 19 Jun 2025 07:24:43 +0930
Subject: [PATCH 07/11] fix: reinstate custom icon for add content buttons

---
 src/library-authoring/add-content/AddContent.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/library-authoring/add-content/AddContent.tsx b/src/library-authoring/add-content/AddContent.tsx
index b9eafca488..8a79d093ff 100644
--- a/src/library-authoring/add-content/AddContent.tsx
+++ b/src/library-authoring/add-content/AddContent.tsx
@@ -65,6 +65,7 @@ const AddContentButton = ({ contentType, onCreateContent } : AddContentButtonPro
   const {
     name,
     disabled = false,
+    icon,
     blockType,
   } = contentType;
   return (
@@ -72,7 +73,7 @@ const AddContentButton = ({ contentType, onCreateContent } : AddContentButtonPro
       variant="outline-primary"
       disabled={disabled}
       className="m-2"
-      iconBefore={getItemIcon(blockType)}
+      iconBefore={icon || getItemIcon(blockType)}
       onClick={() => onCreateContent(blockType)}
     >
       {name}

From ec7491796d4031bf2a7f8fe0482f7795bfd23cdf Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Thu, 19 Jun 2025 07:35:15 +0930
Subject: [PATCH 08/11] test: reinstate one skipped test

---
 src/library-authoring/containers/ContainerInfo.test.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/library-authoring/containers/ContainerInfo.test.tsx b/src/library-authoring/containers/ContainerInfo.test.tsx
index 75c5cc91b3..5370fb0b63 100644
--- a/src/library-authoring/containers/ContainerInfo.test.tsx
+++ b/src/library-authoring/containers/ContainerInfo.test.tsx
@@ -120,10 +120,10 @@ describe('<ContainerInfo />', () => {
       expect(mockShowToast).toHaveBeenCalledWith('Failed to publish changes');
     });
 
-    testIf(containerType === 'Unit')(`show only published ${containerType} content`, async () => {
+    it(`show only published ${containerType} content`, async () => {
       render(containerId, true);
       expect(await screen.findByTestId('container-info-menu-toggle')).toBeInTheDocument();
-      expect(screen.getByText(/text block published 1/i)).toBeInTheDocument();
+      expect(screen.getByText(/block published 1/i)).toBeInTheDocument();
     });
 
     it(`shows the ${containerType} Preview tab by default and the children are readonly`, async () => {

From aba7f70b801aaaffb0b960af0bcda3481254eefb Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Sat, 21 Jun 2025 03:27:32 +0930
Subject: [PATCH 09/11] refactor: make library routes return booleans for
 inside*

to make it easier to type these variables in other places
---
 src/library-authoring/routes.ts | 59 +++++++++++++++++----------------
 1 file changed, 30 insertions(+), 29 deletions(-)

diff --git a/src/library-authoring/routes.ts b/src/library-authoring/routes.ts
index e7760c6fb9..a65781c297 100644
--- a/src/library-authoring/routes.ts
+++ b/src/library-authoring/routes.ts
@@ -9,7 +9,6 @@ import {
   useLocation,
   useNavigate,
   useSearchParams,
-  type PathMatch,
 } from 'react-router-dom';
 import { ContainerType, getBlockType } from '../generic/key-utils';
 
@@ -62,15 +61,15 @@ export type NavigateToData = {
 };
 
 export type LibraryRoutesData = {
-  insideCollection: PathMatch<string> | null;
-  insideCollections: PathMatch<string> | null;
-  insideComponents: PathMatch<string> | null;
-  insideSections: PathMatch<string> | null;
-  insideSection: PathMatch<string> | null;
-  insideSubsections: PathMatch<string> | null;
-  insideSubsection: PathMatch<string> | null;
-  insideUnits: PathMatch<string> | null;
-  insideUnit: PathMatch<string> | null;
+  insideCollection: boolean;
+  insideCollections: boolean;
+  insideComponents: boolean;
+  insideSections: boolean;
+  insideSection: boolean;
+  insideSubsections: boolean;
+  insideSubsection: boolean;
+  insideUnits: boolean;
+  insideUnit: boolean;
 
   /** Navigate using the best route from the current location for the given parameters.
    *  This function can be mutated if there are changes in the current route, so always include
@@ -85,15 +84,17 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
   const [searchParams] = useSearchParams();
   const navigate = useNavigate();
 
-  const insideCollection = matchPath(BASE_ROUTE + ROUTES.COLLECTION, pathname);
-  const insideCollections = matchPath(BASE_ROUTE + ROUTES.COLLECTIONS, pathname);
-  const insideComponents = matchPath(BASE_ROUTE + ROUTES.COMPONENTS, pathname);
-  const insideSections = matchPath(BASE_ROUTE + ROUTES.SECTIONS, pathname);
-  const insideSection = matchPath(BASE_ROUTE + ROUTES.SECTION, pathname);
-  const insideSubsections = matchPath(BASE_ROUTE + ROUTES.SUBSECTIONS, pathname);
-  const insideSubsection = matchPath(BASE_ROUTE + ROUTES.SUBSECTION, pathname);
-  const insideUnits = matchPath(BASE_ROUTE + ROUTES.UNITS, pathname);
-  const insideUnit = matchPath(BASE_ROUTE + ROUTES.UNIT, pathname);
+  // Convert the returned PathMatch<string> | null values to PathMatch<string> | false
+  // to make it easier to return them as booleans below.
+  const insideCollection = matchPath(BASE_ROUTE + ROUTES.COLLECTION, pathname) || false;
+  const insideCollections = matchPath(BASE_ROUTE + ROUTES.COLLECTIONS, pathname) || false;
+  const insideComponents = matchPath(BASE_ROUTE + ROUTES.COMPONENTS, pathname) || false;
+  const insideSections = matchPath(BASE_ROUTE + ROUTES.SECTIONS, pathname) || false;
+  const insideSection = matchPath(BASE_ROUTE + ROUTES.SECTION, pathname) || false;
+  const insideSubsections = matchPath(BASE_ROUTE + ROUTES.SUBSECTIONS, pathname) || false;
+  const insideSubsection = matchPath(BASE_ROUTE + ROUTES.SUBSECTION, pathname) || false;
+  const insideUnits = matchPath(BASE_ROUTE + ROUTES.UNITS, pathname) || false;
+  const insideUnit = matchPath(BASE_ROUTE + ROUTES.UNIT, pathname) || false;
 
   // Sanity check to ensure that we are not inside more than one route at the same time.
   // istanbul ignore if: this is a developer error, not a user error.
@@ -108,7 +109,7 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
       insideSubsection,
       insideUnits,
       insideUnit,
-    ].filter((match): match is PathMatch<string> => match !== null).length > 1) {
+    ].filter((match) => match).length > 1) {
     throw new Error('Cannot be inside more than one route at the same time.');
   }
 
@@ -247,15 +248,15 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
 
   return useMemo(() => ({
     navigateTo,
-    insideCollection,
-    insideCollections,
-    insideComponents,
-    insideSections,
-    insideSection,
-    insideSubsections,
-    insideSubsection,
-    insideUnits,
-    insideUnit,
+    insideCollection: !!insideCollection,
+    insideCollections: !!insideCollections,
+    insideComponents: !!insideComponents,
+    insideSections: !!insideSections,
+    insideSection: !!insideSection,
+    insideSubsections: !!insideSubsections,
+    insideSubsection: !!insideSubsection,
+    insideUnits: !!insideUnits,
+    insideUnit: !!insideUnit,
   }), [
     navigateTo,
     insideCollection,

From 3c629252a9cd9b0b37506458057c3fb2e14591bf Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Sat, 21 Jun 2025 03:28:21 +0930
Subject: [PATCH 10/11] fix: use context-appropriate messages when adding
 existing content

When adding units to a subsection:
* button names are "Add Unit" and "Existing Unit"
* modal title and text is "Select units" and "N units selected"
* modal button is "Add to subsection"

When adding subsection to a section:
* button names are "Add Subsection" and "Existing Subsection"
* modal title and text is "Select subsections" and "N subsections selected"
* modal button is "Add to section"

Otherwise, the more generic "component" text is used.
---
 .../add-content/AddContent.test.tsx           |  19 ++-
 .../add-content/AddContent.tsx                |  16 ++-
 .../add-content/AddContentHeader.tsx          |   2 +-
 .../PickLibraryContentModal.test.tsx          |  47 +++++--
 .../add-content/PickLibraryContentModal.tsx   |  60 ++++-----
 src/library-authoring/add-content/messages.ts | 119 +++++++++++++++---
 .../CreateContainerModal.test.tsx             |   2 +-
 .../LibrarySectionSubsectionPage.test.tsx     |   6 +-
 8 files changed, 188 insertions(+), 83 deletions(-)

diff --git a/src/library-authoring/add-content/AddContent.test.tsx b/src/library-authoring/add-content/AddContent.test.tsx
index 2953a6f5ad..a8789d2b33 100644
--- a/src/library-authoring/add-content/AddContent.test.tsx
+++ b/src/library-authoring/add-content/AddContent.test.tsx
@@ -90,6 +90,7 @@ describe('<AddContent />', () => {
     expect(screen.queryByRole('button', { name: /video/i })).toBeInTheDocument();
     expect(screen.queryByRole('button', { name: /copy from clipboard/i })).not.toBeInTheDocument();
     expect(await screen.findByRole('button', { name: /advanced \/ other/i })).toBeInTheDocument();
+    expect(await screen.queryByRole('button', { name: /existing library content/i })).not.toBeInTheDocument();
   });
 
   it('should render advanced content buttons', async () => {
@@ -331,6 +332,7 @@ describe('<AddContent />', () => {
     const unitId = 'lct:orf1:lib1:unit:test-1';
     renderWithContainer(unitId);
 
+    expect(await screen.findByRole('button', { name: /existing library content/i })).toBeInTheDocument();
     expect(await screen.findByRole('button', { name: 'Text' })).toBeInTheDocument();
 
     expect(screen.queryByRole('button', { name: 'Collection' })).not.toBeInTheDocument();
@@ -396,13 +398,16 @@ describe('<AddContent />', () => {
     expect(mockShowToast).toHaveBeenCalledWith('There was an error linking the content to this container.');
   });
 
-  it('should only show subsection button when inside a section', async () => {
+  it('should only show subsection buttons when inside a section', async () => {
     mockClipboardEmpty.applyMock();
     const sectionId = 'lct:orf1:lib1:section:test-1';
     renderWithContainer(sectionId, 'section');
 
-    expect(await screen.findByRole('button', { name: /existing library content/i })).toBeInTheDocument();
-    expect(await screen.findByRole('button', { name: 'Subsection' })).toBeInTheDocument();
+    expect(await screen.findByRole('button', { name: /existing subsection/i })).toBeInTheDocument();
+
+    // Button is labeled "New Subsection" , not "Subsection"
+    expect(await screen.findByRole('button', { name: /new subsection/i })).toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Subsection' })).not.toBeInTheDocument();
 
     expect(screen.queryByRole('button', { name: 'Collection' })).not.toBeInTheDocument();
     expect(screen.queryByRole('button', { name: 'Unit' })).not.toBeInTheDocument();
@@ -410,13 +415,15 @@ describe('<AddContent />', () => {
     expect(screen.queryByRole('button', { name: 'Text' })).not.toBeInTheDocument();
   });
 
-  it('should only show unit button when inside a subsection', async () => {
+  it('should only show unit buttons when inside a subsection', async () => {
     mockClipboardEmpty.applyMock();
     const subsectionId = 'lct:orf1:lib1:subsection:test-1';
     renderWithContainer(subsectionId, 'subsection');
 
-    expect(await screen.findByRole('button', { name: /existing library content/i })).toBeInTheDocument();
-    expect(await screen.findByRole('button', { name: 'Unit' })).toBeInTheDocument();
+    expect(await screen.findByRole('button', { name: /existing unit/i })).toBeInTheDocument();
+    // Button is labeled "New Unit" in this context, not just "Unit"
+    expect(await screen.findByRole('button', { name: /new unit/i })).toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: 'Unit' })).not.toBeInTheDocument();
 
     expect(screen.queryByRole('button', { name: 'Collection' })).not.toBeInTheDocument();
     expect(screen.queryByRole('button', { name: 'Subsection' })).not.toBeInTheDocument();
diff --git a/src/library-authoring/add-content/AddContent.tsx b/src/library-authoring/add-content/AddContent.tsx
index 8a79d093ff..0698b04574 100644
--- a/src/library-authoring/add-content/AddContent.tsx
+++ b/src/library-authoring/add-content/AddContent.tsx
@@ -31,7 +31,7 @@ import { blockTypes } from '../../editors/data/constants/app';
 
 import { useLibraryRoutes } from '../routes';
 import genericMessages from '../generic/messages';
-import messages from './messages';
+import { messages, getContentMessages } from './messages';
 import type { BlockTypeMetadata } from '../data/api';
 import { ContainerType } from '../../generic/key-utils';
 
@@ -96,11 +96,15 @@ const AddContentView = ({
     insideSubsection,
   } = useLibraryRoutes();
 
+  const contentMessages = useMemo(() => (
+    getContentMessages(insideSection, insideSubsection, insideUnit)
+  ), [insideSection, insideSubsection, insideUnit]);
+
   const collectionButton = (
     <AddContentButton
       key="collection"
       contentType={{
-        name: intl.formatMessage(messages.collectionButton),
+        name: intl.formatMessage(contentMessages.collectionButton),
         blockType: 'collection',
       }}
       onCreateContent={onCreateContent}
@@ -111,7 +115,7 @@ const AddContentView = ({
     <AddContentButton
       key="unit"
       contentType={{
-        name: intl.formatMessage(messages.unitButton),
+        name: intl.formatMessage(contentMessages.unitButton),
         blockType: 'unit',
       }}
       onCreateContent={onCreateContent}
@@ -122,7 +126,7 @@ const AddContentView = ({
     <AddContentButton
       key="section"
       contentType={{
-        name: intl.formatMessage(messages.sectionButton),
+        name: intl.formatMessage(contentMessages.sectionButton),
         blockType: 'section',
       }}
       onCreateContent={onCreateContent}
@@ -133,7 +137,7 @@ const AddContentView = ({
     <AddContentButton
       key="subsection"
       contentType={{
-        name: intl.formatMessage(messages.subsectionButton),
+        name: intl.formatMessage(contentMessages.subsectionButton),
         blockType: 'subsection',
       }}
       onCreateContent={onCreateContent}
@@ -144,7 +148,7 @@ const AddContentView = ({
     <AddContentButton
       key="libraryContent"
       contentType={{
-        name: intl.formatMessage(messages.libraryContentButton),
+        name: intl.formatMessage(contentMessages.libraryContentButton),
         blockType: 'libraryContent',
       }}
       onCreateContent={onCreateContent}
diff --git a/src/library-authoring/add-content/AddContentHeader.tsx b/src/library-authoring/add-content/AddContentHeader.tsx
index a73a41a039..07bf86ea65 100644
--- a/src/library-authoring/add-content/AddContentHeader.tsx
+++ b/src/library-authoring/add-content/AddContentHeader.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import { FormattedMessage } from '@edx/frontend-platform/i18n';
-import messages from './messages';
+import { messages } from './messages';
 
 const AddContentHeader = () => (
   <span className="font-weight-bold m-1.5">
diff --git a/src/library-authoring/add-content/PickLibraryContentModal.test.tsx b/src/library-authoring/add-content/PickLibraryContentModal.test.tsx
index c874a6fe6f..03126f610e 100644
--- a/src/library-authoring/add-content/PickLibraryContentModal.test.tsx
+++ b/src/library-authoring/add-content/PickLibraryContentModal.test.tsx
@@ -81,12 +81,24 @@ describe('<PickLibraryContentModal />', () => {
   });
 
   [
-    'collection' as const,
-    'unit' as const,
-    'section' as const,
-    'subsection' as const,
-  ].forEach((context) => {
-    it(`can pick components from the modal (${context})`, async () => {
+    {
+      context: 'collection' as const,
+      addType: 'component',
+    },
+    {
+      context: 'unit' as const,
+      addType: 'component',
+    },
+    {
+      context: 'subsection' as const,
+      addType: 'unit',
+    },
+    {
+      context: 'section' as const,
+      addType: 'subsection',
+    },
+  ].forEach(({ context, addType }) => {
+    it(`can pick content from the modal (${context})`, async () => {
       render(context);
 
       // Wait for the content library to load
@@ -95,11 +107,16 @@ describe('<PickLibraryContentModal />', () => {
         expect(screen.queryAllByText('Introduction to Testing')[0]).toBeInTheDocument();
       });
 
-      // Select the first component
-      fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
-      expect(await screen.findByText('1 Selected Component')).toBeInTheDocument();
+      expect(screen.getByText(`Select ${addType}s`)).toBeInTheDocument();
 
-      fireEvent.click(screen.getByRole('button', { name: /add to .*/i }));
+      // Select and add the first item
+      fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
+      expect(await screen.findByText(
+        new RegExp(`1 selected ${addType}`, 'i'),
+      )).toBeInTheDocument();
+      fireEvent.click(screen.getByRole('button', {
+        name: new RegExp(`add to ${context}`, 'i'),
+      }));
 
       await waitFor(() => {
         switch (context) {
@@ -143,11 +160,15 @@ describe('<PickLibraryContentModal />', () => {
         expect(screen.queryAllByText('Introduction to Testing')[0]).toBeInTheDocument();
       });
 
-      // Select the first component
+      // Select and add the first item
       fireEvent.click(screen.queryAllByRole('button', { name: 'Select' })[0]);
-      expect(await screen.findByText('1 Selected Component')).toBeInTheDocument();
+      expect(await screen.findByText(
+        new RegExp(`1 selected ${addType}`, 'i'),
+      )).toBeInTheDocument();
 
-      fireEvent.click(screen.getByRole('button', { name: /add to .*/i }));
+      fireEvent.click(screen.getByRole('button', {
+        name: new RegExp(`add to ${context}`, 'i'),
+      }));
 
       await waitFor(() => {
         switch (context) {
diff --git a/src/library-authoring/add-content/PickLibraryContentModal.tsx b/src/library-authoring/add-content/PickLibraryContentModal.tsx
index 3e9f396d76..43993d3754 100644
--- a/src/library-authoring/add-content/PickLibraryContentModal.tsx
+++ b/src/library-authoring/add-content/PickLibraryContentModal.tsx
@@ -1,4 +1,9 @@
-import React, { useCallback, useContext, useState } from 'react';
+import React, {
+  useCallback,
+  useContext,
+  useMemo,
+  useState,
+} from 'react';
 import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
 import { ActionRow, Button, StandardModal } from '@openedx/paragon';
 
@@ -8,27 +13,7 @@ import type { SelectedComponent } from '../common/context/ComponentPickerContext
 import { useAddItemsToCollection, useAddItemsToContainer } from '../data/apiHooks';
 import genericMessages from '../generic/messages';
 import { allLibraryPageTabs, ContentType, useLibraryRoutes } from '../routes';
-import messages from './messages';
-
-interface PickLibraryContentModalFooterProps {
-  onSubmit: () => void;
-  selectedComponents: SelectedComponent[];
-  buttonText: React.ReactNode;
-}
-
-const PickLibraryContentModalFooter: React.FC<PickLibraryContentModalFooterProps> = ({
-  onSubmit,
-  selectedComponents,
-  buttonText,
-}) => (
-  <ActionRow>
-    <FormattedMessage {...messages.selectedComponents} values={{ count: selectedComponents.length }} />
-    <ActionRow.Spacer />
-    <Button variant="primary" onClick={onSubmit}>
-      {buttonText}
-    </Button>
-  </ActionRow>
-);
+import { messages, getContentMessages } from './messages';
 
 interface PickLibraryContentModalProps {
   isOpen: boolean;
@@ -57,10 +42,14 @@ export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = (
 
   const { showToast } = useContext(ToastContext);
 
-  const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);
+  const contentMessages = useMemo(() => (
+    getContentMessages(insideSection, insideSubsection, insideUnit)
+  ), [insideSection, insideSubsection, insideUnit]);
+
+  const [selectedContent, setSelectedComponents] = useState<SelectedComponent[]>([]);
 
   const onSubmit = useCallback(() => {
-    const usageKeys = selectedComponents.map(({ usageKey }) => usageKey);
+    const usageKeys = selectedContent.map(({ usageKey }) => usageKey);
     onClose();
     if (insideCollection && collectionId) {
       updateCollectionItemsMutation.mutateAsync(usageKeys)
@@ -80,7 +69,7 @@ export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = (
         });
     }
   }, [
-    selectedComponents,
+    selectedContent,
     insideSection,
     insideSubsection,
     insideUnit,
@@ -91,16 +80,13 @@ export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = (
   // determine filter an visibleTabs based on current location
   let extraFilter = ['NOT type = "collection"'];
   let visibleTabs = allLibraryPageTabs.filter((tab) => tab !== ContentType.collections);
-  let addBtnText = messages.addToCollectionButton;
   if (insideSection) {
     // show only subsections
     extraFilter = ['block_type = "subsection"'];
-    addBtnText = messages.addToSectionButton;
     visibleTabs = [ContentType.subsections];
   } else if (insideSubsection) {
     // show only units
     extraFilter = ['block_type = "unit"'];
-    addBtnText = messages.addToSubsectionButton;
     visibleTabs = [ContentType.units];
   } else if (insideUnit) {
     // show only components
@@ -109,7 +95,6 @@ export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = (
       'NOT block_type = "subsection"',
       'NOT block_type = "section"',
     ];
-    addBtnText = messages.addToUnitButton;
     visibleTabs = [ContentType.components];
   }
 
@@ -120,17 +105,22 @@ export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = (
 
   return (
     <StandardModal
-      title="Select components"
+      title={intl.formatMessage(contentMessages.selectContentTitle)}
       isOverflowVisible={false}
       size="xl"
       isOpen={isOpen}
       onClose={onClose}
       footerNode={(
-        <PickLibraryContentModalFooter
-          onSubmit={onSubmit}
-          selectedComponents={selectedComponents}
-          buttonText={intl.formatMessage(addBtnText)}
-        />
+        <ActionRow>
+          <FormattedMessage
+            {...contentMessages.selectedContent}
+            values={{ count: selectedContent.length }}
+          />
+          <ActionRow.Spacer />
+          <Button variant="primary" onClick={onSubmit}>
+            {intl.formatMessage(contentMessages.addToButton)}
+          </Button>
+        </ActionRow>
       )}
     >
       <ComponentPicker
diff --git a/src/library-authoring/add-content/messages.ts b/src/library-authoring/add-content/messages.ts
index 99198a6082..d57c48d9e1 100644
--- a/src/library-authoring/add-content/messages.ts
+++ b/src/library-authoring/add-content/messages.ts
@@ -1,6 +1,6 @@
 import { defineMessages } from '@edx/frontend-platform/i18n';
 
-const messages = defineMessages({
+export const messages = defineMessages({
   collectionButton: {
     id: 'course-authoring.library-authoring.add-content.buttons.collection',
     defaultMessage: 'Collection',
@@ -24,29 +24,19 @@ const messages = defineMessages({
   libraryContentButton: {
     id: 'course-authoring.library-authoring.add-content.buttons.library-content',
     defaultMessage: 'Existing Library Content',
-    description: 'Content of button to add existing library content to a collection.',
+    description: 'Content of button to add existing library content to a collection or container.',
   },
-  addToCollectionButton: {
+  addToButton: {
     id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-collection',
     defaultMessage: 'Add to Collection',
     description: 'Button to add library content to a collection.',
   },
-  addToUnitButton: {
-    id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-unit',
-    defaultMessage: 'Add to Unit',
-    description: 'Button to add library content to a unit.',
-  },
-  addToSectionButton: {
-    id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-section',
-    defaultMessage: 'Add to Section',
-    description: 'Button to add library content to a section.',
-  },
-  addToSubsectionButton: {
-    id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-subsection',
-    defaultMessage: 'Add to Subsection',
-    description: 'Button to add library content to a subsection.',
+  selectContentTitle: {
+    id: 'course-authoring.library-authoring.add-content.select-components',
+    defaultMessage: 'Select components',
+    description: 'Title for the content picker when selecting components in library.',
   },
-  selectedComponents: {
+  selectedContent: {
     id: 'course-authoring.library-authoring.add-content.selected-components',
     defaultMessage: '{count, plural, one {# Selected Component} other {# Selected Components}}',
     description: 'Title for selected components in library.',
@@ -154,4 +144,97 @@ const messages = defineMessages({
   },
 });
 
+export const unitMessages = defineMessages({
+  addToButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-unit',
+    defaultMessage: 'Add to Unit',
+    description: 'Button to add library content to a unit.',
+  },
+});
+
+export const subsectionMessages = defineMessages({
+  unitButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.new-unit',
+    defaultMessage: 'New Unit',
+    description: 'Content of button to create a new Unit in a Subsection.',
+  },
+  libraryContentButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.library-unit',
+    defaultMessage: 'Existing Unit',
+    description: 'Content of button to add an existing Unit to a Subsection.',
+  },
+  addToButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-subsection',
+    defaultMessage: 'Add to Subsection',
+    description: 'Button to add Units to a Subsection.',
+  },
+  selectContentTitle: {
+    id: 'course-authoring.library-authoring.add-content.select-units',
+    defaultMessage: 'Select units',
+    description: 'Title for the content picker when selecting units in library.',
+  },
+  selectedContent: {
+    id: 'course-authoring.library-authoring.add-content.selected-units',
+    defaultMessage: '{count, plural, one {# Selected Unit} other {# Selected Units}}',
+    description: 'Title for selected units in library.',
+  },
+});
+
+export const sectionMessages = defineMessages({
+  subsectionButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.new-subsection',
+    defaultMessage: 'New Subsection',
+    description: 'Content of button to create a new Subsection in a Section.',
+  },
+  libraryContentButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.library-subsection',
+    defaultMessage: 'Existing Subsection',
+    description: 'Content of button to add an existing Subsection to a Section.',
+  },
+  addToButton: {
+    id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-section',
+    defaultMessage: 'Add to Section',
+    description: 'Button to add library content to a section.',
+  },
+  selectContentTitle: {
+    id: 'course-authoring.library-authoring.add-content.select-subsections',
+    defaultMessage: 'Select subsections',
+    description: 'Title for the content picker when selecting subsections in library.',
+  },
+  selectedContent: {
+    id: 'course-authoring.library-authoring.add-content.selected-subsections',
+    defaultMessage: '{count, plural, one {# Selected Subsections} other {# Selected Subsections}}',
+    description: 'Title for selected subsections in library.',
+  },
+});
+
+/*
+ * Returns the appropriate message set for the given route conditions.
+ */
+export const getContentMessages = (
+  insideSection: boolean,
+  insideSubsection: boolean,
+  insideUnit: boolean,
+) => {
+  if (insideSection) {
+    return {
+      ...messages,
+      ...sectionMessages,
+    };
+  }
+  if (insideSubsection) {
+    return {
+      ...messages,
+      ...subsectionMessages,
+    };
+  }
+  if (insideUnit) {
+    return {
+      ...messages,
+      ...unitMessages,
+    };
+  }
+  return messages;
+};
+
 export default messages;
diff --git a/src/library-authoring/create-container/CreateContainerModal.test.tsx b/src/library-authoring/create-container/CreateContainerModal.test.tsx
index bff91e8dc0..7c7fb96fa9 100644
--- a/src/library-authoring/create-container/CreateContainerModal.test.tsx
+++ b/src/library-authoring/create-container/CreateContainerModal.test.tsx
@@ -105,7 +105,7 @@ describe('CreateContainerModal container linking', () => {
       { containerId: newSectionId, routeType: 'section' },
     );
 
-    const subsectionButton = await screen.findByRole('button', { name: /^Subsection$/ });
+    const subsectionButton = await screen.findByRole('button', { name: /New subsection/i });
     userEvent.click(subsectionButton);
     const nameInput = await screen.findByLabelText(/name your subsection/i);
     userEvent.type(nameInput, 'Test Subsection');
diff --git a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
index b8ec61186d..cce36ab5ea 100644
--- a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
+++ b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
@@ -379,12 +379,12 @@ describe('<LibrarySectionPage / LibrarySubsectionPage />', () => {
       expect(await screen.findByRole('button', { name: new RegExp(`${childType} Info`, 'i') })).toBeInTheDocument();
     });
 
-    it(`${cType} sidebar should render "new ${childType}" and "existing library content" buttons`, async () => {
+    it(`${cType} sidebar should render "new ${childType}" and "existing ${childType}" buttons`, async () => {
       renderLibrarySectionPage(undefined, undefined, cType);
       const addChild = await screen.findByRole('button', { name: new RegExp(`add ${childType}`, 'i') });
       userEvent.click(addChild);
-      const addNew = await screen.findByRole('button', { name: new RegExp(`^${childType}$`, 'i') });
-      const addExisting = await screen.findByRole('button', { name: /existing library content/i });
+      const addNew = await screen.findByRole('button', { name: new RegExp(`^new ${childType}$`, 'i') });
+      const addExisting = await screen.findByRole('button', { name: new RegExp(`^existing ${childType}$`, 'i') });
 
       // Clicking "add new" shows create container modal (tested below)
       userEvent.click(addNew);

From 50aedd21b6974bf721a1bc0df7f0261db7db39ef Mon Sep 17 00:00:00 2001
From: Jillian Vogel <jill@opencraft.com>
Date: Mon, 23 Jun 2025 04:06:56 +0930
Subject: [PATCH 11/11] feat: hide Types filter in the section/subsection
 content picker

---
 src/library-authoring/LibraryAuthoringPage.tsx         | 10 ++++++++--
 .../LibrarySectionSubsectionPage.test.tsx              |  2 ++
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx
index 1d11dbae98..bfc13677c2 100644
--- a/src/library-authoring/LibraryAuthoringPage.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.tsx
@@ -158,7 +158,9 @@ const LibraryAuthoringPage = ({
     insideCollections,
     insideComponents,
     insideUnits,
+    insideSection,
     insideSections,
+    insideSubsection,
     insideSubsections,
     navigateTo,
   } = useLibraryRoutes();
@@ -251,8 +253,12 @@ const LibraryAuthoringPage = ({
     extraFilter.push(activeTypeFilters[activeKey]);
   }
 
-  // Disable filtering by block/problem type when viewing the Collections/Units/Sections/Subsections tab.
-  const onlyOneType = (insideCollections || insideUnits || insideSections || insideSubsections);
+  // Disable filtering by block/problem type when viewing the Collections/Units/Sections/Subsections tab,
+  // or when inside a specific Section or Subsection.
+  const onlyOneType = (
+    insideCollections || insideUnits || insideSections || insideSubsections
+    || insideSection || insideSubsection
+  );
   const overrideTypesFilter = onlyOneType
     ? new TypesFilterData()
     : undefined;
diff --git a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
index cce36ab5ea..cf4dc82a1e 100644
--- a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
+++ b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx
@@ -395,6 +395,8 @@ describe('<LibrarySectionPage / LibrarySubsectionPage />', () => {
       userEvent.click(addExisting);
       expect(await screen.findByRole('dialog')).toBeInTheDocument();
       expect(await screen.findByRole('button', { name: new RegExp(`add to ${cType}`, 'i') })).toBeInTheDocument();
+      // No "Types" filter shown
+      expect(screen.queryByRole('button', { name: /type/i })).not.toBeInTheDocument();
     });
 
     it(`"add new" button should add ${childType} to the ${cType}`, async () => {