Skip to content

refactor(admin): group the settings page into sections - #178

Merged
Jaggob merged 4 commits into
mainfrom
feat/admin-settings-sections
Aug 1, 2026
Merged

refactor(admin): group the settings page into sections#178
Jaggob merged 4 commits into
mainfrom
feat/admin-settings-sections

Conversation

@Jaggob

@Jaggob Jaggob commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

The admin page was one long list of fields. It is now split into four sections: Etherpad server, Pad types and behaviour, External pads and embedding and Diagnostics. The pad type toggles sit at the top of their section, and each hint sits directly under the field it belongs to.

Saving and the diagnostic tools now report next to their own buttons instead of into one shared status line. The two areas update independently – a late response no longer clears the other one’s result.

Accessibility: hints are linked with aria-describedby, and the dynamic “no pad type enabled” hint announces itself via role="status". Spacing comes from explicit row classes rather than negative margins.

New: tests/js/admin-settings.test.js – the first coverage for src/admin-settings.js, including a regression test for the independent status areas.

Structural change only; no behaviour change to the settings themselves.

The admin page was one long list of fields. It is now split into
"Etherpad server", "Pad types and behaviour", "External pads and
embedding" and "Diagnostics", with the pad type toggles at the top of
their section and the field hints tied to their inputs.

Saving and the diagnostic tools report next to their own buttons instead
of into a single shared status line, so feedback appears where the action
was triggered. The two areas update independently – a late response no
longer clears the other one's result.

Hints are linked with aria-describedby, the dynamic "no pad type enabled"
hint announces itself via role="status", and spacing is set with explicit
row classes rather than negative margins.

Adds tests/js/admin-settings.test.js, the first coverage for
src/admin-settings.js.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 31, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. E2E health check selector stale ✓ Resolved 🐞 Bug ≡ Correctness
Description
The health-check UI now reports status in the new diagnostics status node, but the Playwright helper
still asserts on the old save-status node, so the admin health-check E2E spec will fail. This breaks
CI coverage for verifying the configured Etherpad connection.
Code

src/admin-settings.js[256]

+			setStatus(message, 'success', diagnosticsStatusNode)
Relevance

●●● Strong

