From fd6ecb4366da7cd029675b89ae3b8b33dbf696c6 Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 18 Jul 2026 17:05:43 -0700 Subject: [PATCH 1/2] Browser-UI errors should retain form field values --- package.json | 4 -- public/assets/sandbox-ui.js | 61 ++++++++++++++++++++++++ tests/Integration/WebApplicationTest.php | 4 ++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 70de9769..7435e0a7 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,6 @@ 6 ], "block-spacing": "error", - "array-bracket-spacing": [ - "error", - "always" - ], "multiline-comment-style": "error", "computed-property-spacing": "off", "space-in-parens": "off", diff --git a/public/assets/sandbox-ui.js b/public/assets/sandbox-ui.js index 2603e6f3..e355577a 100644 --- a/public/assets/sandbox-ui.js +++ b/public/assets/sandbox-ui.js @@ -19,6 +19,63 @@ function sameAction(left, right) { && left.source === right.source; } +/** Captures user-editable controls before dashboard markup is replaced. */ +function snapshotForm(form) { + const occurrences = new Map(); + return Array.from(form.elements).flatMap((control) => { + if (!control.name || ['hidden', 'submit', 'button', 'reset', 'file'].includes(control.type)) { + return []; + } + + const occurrence = occurrences.get(control.name) || 0; + occurrences.set(control.name, occurrence + 1); + return [{ + name: control.name, + occurrence, + value: control.value, + checked: control.checked, + selected: control.options ? Array.from(control.options).filter((option) => option.selected).map((option) => option.value) : null, + }]; + }); +} + +/** Finds the regenerated form without relying on mutable field values. */ +function actionForm(context) { + const candidates = Array.from(dashboard.querySelectorAll('form[data-ajax]')).filter((form) => { + const candidate = actionContext(form); + return candidate.action === context.action + && candidate.board === context.board + && candidate.section === context.section; + }); + + return candidates.find((form) => sameAction(actionContext(form), context)) || (candidates.length === 1 ? candidates[0] : null); +} + +/** Restores submitted values after an error response regenerates the dashboard. */ +function restoreForm(context, snapshot) { + const form = actionForm(context); + if (!form) { + return; + } + + for (const saved of snapshot) { + const controls = Array.from(form.elements).filter((control) => control.name === saved.name); + const control = controls[saved.occurrence]; + if (!control) { + continue; + } + if (saved.selected !== null) { + for (const option of control.options) { + option.selected = saved.selected.includes(option.value); + } + } else if (control.type === 'checkbox' || control.type === 'radio') { + control.checked = saved.checked; + } else { + control.value = saved.value; + } + } +} + /** Reapplies pending state after AJAX replaces dashboard markup. */ function syncPendingActions() { pendingActions.forEach((pending) => { @@ -136,6 +193,7 @@ function bindAjax() { } const context = actionContext(form); + const formSnapshot = snapshotForm(form); const submitter = event.submitter; const original = submitter ? submitter.innerHTML : ''; const actionId = nextActionId++; @@ -170,6 +228,9 @@ function bindAjax() { if (typeof data.html === 'string' && data.html !== '') { dashboard.innerHTML = data.html; bindAjax(); + if (data.error) { + restoreForm(context, formSnapshot); + } syncPendingActions(); } showActionResult(data, context); diff --git a/tests/Integration/WebApplicationTest.php b/tests/Integration/WebApplicationTest.php index 1b747add..1de654c4 100644 --- a/tests/Integration/WebApplicationTest.php +++ b/tests/Integration/WebApplicationTest.php @@ -119,6 +119,10 @@ public function testDashboardJavascriptTracksConcurrentActions(): void self::assertStringContainsString('entry.status = runningAssigned ? \'queued\' : \'running\';', $javascript); self::assertStringContainsString("document.querySelectorAll('[data-mounted-toggle]')", $javascript); self::assertStringContainsString("button.textContent = expanded ? button.dataset.moreLabel : 'Show less';", $javascript); + self::assertStringContainsString('const formSnapshot = snapshotForm(form);', $javascript); + self::assertStringContainsString('if (data.error) {', $javascript); + self::assertStringContainsString('restoreForm(context, formSnapshot);', $javascript); + self::assertStringContainsString("control.type === 'checkbox' || control.type === 'radio'", $javascript); } public function testNarrowBoardCardsStackMountedLists(): void From 308c6749ede1f89a30f3bd0f9fa480596e52eb2f Mon Sep 17 00:00:00 2001 From: Matt Friedman Date: Sat, 18 Jul 2026 17:06:24 -0700 Subject: [PATCH 2/2] Rewrite browser-ui section text to be more user friendly --- src/QuickInstall/Sandbox/Web/templates/dashboard.php | 10 +++++----- src/QuickInstall/Sandbox/Web/templates/layout.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/QuickInstall/Sandbox/Web/templates/dashboard.php b/src/QuickInstall/Sandbox/Web/templates/dashboard.php index 07473ba9..491d3991 100644 --- a/src/QuickInstall/Sandbox/Web/templates/dashboard.php +++ b/src/QuickInstall/Sandbox/Web/templates/dashboard.php @@ -57,7 +57,7 @@

Boards

-

Start, stop, seed, and inspect local Docker-backed phpBB installs.

+

Manage your local phpBB boards and add sample content.

@@ -159,7 +159,7 @@

Create board

-

Choose the phpBB source, runtime, port, and optional seed preset.

+

Set up a new phpBB board for development or testing.

@@ -181,7 +181,7 @@

Customisations

-

Bind or copy local extension and style work into a board.

+

Add extensions and styles from your local projects.

@@ -209,7 +209,7 @@

Sources

-

Fetch and inspect phpBB sources used by boards.

+

Manage the phpBB versions available for your boards.

@@ -264,7 +264,7 @@

Activity log

-

Queued and completed actions from this browser session.

+

Review actions and results from this session.

diff --git a/src/QuickInstall/Sandbox/Web/templates/layout.php b/src/QuickInstall/Sandbox/Web/templates/layout.php index 7346e426..75ccd98d 100644 --- a/src/QuickInstall/Sandbox/Web/templates/layout.php +++ b/src/QuickInstall/Sandbox/Web/templates/layout.php @@ -46,7 +46,7 @@

QuickInstall Dashboard

-

Manage disposable phpBB boards backed by the same Docker services as the CLI.

+

Create and manage local phpBB boards for development and testing.