From ef1c2081eca2b6bc9a46ce673935c21194c1981b Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Thu, 3 Sep 2026 16:14:29 +0200 Subject: [PATCH] Put the failed-save message in the sticky bar, beside Save At the top of the panel the message could be scrolled out of sight by the very fields it was about, while the button that produced it sits at the foot. It now shares the sticky bar with that button, so it cannot scroll away. The bar stacks: the message takes a full-width line that wraps, above the row with the status and the button, so Save stays exactly where it was and the text is not clipped to one line in a 320px aside. The pending count stays alongside it. The edits are still there and the save is retryable, which is worth showing rather than replacing with the failure. Co-Authored-By: Claude Opus 5 (1M context) --- .../task-execution-inputs-panel.css | 22 ++++++++---- .../task-execution-inputs-panel.html | 35 ++++++++++--------- .../task-execution-inputs-panel.spec.ts | 11 +++++- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css index ea69858..f70b3d2 100644 --- a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css +++ b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.css @@ -289,15 +289,21 @@ position: sticky; bottom: 0; display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; + flex-direction: column; + gap: 6px; margin: 4px -12px -12px; padding: 8px 12px; border-top: 1px solid #e2e8f0; background: #ffffff; } +.inputs-panel-savebar-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + .inputs-panel-savebar-status { min-width: 0; overflow: hidden; @@ -420,17 +426,21 @@ color: #64748b; } -/* One message for the whole panel, kept above the fields it concerns. */ +/* + * One message for the whole panel, in the sticky bar beside the button that triggered it: at the + * top of the panel it could be scrolled out of sight by the very fields it was about. + */ .inputs-panel-alert { display: flex; align-items: flex-start; gap: 6px; - padding: 7px 9px; + padding: 6px 8px; border: 1px solid #fecaca; border-radius: 6px; background: #fef2f2; color: #b91c1c; - font-size: 12px; + font-size: 11.5px; + line-height: 1.35; } .inputs-panel-alert .mat-icon { diff --git a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html index 9fa4af7..2e585a3 100644 --- a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html +++ b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.html @@ -71,13 +71,6 @@
No manual inputs required.
} @else { - @if (saveError(); as error) { - - } -
Inputs
@@ -113,16 +106,24 @@ @if (editableInputs().length && !readOnly()) {
- - @if (pendingCount()) { - {{ pendingCount() }} unsaved {{ pendingCount() === 1 ? 'change' : 'changes' }} - } @else { - All changes saved - } - - + @if (saveError(); as error) { + + } +
+ + @if (pendingCount()) { + {{ pendingCount() }} unsaved {{ pendingCount() === 1 ? 'change' : 'changes' }} + } @else { + All changes saved + } + + +
} } diff --git a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts index 1336470..b3c7ffa 100644 --- a/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts +++ b/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel.spec.ts @@ -269,11 +269,20 @@ describe('TaskExecutionInputsPanelComponent', () => { makeInput({ key: 'g:a', inputName: 'a' }), makeInput({ key: 'g:b', inputName: 'b' }), makeInput({ key: 'g:c', inputName: 'c' }) - ], { saveError: 'Could not save the global inputs, so none of them were saved.' }); + ], { + pendingKeys: ['g:a', 'g:b', 'g:c'], + saveError: 'Could not save the global inputs, so none of them were saved.' + }); const alerts = fixture.nativeElement.querySelectorAll('.inputs-panel-alert'); expect(alerts.length).toBe(1); expect(alerts[0].textContent).toContain('none of them were saved'); + // In the sticky bar, next to the button that triggered it: at the top of a long panel it could + // be scrolled out of sight by the very fields it was about. + expect(fixture.nativeElement.querySelector('.inputs-panel-savebar .inputs-panel-alert')).not.toBeNull(); + // The edits are still pending, so the bar still counts them: the save is retryable. + expect(fixture.nativeElement.textContent).toContain('3 unsaved changes'); + expect(fixture.componentInstance.canSubmitAll()).toBe(true); }); it('shows no message when nothing has failed', async () => {