E2E suite is actively maintained; selector regressions would break CI, likely fixed promptly (see PR
#100).

PR-#100

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
src/admin-settings.js now writes health-check results to diagnosticsStatusNode, while the E2E
helper still looks at the save-status element #etherpad-nextcloud-admin-status after clicking the
health-check button.

src/admin-settings.js[233-257]
tests/e2e/fixtures/nextcloud.ts[50-57]
templates/admin-settings.php[179-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The E2E helper `runAdminEtherpadHealthCheck()` still reads `#etherpad-nextcloud-admin-status`, but the health check now writes to `#etherpad-nextcloud-diagnostics-status`, causing the E2E assertion to never observe `ep-status-success`.
### Issue Context
The PR intentionally split status output into separate areas (save vs diagnostics). Tests need to follow the new UX structure.
### Fix Focus Areas
- tests/e2e/fixtures/nextcloud.ts[50-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. E2E health check selector stale ✓ Resolved 🐞 Bug ≡ Correctness
Description
The health-check UI now reports status in the new diagnostics status node, but the Playwright helper
still asserts on the old save-status node, so the admin health-check E2E spec will fail. This breaks
CI coverage for verifying the configured Etherpad connection.
Code

src/admin-settings.js[256]

+			setStatus(message, 'success', diagnosticsStatusNode)
Relevance

●●● Strong

E2E suite is actively maintained; selector regressions would break CI, likely fixed promptly (see PR
#100).

PR-#100

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
src/admin-settings.js now writes health-check results to diagnosticsStatusNode, while the E2E
helper still looks at the save-status element #etherpad-nextcloud-admin-status after clicking the
health-check button.

src/admin-settings.js[233-257]
tests/e2e/fixtures/nextcloud.ts[50-57]
templates/admin-settings.php[179-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The E2E helper `runAdminEtherpadHealthCheck()` still reads `#etherpad-nextcloud-admin-status`, but the health check now writes to `#etherpad-nextcloud-diagnostics-status`, causing the E2E assertion to never observe `ep-status-success`.
### Issue Context
The PR intentionally split status output into separate areas (save vs diagnostics). Tests need to follow the new UX structure.
### Fix Focus Areas
- tests/e2e/fixtures/nextcloud.ts[50-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. E2E health check selector stale ✓ Resolved 🐞 Bug ≡ Correctness
Description
The health-check UI now reports status in the new diagnostics status node, but the Playwright helper
still asserts on the old save-status node, so the admin health-check E2E spec will fail. This breaks
CI coverage for verifying the configured Etherpad connection.
Code

src/admin-settings.js[256]

+			setStatus(message, 'success', diagnosticsStatusNode)
Relevance

●●● Strong

E2E suite is actively maintained; selector regressions would break CI, likely fixed promptly (see PR
#100).

PR-#100

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
src/admin-settings.js now writes health-check results to diagnosticsStatusNode, while the E2E
helper still looks at the save-status element #etherpad-nextcloud-admin-status after clicking the
health-check button.

src/admin-settings.js[233-257]
tests/e2e/fixtures/nextcloud.ts[50-57]
templates/admin-settings.php[179-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The E2E helper `runAdminEtherpadHealthCheck()` still reads `#etherpad-nextcloud-admin-status`, but the health check now writes to `#etherpad-nextcloud-diagnostics-status`, causing the E2E assertion to never observe `ep-status-success`.
### Issue Context
The PR intentionally split status output into separate areas (save vs diagnostics). Tests need to follow the new UX structure.
### Fix Focus Areas
- tests/e2e/fixtures/nextcloud.ts[50-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. E2E health check selector stale ✓ Resolved 🐞 Bug ≡ Correctness
Description
The health-check UI now reports status in the new diagnostics status node, but the Playwright helper
still asserts on the old save-status node, so the admin health-check E2E spec will fail. This breaks
CI coverage for verifying the configured Etherpad connection.
Code

src/admin-settings.js[256]

+			setStatus(message, 'success', diagnosticsStatusNode)
Relevance

●●● Strong

E2E suite is actively maintained; selector regressions would break CI, likely fixed promptly (see PR
#100).

PR-#100

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
src/admin-settings.js now writes health-check results to diagnosticsStatusNode, while the E2E
helper still looks at the save-status element #etherpad-nextcloud-admin-status after clicking the
health-check button.

src/admin-settings.js[233-257]
tests/e2e/fixtures/nextcloud.ts[50-57]
templates/admin-settings.php[179-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The E2E helper `runAdminEtherpadHealthCheck()` still reads `#etherpad-nextcloud-admin-status`, but the health check now writes to `#etherpad-nextcloud-diagnostics-status`, causing the E2E assertion to never observe `ep-status-success`.
### Issue Context
The PR intentionally split status output into separate areas (save vs diagnostics). Tests need to follow the new UX structure.
### Fix Focus Areas
- tests/e2e/fixtures/nextcloud.ts[50-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Diagnostics status node not required ✓ Resolved 🐞 Bug ☼ Reliability
Description
beginStatus() and setStatus() are called with diagnosticsStatusNode even though script
initialization does not require that element; if it’s missing, the save status gets cleared and
diagnostics progress/results are never shown. This makes the diagnostics actions silently lose UI
feedback in any DOM variant missing the new node.
Code

src/admin-settings.js[R111-134]

+	function setStatus(message, state, node = statusNode) {
+		if (!(node instanceof HTMLElement)) {
+			return
+		}
+		node.textContent = message
+		node.classList.remove('ep-status-success', 'ep-status-error')
if (state === 'success') {
-			statusNode.classList.add('ep-status-success')
+			node.classList.add('ep-status-success')
} else if (state === 'error') {
-			statusNode.classList.add('ep-status-error')
+			node.classList.add('ep-status-error')
+		}
+	}
+
+	// Starting an action does clear the other area: its result predates this
+	// action and could otherwise be read as belonging to it.
+	function beginStatus(message, node = statusNode) {
+		for (const other of [statusNode, diagnosticsStatusNode]) {
+			if (other instanceof HTMLElement && other !== node) {
+				other.textContent = ''
+				other.classList.remove('ep-status-success', 'ep-status-error')
+			}
}
+		setStatus(message, null, node)
}
Relevance

●●● Strong

Team often accepts defensive JS hardening to avoid silent failures (e.g., error-path robustness in
PR #96).

PR-#96
PR-#34

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Initialization only checks for root, form, statusNode, and healthButton but not
diagnosticsStatusNode. setStatus() returns early if the node is not an HTMLElement, and
diagnostics actions pass diagnosticsStatusNode into beginStatus()/setStatus().

src/admin-settings.js[4-35]
src/admin-settings.js[107-134]
src/admin-settings.js[233-306]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The script treats `diagnosticsStatusNode` as optional during initialization, but later passes it into `beginStatus()`/`setStatus()`. When the node is null, `setStatus()` no-ops and `beginStatus()` may still clear the other area.
### Issue Context
The template in this PR includes the node, but this is a fragile coupling: any partial/custom markup, stale rendered HTML, or test DOM that omits `#etherpad-nextcloud-diagnostics-status` will lose diagnostics feedback.
### Fix Focus Areas
- src/admin-settings.js[4-35]
- src/admin-settings.js[107-134]
- src/admin-settings.js[233-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Unit test mocks skip ok ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new Vitest admin-settings tests mock server responses without { ok: true }, so postJson()
treats them as failures and the tests unintentionally exercise the error path. This reduces the
tests’ value as regression coverage for the intended success behavior.
Code

tests/js/admin-settings.test.js[R81-87]

+	it('reports saving and diagnostics next to their own actions', async () => {
+		vi.stubGlobal('fetch', vi.fn(() => Promise.resolve({
+			ok: true,
+			status: 200,
+			text: () => Promise.resolve(JSON.stringify({ message: 'Connection ok.' })),
+		})))
+		await import(MODULE)
Relevance

●●● Strong

Repo has history of tightening Vitest mocks/expectations for correctness and isolation (accepted
test fixes in PR #96).

PR-#96

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
postJson() throws unless data.ok === true. Real endpoints (saveSettings/healthCheck) include
'ok' => true, but the tests’ mocked JSON bodies omit it.

tests/js/admin-settings.test.js[81-123]
src/admin-settings.js[181-215]
lib/Controller/AdminController.php[54-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`postJson()` requires parsed JSON to contain `ok: true` for success, but the new tests mock successful responses without that field. This means the tests validate status-area separation mainly through the error path.
### Issue Context
Backend controllers return `['ok' => true, ...]` on success.
### Fix Focus Areas
- tests/js/admin-settings.test.js[81-123]
- tests/js/admin-settings.test.js[96-116]
- lib/Controller/AdminController.php[54-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Diagnostics status node not required ✓ Resolved 🐞 Bug ☼ Reliability
Description
beginStatus() and setStatus() are called with diagnosticsStatusNode even though script
initialization does not require that element; if it’s missing, the save status gets cleared and
diagnostics progress/results are never shown. This makes the diagnostics actions silently lose UI
feedback in any DOM variant missing the new node.
Code

src/admin-settings.js[R111-134]

+	function setStatus(message, state, node = statusNode) {
+		if (!(node instanceof HTMLElement)) {
+			return
+		}
+		node.textContent = message
+		node.classList.remove('ep-status-success', 'ep-status-error')
if (state === 'success') {
-			statusNode.classList.add('ep-status-success')
+			node.classList.add('ep-status-success')
} else if (state === 'error') {
-			statusNode.classList.add('ep-status-error')
+			node.classList.add('ep-status-error')
+		}
+	}
+
+	// Starting an action does clear the other area: its result predates this
+	// action and could otherwise be read as belonging to it.
+	function beginStatus(message, node = statusNode) {
+		for (const other of [statusNode, diagnosticsStatusNode]) {
+			if (other instanceof HTMLElement && other !== node) {
+				other.textContent = ''
+				other.classList.remove('ep-status-success', 'ep-status-error')
+			}
}
+		setStatus(message, null, node)
}
Relevance

●●● Strong

Team often accepts defensive JS hardening to avoid silent failures (e.g., error-path robustness in
PR #96).

PR-#96
PR-#34

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Initialization only checks for root, form, statusNode, and healthButton but not
diagnosticsStatusNode. setStatus() returns early if the node is not an HTMLElement, and
diagnostics actions pass diagnosticsStatusNode into beginStatus()/setStatus().

src/admin-settings.js[4-35]
src/admin-settings.js[107-134]
src/admin-settings.js[233-306]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The script treats `diagnosticsStatusNode` as optional during initialization, but later passes it into `beginStatus()`/`setStatus()`. When the node is null, `setStatus()` no-ops and `beginStatus()` may still clear the other area.
### Issue Context
The template in this PR includes the node, but this is a fragile coupling: any partial/custom markup, stale rendered HTML, or test DOM that omits `#etherpad-nextcloud-diagnostics-status` will lose diagnostics feedback.
### Fix Focus Areas
- src/admin-settings.js[4-35]
- src/admin-settings.js[107-134]
- src/admin-settings.js[233-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
8. Unit test mocks skip ok ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new Vitest admin-settings tests mock server responses without { ok: true }, so postJson()
treats them as failures and the tests unintentionally exercise the error path. This reduces the
tests’ value as regression coverage for the intended success behavior.
Code

tests/js/admin-settings.test.js[R81-87]

+	it('reports saving and diagnostics next to their own actions', async () => {
+		vi.stubGlobal('fetch', vi.fn(() => Promise.resolve({
+			ok: true,
+			status: 200,
+			text: () => Promise.resolve(JSON.stringify({ message: 'Connection ok.' })),
+		})))
+		await import(MODULE)
Relevance

●●● Strong

Repo has history of tightening Vitest mocks/expectations for correctness and isolation (accepted
test fixes in PR #96).

PR-#96

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
postJson() throws unless data.ok === true. Real endpoints (saveSettings/healthCheck) include
'ok' => true, but the tests’ mocked JSON bodies omit it.

tests/js/admin-settings.test.js[81-123]
src/admin-settings.js[181-215]
lib/Controller/AdminController.php[54-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`postJson()` requires parsed JSON to contain `ok: true` for success, but the new tests mock successful responses without that field. This means the tests validate status-area separation mainly through the error path.
### Issue Context
Backend controllers return `['ok' => true, ...]` on success.
### Fix Focus Areas
- tests/js/admin-settings.test.js[81-123]
- tests/js/admin-settings.test.js[96-116]
- lib/Controller/AdminController.php[54-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Diagnostics status node not required ✓ Resolved 🐞 Bug ☼ Reliability
Description
beginStatus() and setStatus() are called with diagnosticsStatusNode even though script
initialization does not require that element; if it’s missing, the save status gets cleared and
diagnostics progress/results are never shown. This makes the diagnostics actions silently lose UI
feedback in any DOM variant missing the new node.
Code

src/admin-settings.js[R111-134]

+	function setStatus(message, state, node = statusNode) {
+		if (!(node instanceof HTMLElement)) {
+			return
+		}
+		node.textContent = message
+		node.classList.remove('ep-status-success', 'ep-status-error')
  if (state === 'success') {
-			statusNode.classList.add('ep-status-success')
+			node.classList.add('ep-status-success')
  } else if (state === 'error') {
-			statusNode.classList.add('ep-status-error')
+			node.classList.add('ep-status-error')
+		}
+	}
+
+	// Starting an action does clear the other area: its result predates this
+	// action and could otherwise be read as belonging to it.
+	function beginStatus(message, node = statusNode) {
+		for (const other of [statusNode, diagnosticsStatusNode]) {
+			if (other instanceof HTMLElement && other !== node) {
+				other.textContent = ''
+				other.classList.remove('ep-status-success', 'ep-status-error')
+			}
  }
+		setStatus(message, null, node)
}
Relevance

●●● Strong

Team often accepts defensive JS hardening to avoid silent failures (e.g., error-path robustness in
PR #96).

PR-#96
PR-#34

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Initialization only checks for root, form, statusNode, and healthButton but not
diagnosticsStatusNode. setStatus() returns early if the node is not an HTMLElement, and
diagnostics actions pass diagnosticsStatusNode into beginStatus()/setStatus().

src/admin-settings.js[4-35]
src/admin-settings.js[107-134]
src/admin-settings.js[233-306]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The script treats `diagnosticsStatusNode` as optional during initialization, but later passes it into `beginStatus()`/`setStatus()`. When the node is null, `setStatus()` no-ops and `beginStatus()` may still clear the other area.
### Issue Context
The template in this PR includes the node, but this is a fragile coupling: any partial/custom markup, stale rendered HTML, or test DOM that omits `#etherpad-nextcloud-diagnostics-status` will lose diagnostics feedback.
### Fix Focus Areas
- src/admin-settings.js[4-35]
- src/admin-settings.js[107-134]
- src/admin-settings.js[233-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Unit test mocks skip ok ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new Vitest admin-settings tests mock server responses without { ok: true }, so postJson()
treats them as failures and the tests unintentionally exercise the error path. This reduces the
tests’ value as regression coverage for the intended success behavior.
Code

tests/js/admin-settings.test.js[R81-87]

+	it('reports saving and diagnostics next to their own actions', async () => {
+		vi.stubGlobal('fetch', vi.fn(() => Promise.resolve({
+			ok: true,
+			status: 200,
+			text: () => Promise.resolve(JSON.stringify({ message: 'Connection ok.' })),
+		})))
+		await import(MODULE)
Relevance

●●● Strong

Repo has history of tightening Vitest mocks/expectations for correctness and isolation (accepted
test fixes in PR #96).

PR-#96

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
postJson() throws unless data.ok === true. Real endpoints (saveSettings/healthCheck) include
'ok' => true, but the tests’ mocked JSON bodies omit it.

tests/js/admin-settings.test.js[81-123]
src/admin-settings.js[181-215]
lib/Controller/AdminController.php[54-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`postJson()` requires parsed JSON to contain `ok: true` for success, but the new tests mock successful responses without that field. This means the tests validate status-area separation mainly through the error path.
### Issue Context
Backend controllers return `['ok' => true, ...]` on success.
### Fix Focus Areas
- tests/js/admin-settings.test.js[81-123]
- tests/js/admin-settings.test.js[96-116]
- lib/Controller/AdminController.php[54-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Diagnostics status node not required ✓ Resolved 🐞 Bug ☼ Reliability
Description
beginStatus() and setStatus() are called with diagnosticsStatusNode even though script
initialization does not require that element; if it’s missing, the save status gets cleared and
diagnostics progress/results are never shown. This makes the diagnostics actions silently lose UI
feedback in any DOM variant missing the new node.
Code

src/admin-settings.js[R111-134]

+	function setStatus(message, state, node = statusNode) {
+		if (!(node instanceof HTMLElement)) {
+			return
+		}
+		node.textContent = message
+		node.classList.remove('ep-status-success', 'ep-status-error')
  	if (state === 'success') {
-			statusNode.classList.add('ep-status-success')
+			node.classList.add('ep-status-success')
  	} else if (state === 'error') {
-			statusNode.classList.add('ep-status-error')
+			node.classList.add('ep-status-error')
+		}
+	}
+
+	// Starting an action does clear the other area: its result predates this
+	// action and could otherwise be read as belonging to it.
+	function beginStatus(message, node = statusNode) {
+		for (const other of [statusNode, diagnosticsStatusNode]) {
+			if (other instanceof HTMLElement && other !== node) {
+				other.textContent = ''
+				other.classList.remove('ep-status-success', 'ep-status-error')
+			}
  	}
+		setStatus(message, null, node)
  }
Relevance

●●● Strong

Team often accepts defensive JS hardening to avoid silent failures (e.g., error-path robustness in
PR #96).

PR-#96
PR-#34

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Initialization only checks for root, form, statusNode, and healthButton but not
diagnosticsStatusNode. setStatus() returns early if the node is not an HTMLElement, and
diagnostics actions pass diagnosticsStatusNode into beginStatus()/setStatus().

src/admin-settings.js[4-35]
src/admin-settings.js[107-134]
src/admin-settings.js[233-306]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The script treats `diagnosticsStatusNode` as optional during initialization, but later passes it into `beginStatus()`/`setStatus()`. When the node is null, `setStatus()` no-ops and `beginStatus()` may still clear the other area.
### Issue Context
The template in this PR includes the node, but this is a fragile coupling: any partial/custom markup, stale rendered HTML, or test DOM that omits `#etherpad-nextcloud-diagnostics-status` will lose diagnostics feedback.
### Fix Focus Areas
- src/admin-settings.js[4-35]
- src/admin-settings.js[107-134]
- src/admin-settings.js[233-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Unit test mocks skip ok ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new Vitest admin-settings tests mock server responses without { ok: true }, so postJson()
treats them as failures and the tests unintentionally exercise the error path. This reduces the
tests’ value as regression coverage for the intended success behavior.
Code

tests/js/admin-settings.test.js[R81-87]

+	it('reports saving and diagnostics next to their own actions', async () => {
+		vi.stubGlobal('fetch', vi.fn(() => Promise.resolve({
+			ok: true,
+			status: 200,
+			text: () => Promise.resolve(JSON.stringify({ message: 'Connection ok.' })),
+		})))
+		await import(MODULE)
Relevance

●●● Strong

Repo has history of tightening Vitest mocks/expectations for correctness and isolation (accepted
test fixes in PR #96).

PR-#96

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
postJson() throws unless data.ok === true. Real endpoints (saveSettings/healthCheck) include
'ok' => true, but the tests’ mocked JSON bodies omit it.

tests/js/admin-settings.test.js[81-123]
src/admin-settings.js[181-215]
lib/Controller/AdminController.php[54-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`postJson()` requires parsed JSON to contain `ok: true` for success, but the new tests mock successful responses without that field. This means the tests validate status-area separation mainly through the error path.
### Issue Context
Backend controllers return `['ok' => true, ...]` on success.
### Fix Focus Areas
- tests/js/admin-settings.test.js[81-123]
- tests/js/admin-settings.test.js[96-116]
- lib/Controller/AdminController.php[54-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Admin settings: sectioned layout + independent save/diagnostics status

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Split admin settings into four sections with improved visual hierarchy and spacing.
• Show save vs diagnostics feedback next to the triggering buttons, without race overwrites.
• Add a11y links for hints via aria-describedby and announce “no pad type enabled” status.
Diagram

graph TD
A["AdminSettings.php"] --> B["admin-settings.php template"] --> C["admin-settings.css"]
B --> D["admin-settings.js"]
D --> G["Status DOM nodes"]
D --> E["Save endpoint"]
D --> F["Diagnostics endpoints"]
H["admin-settings.test.js"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Single status node with per-request tokens
  • ➕ No extra DOM nodes/IDs needed
  • ➕ Keeps feedback in one place
  • ➖ More complex concurrency handling (tokens, cancellation, or request IDs)
  • ➖ Harder UX: users must correlate actions with a shared status line
2. Use Nextcloud notification/toast APIs
  • ➕ Consistent UI pattern across Nextcloud apps
  • ➕ Avoids layout work for inline status areas
  • ➖ Less contextual than inline ‘next to the button’ feedback
  • ➖ May require additional framework plumbing and i18n tuning

Recommendation: The PR’s approach (separate status areas + beginStatus clearing only at action start) is the best trade-off: it’s simple, prevents async race overwrites, and improves UX by colocating feedback with the initiating controls. Alternatives were considered but add complexity or reduce contextual clarity.

Files changed (13) +345 / -77

Enhancement (9) +169 / -57
admin-settings.cssAdd section, hint, and checkbox block styling (no negative-margin hacks) +68/-10

Add section, hint, and checkbox block styling (no negative-margin hacks)

• Introduces explicit row classes for fields/checkboxes and consistent hint spacing. Adds section heading styling, restores label contrast/weight, and generalizes status color rules to apply to multiple status nodes.

css/admin-settings.css

de.jsAdd German strings for new settings sections and diagnostics hint +5/-0

Add German strings for new settings sections and diagnostics hint

• Adds localized labels for the new section headings and the diagnostics explanation text.

l10n/de.js

de.jsonAdd German JSON strings for new settings sections and diagnostics hint +5/-0

Add German JSON strings for new settings sections and diagnostics hint

• Keeps JSON translation output in sync with de.js for section headings and diagnostics hint.

l10n/de.json

es.jsAdd Spanish strings for new settings sections and diagnostics hint +5/-0

Add Spanish strings for new settings sections and diagnostics hint

• Adds translated labels for section headings and the diagnostics explanatory sentence.

l10n/es.js

es.jsonAdd Spanish JSON strings for new settings sections and diagnostics hint +5/-0

Add Spanish JSON strings for new settings sections and diagnostics hint

• Synchronizes the JSON translation file with the newly introduced UI section strings.

l10n/es.json

fr.jsAdd French strings for new settings sections and diagnostics hint +5/-0

Add French strings for new settings sections and diagnostics hint

• Introduces French translations for the section headings and diagnostics description.

l10n/fr.js

fr.jsonAdd French JSON strings for new settings sections and diagnostics hint +5/-0

Add French JSON strings for new settings sections and diagnostics hint

• Updates the JSON translation file to include the new UI section strings and diagnostics hint.

l10n/fr.json

AdminSettings.phpProvide l10n keys for new section headings and diagnostics hint +5/-0

Provide l10n keys for new section headings and diagnostics hint

• Adds new l10n entries for the four section titles plus a diagnostics explanatory hint, so the template can render headings and guidance text.

lib/Settings/AdminSettings.php

admin-settings.phpRestructure admin settings into sections and improve a11y hint associations +66/-47

Restructure admin settings into sections and improve a11y hint associations

• Groups fields into section headings and moves pad-type toggles to the top of their section. Adds aria-describedby links for hints, uses role="status" for the dynamic “no pad type enabled” hint, and splits save vs diagnostics status output into separate DOM nodes.

templates/admin-settings.php

Bug fix (2) +39 / -19
etherpad_nextcloud-admin-settings.mjsBundled admin settings JS: separate save vs diagnostics status nodes +1/-1

Bundled admin settings JS: separate save vs diagnostics status nodes

• Updates the compiled/minified bundle to write status messages into either the save or diagnostics status element. Prevents late responses from wiping the other section’s result while still clearing the other area at action start.

js/etherpad_nextcloud-admin-settings.mjs

admin-settings.jsSplit status handling into save vs diagnostics areas and avoid async overwrites +38/-18

Split status handling into save vs diagnostics areas and avoid async overwrites

• Adds a dedicated diagnostics status node and updates all diagnostics actions to write there. Introduces setStatus(node) and beginStatus(node) to ensure overlapping requests don’t clear unrelated results, while still clearing the other area when a new action begins.

src/admin-settings.js

Refactor (1) +1 / -1
etherpad_nextcloud-admin-settings.mjs.mapSource map update for new diagnostics status support +1/-1

Source map update for new diagnostics status support

• Refreshes sourcesContent/mappings to reflect the new diagnostics status node and the beginStatus/setStatus split.

js/etherpad_nextcloud-admin-settings.mjs.map

Tests (1) +136 / -0
admin-settings.test.jsAdd Vitest coverage for independent save/diagnostics status behavior +136/-0

Add Vitest coverage for independent save/diagnostics status behavior

• Introduces the first tests for admin-settings.js, including a regression test ensuring late save responses do not wipe an earlier diagnostics result. Also verifies per-action status placement and that starting a new action clears the other area.

tests/js/admin-settings.test.js

@qodo-code-review

qodo-code-review Bot commented Jul 31, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. E2E health check selector stale ✓ Resolved 🐞 Bug ≡ Correctness
Description
The health-check UI now reports status in the new diagnostics status node, but the Playwright helper
still asserts on the old save-status node, so the admin health-check E2E spec will fail. This breaks
CI coverage for verifying the configured Etherpad connection.
Code

src/admin-settings.js[256]

+			setStatus(message, 'success', diagnosticsStatusNode)
Relevance

●●● Strong

E2E suite is actively maintained; selector regressions would break CI, likely fixed promptly (see PR
#100).

PR-#100

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
src/admin-settings.js now writes health-check results to diagnosticsStatusNode, while the E2E
helper still looks at the save-status element #etherpad-nextcloud-admin-status after clicking the
health-check button.

src/admin-settings.js[233-257]
tests/e2e/fixtures/nextcloud.ts[50-57]
templates/admin-settings.php[179-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The E2E helper `runAdminEtherpadHealthCheck()` still reads `#etherpad-nextcloud-admin-status`, but the health check now writes to `#etherpad-nextcloud-diagnostics-status`, causing the E2E assertion to never observe `ep-status-success`.

### Issue Context
The PR intentionally split status output into separate areas (save vs diagnostics). Tests need to follow the new UX structure.

### Fix Focus Areas
- tests/e2e/fixtures/nextcloud.ts[50-57]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Diagnostics status node not required ✓ Resolved 🐞 Bug ☼ Reliability
Description
beginStatus() and setStatus() are called with diagnosticsStatusNode even though script
initialization does not require that element; if it’s missing, the save status gets cleared and
diagnostics progress/results are never shown. This makes the diagnostics actions silently lose UI
feedback in any DOM variant missing the new node.
Code

src/admin-settings.js[R111-134]

+	function setStatus(message, state, node = statusNode) {
+		if (!(node instanceof HTMLElement)) {
+			return
+		}
+		node.textContent = message
+		node.classList.remove('ep-status-success', 'ep-status-error')
		if (state === 'success') {
-			statusNode.classList.add('ep-status-success')
+			node.classList.add('ep-status-success')
		} else if (state === 'error') {
-			statusNode.classList.add('ep-status-error')
+			node.classList.add('ep-status-error')
+		}
+	}
+
+	// Starting an action does clear the other area: its result predates this
+	// action and could otherwise be read as belonging to it.
+	function beginStatus(message, node = statusNode) {
+		for (const other of [statusNode, diagnosticsStatusNode]) {
+			if (other instanceof HTMLElement && other !== node) {
+				other.textContent = ''
+				other.classList.remove('ep-status-success', 'ep-status-error')
+			}
		}
+		setStatus(message, null, node)
	}
Relevance

●●● Strong

Team often accepts defensive JS hardening to avoid silent failures (e.g., error-path robustness in
PR #96).

PR-#96
PR-#34

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Initialization only checks for root, form, statusNode, and healthButton but not
diagnosticsStatusNode. setStatus() returns early if the node is not an HTMLElement, and
diagnostics actions pass diagnosticsStatusNode into beginStatus()/setStatus().

src/admin-settings.js[4-35]
src/admin-settings.js[107-134]
src/admin-settings.js[233-306]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The script treats `diagnosticsStatusNode` as optional during initialization, but later passes it into `beginStatus()`/`setStatus()`. When the node is null, `setStatus()` no-ops and `beginStatus()` may still clear the other area.

### Issue Context
The template in this PR includes the node, but this is a fragile coupling: any partial/custom markup, stale rendered HTML, or test DOM that omits `#etherpad-nextcloud-diagnostics-status` will lose diagnostics feedback.

### Fix Focus Areas
- src/admin-settings.js[4-35]
- src/admin-settings.js[107-134]
- src/admin-settings.js[233-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unit test mocks skip ok ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new Vitest admin-settings tests mock server responses without { ok: true }, so postJson()
treats them as failures and the tests unintentionally exercise the error path. This reduces the
tests’ value as regression coverage for the intended success behavior.
Code

tests/js/admin-settings.test.js[R81-87]

+	it('reports saving and diagnostics next to their own actions', async () => {
+		vi.stubGlobal('fetch', vi.fn(() => Promise.resolve({
+			ok: true,
+			status: 200,
+			text: () => Promise.resolve(JSON.stringify({ message: 'Connection ok.' })),
+		})))
+		await import(MODULE)
Relevance

●●● Strong

Repo has history of tightening Vitest mocks/expectations for correctness and isolation (accepted
test fixes in PR #96).

PR-#96

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
postJson() throws unless data.ok === true. Real endpoints (saveSettings/healthCheck) include
'ok' => true, but the tests’ mocked JSON bodies omit it.

tests/js/admin-settings.test.js[81-123]
src/admin-settings.js[181-215]
lib/Controller/AdminController.php[54-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`postJson()` requires parsed JSON to contain `ok: true` for success, but the new tests mock successful responses without that field. This means the tests validate status-area separation mainly through the error path.

### Issue Context
Backend controllers return `['ok' => true, ...]` on success.

### Fix Focus Areas
- tests/js/admin-settings.test.js[81-123]
- tests/js/admin-settings.test.js[96-116]
- lib/Controller/AdminController.php[54-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/admin-settings.js Outdated
Comment thread src/admin-settings.js
Comment thread tests/js/admin-settings.test.js
Jaggob added 3 commits August 1, 2026 01:24
- The Playwright health-check helper still read the save status node, so
  the admin health check spec would have failed against the new markup.
- Diagnostics fall back to the save status area when the diagnostics area
  is absent, instead of silently dropping the feedback.
- The new unit tests mocked responses without `ok: true`, so they ran
  through the error path; they now assert the success path explicitly.
role="status" was set on an element toggled via `display`, so the text
arrived as initial content rather than a change and screen readers
commonly stayed silent. The element is now always rendered and the script
writes or clears its text, which is what a live region reacts to; an
empty hint collapses via `.ep-field-hint:empty`, so nothing moves
visually. Re-rendering identical text is skipped so a server-rendered
message is not announced on load.

The six new hint ids gain the app's `epnc-` prefix, matching every other
id in the template.
The explanations were written for the review and read as heavy in the
final code. Kept only the reasoning that is not visible from the code
itself, at roughly a third the length.
@Jaggob
Jaggob merged commit f38dbb6 into main Aug 1, 2026
13 checks passed
@Jaggob
Jaggob deleted the feat/admin-settings-sections branch August 1, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant