diff --git a/appinfo/info.xml b/appinfo/info.xml
index 8024e57703..826a042ea2 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -55,4 +55,12 @@ You can also edit your documents off-line with the Collabora Office app from the
OCA\Richdocuments\Settings\Personal
OCA\Richdocuments\Settings\Section
+
+
+ Office
+ richdocuments.overview.index
+ app.svg
+ 10
+
+
diff --git a/appinfo/routes.php b/appinfo/routes.php
index e17267f6ce..6646968513 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -24,6 +24,9 @@
// external api access
['name' => 'document#extAppGetData', 'url' => '/ajax/extapp/data/{fileId}', 'verb' => 'POST'],
+ // Office overview page
+ ['name' => 'overview#index', 'url' => '/overview', 'verb' => 'GET'],
+
// Settings
['name' => 'settings#setPersonalSettings', 'url' => 'ajax/personal.php', 'verb' => 'POST'],
['name' => 'settings#setSettings', 'url' => 'ajax/admin.php', 'verb' => 'POST'],
diff --git a/cypress/e2e/overview.spec.js b/cypress/e2e/overview.spec.js
new file mode 100644
index 0000000000..06acc8c840
--- /dev/null
+++ b/cypress/e2e/overview.spec.js
@@ -0,0 +1,158 @@
+/**
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+const CATEGORY_FILES = [
+ {
+ category: 'Documents',
+ emptyMessage: 'No documents found',
+ fixture: 'document.odt',
+ mimeType: 'application/vnd.oasis.opendocument.text',
+ },
+ {
+ category: 'Presentations',
+ emptyMessage: 'No presentations found',
+ fixture: 'presentation.odp',
+ mimeType: 'application/vnd.oasis.opendocument.presentation',
+ },
+ {
+ category: 'Spreadsheets',
+ emptyMessage: 'No spreadsheets found',
+ fixture: 'spreadsheet.ods',
+ mimeType: 'application/vnd.oasis.opendocument.spreadsheet',
+ },
+ {
+ category: 'Diagrams',
+ emptyMessage: 'No diagrams found',
+ fixture: 'drawing.odg',
+ mimeType: 'application/vnd.oasis.opendocument.graphics',
+ },
+]
+
+describe('Office overview page', function() {
+ describe('without files', function() {
+ let randUser
+
+ before(function() {
+ cy.createRandomUser().then(user => {
+ randUser = user
+ })
+ })
+
+ beforeEach(function() {
+ cy.login(randUser)
+ cy.visit('/apps/richdocuments/overview')
+ })
+
+ it('Shows the navigation sidebar with appropriate entries', function() {
+ CATEGORY_FILES.forEach(({ category }) => {
+ cy.contains('.app-navigation-entry', category).should('exist')
+ })
+ })
+
+ it('Highlights the active navigation item and shows empty state on click', function() {
+ CATEGORY_FILES.forEach(({ category, emptyMessage }) => {
+ cy.contains('.app-navigation-entry', category).click()
+ cy.contains('.app-navigation-entry', category)
+ .should('have.class', 'active')
+ cy.get('.empty-content')
+ .should('be.visible')
+ .and('contain', emptyMessage)
+ })
+ })
+ })
+
+ describe('with files', function() {
+ let randUser
+
+ before(function() {
+ cy.createRandomUser().then(user => {
+ randUser = user
+ cy.login(user)
+
+ CATEGORY_FILES.forEach(({ fixture, mimeType }) => {
+ cy.uploadFile(user, fixture, mimeType, `/${fixture}`)
+ })
+
+ cy.createFolder(user, 'subfolder').then(() => {
+ CATEGORY_FILES.forEach(({ fixture, mimeType }) => {
+ cy.uploadFile(user, fixture, mimeType, `/subfolder/${fixture}`)
+ })
+ })
+ })
+ })
+
+ beforeEach(function() {
+ cy.login(randUser)
+ cy.visit('/apps/richdocuments/overview', {
+ onBeforeLoad(win) {
+ cy.spy(win, 'postMessage').as('postMessage')
+ },
+ })
+ })
+
+ CATEGORY_FILES.forEach(({ category, fixture }) => {
+ it(`Shows ${category} file cards in the correct category`, function() {
+ cy.contains('.app-navigation-entry', category).click()
+
+ cy.contains('.file-card__name', fixture)
+ .should('be.visible')
+
+ cy.get('.file-card__preview img')
+ .should('exist')
+
+ cy.get('.input-field__label')
+ .should('contain', `Search ${category.toLowerCase()}`)
+ })
+
+ it(`Opens the viewer when clicking a ${category} file card`, function() {
+ cy.contains('.app-navigation-entry', category).click()
+ cy.contains('.file-card', fixture).click()
+
+ cy.waitForViewer()
+ cy.waitForCollabora()
+
+ cy.closeDocument()
+ })
+ })
+
+ it('Shows file cards for files in subdirectories', function() {
+ CATEGORY_FILES.forEach(({ category }) => {
+ cy.contains('.app-navigation-entry', category).click()
+ cy.get('.file-card').should('have.length.at.least', 2)
+ })
+ })
+
+ it('Filters file cards by search query', function() {
+ const { category, fixture } = CATEGORY_FILES[0]
+ const stem = fixture.split('.')[0]
+
+ cy.contains('.app-navigation-entry', category).click()
+
+ cy.get('.office-overview__search [type="search"]').type(stem)
+ cy.contains('.file-card__name', fixture).should('be.visible')
+ })
+
+ it('Shows empty state when search matches nothing', function() {
+ const { category } = CATEGORY_FILES[0]
+
+ cy.contains('.app-navigation-entry', category).click()
+
+ cy.get('.office-overview__search [type="search"]').type('xyz123noresults')
+ cy.get('.empty-content').should('be.visible')
+ })
+
+ it('Resets search when switching categories', function() {
+ const [first, second] = CATEGORY_FILES
+
+ cy.contains('.app-navigation-entry', first.category).click()
+ cy.get('.office-overview__search [type="search"]').type('xyz123noresults')
+
+ cy.contains('.app-navigation-entry', second.category).click()
+
+ cy.get('.office-overview__search [type="search"]').should('have.value', '')
+ cy.contains('.file-card__name', second.fixture).should('be.visible')
+ })
+ })
+})
diff --git a/cypress/e2e/templates.spec.js b/cypress/e2e/templates.spec.js
index e8da9e890f..e5187bf2a6 100644
--- a/cypress/e2e/templates.spec.js
+++ b/cypress/e2e/templates.spec.js
@@ -52,7 +52,7 @@ describe('Global templates', function() {
.scrollIntoView()
cy.intercept('DELETE', '**/richdocuments/template/*').as('templateDeleteRequest')
- cy.get('.template-btn[data-cy-template-btn-name="systemtemplate"]').click()
+ cy.get('.file-card[data-cy-template-btn-name="systemtemplate"]').click()
cy.wait('@templateDeleteRequest').then(({ response }) => {
expect(response.statusCode).to.equal(204)
diff --git a/lib/Controller/OverviewController.php b/lib/Controller/OverviewController.php
new file mode 100644
index 0000000000..a6d1e14708
--- /dev/null
+++ b/lib/Controller/OverviewController.php
@@ -0,0 +1,54 @@
+initialState->provideInitialState('previewEnabled', $this->preview->isMimeSupported('application/vnd.oasis.opendocument.text'));
+
+ // Viewer is pre-installed in production but may not be available in other environments
+ if (class_exists(LoadViewer::class)) {
+ $this->eventDispatcher->dispatchTyped(new LoadViewer());
+ }
+
+ return new TemplateResponse('richdocuments', 'overview', [
+ 'id-app-content' => '#app-content-vue',
+ 'id-app-navigation' => '#app-navigation-vue',
+ ]);
+ }
+}
diff --git a/src/components/AdminSettings/GlobalTemplates.vue b/src/components/AdminSettings/GlobalTemplates.vue
index 5b1a82c7b3..2dbbc389c2 100644
--- a/src/components/AdminSettings/GlobalTemplates.vue
+++ b/src/components/AdminSettings/GlobalTemplates.vue
@@ -14,37 +14,37 @@
@change="selectFile">