Skip to content

feat(task-tray): backgroundable running ops + corrected modal buttons [OS-461]#2669

Closed
elibosley wants to merge 31 commits into
feat/backend-task-queuefrom
feat/task-modal-buttons
Closed

feat(task-tray): backgroundable running ops + corrected modal buttons [OS-461]#2669
elibosley wants to merge 31 commits into
feat/backend-task-queuefrom
feat/task-modal-buttons

Conversation

@elibosley

@elibosley elibosley commented Jun 18, 2026

Copy link
Copy Markdown
Member

Built on #2665 (base = feat/backend-task-queue). It edits foregroundTask() / the task tray, which only exist in #2665, so it targets that branch until #2665 merges (then GitHub auto-retargets to master).

Summary

Fixes the foreground task modal's behavior and gives it a compact, theme-aware restyle. The behavioral issues were rooted in one place — the modal drove a single button off an inverted per-type button flag in foregroundTask(), which for docker/vmaction hid the close button for the whole running phase (can't background) and left the confirm button [disabled], surfacing SweetAlert's la-ball-fall loader (the "bouncing dots").

Behavior

  • Backgroundable ops — a top-corner minimize backgrounds any running task (it keeps running under the daemon, stays in the tray). Fixes docker being non-backgroundable. No disabled-button loader, so the bouncing dots are gone.
  • Status-driven buttons — running shows the corner minimize; on done/error the corner becomes a plain close (keeps the task in the tray) and a primary Dismiss appears (clears it). Mirrors the tray tile's Show + Dismiss.
  • Esc while running backgrounds instead of deleting.

Look

  • Compact centered modal (deliberately not a full-height sidebar — a transient progress log shouldn't take over the screen). max-width: 42rem, max-height: 85vh, log scrolls inside.
  • Smaller header (1.6rem, left-aligned) with the status on a muted subline.
  • Themed throughout — all colors are theme tokens (--dynamix-sweet-alert-*, --shade-bg-color, --alt-text-color, --border-color, --brand-orange), so it adapts across white / azure / black / gray. (Notably, azure/white override the sweet-alert tokens to a light surface — the earlier hardcoded-white close icon was invisible there; it now uses the modal's own text-color token.)
  • Log in a card, brand-orange Dismiss button.
  • Mobile (≤767px) → fullscreen (a centered card is cramped on phones).

Files

  • BodyInlineJS.php — status-driven foregroundTask(); minimizeForegroundTask().
  • HeadInlineJS.phpopenDone/openError swap the corner control for the Dismiss primary.
  • default-base.css — centered-card restyle + theme tokens + mobile fullscreen.

Notes

  • No caller passes the button flag, so no behavior contract changes; task.button is now inert (follow-up: drop it from the record + open* signatures).
  • php -l clean; swal / swal.close() are globals in the bundled SweetAlert v1.
  • Dogfooded on a dev box; warrants a live pass per theme before merge.

Closes OS-461

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6c17297a-66f6-43ca-a70f-6d8bba893e9e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-modal-buttons

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.06.19.1437
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2669/webgui-pr-2669.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix.plugin.manager/Plugins.page
emhttp/plugins/dynamix.plugin.manager/include/PluginHelpers.php
emhttp/plugins/dynamix/include/DefaultPageLayout/BodyInlineJS.php
emhttp/plugins/dynamix/include/DefaultPageLayout/HeadInlineJS.php
emhttp/plugins/dynamix/include/TaskCommand.php
emhttp/plugins/dynamix/include/TaskQueue.php
emhttp/plugins/dynamix/nchan/tasks
emhttp/plugins/dynamix/styles/default-base.css

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2669, or run:

plugin remove webgui-pr-2669

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@elibosley elibosley force-pushed the feat/backend-task-queue branch from d54a2d6 to 9d016d8 Compare June 19, 2026 14:37
elibosley and others added 27 commits June 19, 2026 10:37
Apply robot review feedback for the backend task queue:

- TaskQueue.php: escape the full bash -c payload with escapeshellarg() to
  close a single-quote shell-injection vector while preserving multi-arg
  word splitting; serialize create->launch per type with flock to keep the
  one-running-task-per-type invariant under concurrent requests.
- nchan/tasks: require numeric pid before the /proc liveness check;
  recover queued-only tasks so the daemon never sleeps with pending work;
  match _ERROR_ as a discrete RS-delimited record (read tail in PHP).
- TaskCommand.php: validate pid is numeric before kill.
- BodyInlineJS.php: escape task.title in the swal title and task ids in the
  tray onclick handlers.
- HeadInlineJS.php: surface a createTask() failure via .fail().
- default-base.css: add min-width:0 so .op-title ellipsizes reliably.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The foreground task modal drove its single button off an inverted per-type
`button` flag. For docker/vmaction (default button=0) this hid the Close
button for the entire running phase AND left the confirm button disabled,
which surfaced SweetAlert's `.la-ball-fall` loader (the "bouncing green
dots") with no way to background the operation.

Drive the modal by task status instead, uniformly for all types:

- running  -> a top-corner minimize control backgrounds the task (it keeps
              running under the daemon and stays in the tray); no disabled
              button, so the bouncing-dots loader never appears. The
              spinning title icon remains the in-progress indicator.
- done/err -> a single primary "Dismiss" button that fires the reload
              callback and clears the task from the tray.

Esc while running now backgrounds (instead of deleting) since the
done-callback checks live status. No caller passes the `button` flag, so
no real behavior contract changes; `task.button` is now inert (follow-up:
drop it from the task record + open* signatures).

Stacked on the backend task queue (#2665).

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The foreground task modal is a 90vw centered, rounded card — on phones that
reads as a cramped "circular" blob. On <=767px take it fullscreen (flush,
no radius), pin the title at top, let the log fill and scroll, and keep the
corner minimize + bottom Dismiss reachable. Scoped to `.nchan` so ordinary
confirm/warning dialogs keep their dialog look.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per design review: the top-corner control is now a persistent "close this
window" present in every state, rather than vanishing when the task finishes.
While running it reads as minimize (fa-window-minimize, task keeps running);
once finished it swaps to a plain close (fa-times, task stays in the tray).
Removal stays the separate primary Dismiss action — mirroring the tray tile's
Show + Dismiss split: close-and-keep (corner) vs dismiss-and-remove (button).

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The control was colored with --text-color at 60% opacity, but the modal is a
dark surface (.sweet-alert bg = --black), so on the default theme it was
dark-on-dark and easy to miss. Make it a light, bordered circular button with a
brand-orange hover, bump the hit area (and again on mobile for touch), and use a
cleaner fa-minus for the running/minimize glyph (fa-times stays for close).

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align the foreground task modal with the Unraid notifications sidebar: dock it
to the right as a full-height sheet (full-width on mobile), shrink the header
and move the status onto a muted subline, put the log in a card, and give it a
brand-orange primary Dismiss button.

Critically, all colors now come from theme tokens (--dynamix-sweet-alert-*,
--alt-text-color, --shade-bg-color, --border-color, --brand-orange) instead of
hardcoded light values, so it adapts correctly across white / azure / black /
gray. (azure/white override the sweet-alert tokens to a LIGHT surface, which is
why the previous hardcoded-white close icon was invisible.)

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump the docked sheet to 46rem (max 95vw) and pin min/height to 100dvh +
top/bottom:0 so it always fills the available height.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pull the desktop form back from a full-height right sidebar to a compact,
centered card — a transient progress log shouldn't take over the screen, and a
familiar centered modal is lower-risk for the docker/plugin update flow. All the
substance stays: smaller header + muted status subline, themed log card,
brand-orange Dismiss, minimize/close semantics, theme-token colors, and mobile
fullscreen.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sparent)

SweetAlert sets background-color:transparent inline on the confirm button
(default confirmButtonColor), which beat the CSS class and left the orange
Dismiss button white-on-white. Override bg/color/border with !important.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…carousel

The scroll-to-top/bottom buttons and the task tray both sit bottom-right at the
same z-index, so the buttons covered the task tiles. Raise the tray above them
and, when the tray has tasks, move the buttons clear (bottom-left on desktop,
lifted above the tray on mobile) via the #opTray sibling selector.

On mobile the tray is now a full-width horizontal scroll-snap carousel — one
task tile at a time, swipe between them — instead of a bottom-right stack.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…swap)

The corner control flipped from minimize (-) while running to a close (x) when
done, but that 'x' still only minimized (kept the task in the tray) while the
Dismiss button was the real remove. Confusing. Keep the corner as a single,
consistent minimize in every state; Dismiss remains the close-and-remove.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… screens

- Close: fade the modal out with .nchan still applied, then strip the class
  after (~350ms) via a shared nchanCloseModal(). Removing .nchan while visible
  snapped it back to the default swal look for a frame (the flash). Guard with
  pointer-events:none and a reopen check.
- Width: bump max-width 42rem -> 60rem so it's as wide as it used to be on
  large screens (still 90vw on smaller ones).

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…button)

The Dismiss button was centered (swal's .sa-confirm-button-container centers it)
in a tall footer (margin-top + a hidden loader reserving space). Make it a
full-width button in a slim footer: deliberate, less vertical space, consistent
desktop/mobile.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SweetAlert adds padding-bottom:40px when no buttons are shown (the running
state has no footer button), and that selector outspecified .nchan { padding:0 }.
Override it with a matching higher-specificity rule.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… placement)

Switch from full-width to a compact bottom-right Dismiss - lighter, more
dialog-like.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…was centering)

.sa-button-container is itself a flex container with justify-content:center, and
the inner confirm-container is only button-width, so right-aligning the inner one
did nothing. Right-align the outer container instead.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The log sat in its own bordered, filled box inside the modal that already has a
header divider and footer divider - a box-in-a-box. Make the log a plain region
framed by the dividers (header / body / footer).

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ty strip)

The running modal has no Dismiss button, but the footer container still rendered
its top border + padding as an empty bottom bar. Hide it when no confirm button.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…85vh

The log used flex:1 1 auto and stretched the modal full height, leaving a big
empty gap for short logs. Use flex:0 1 auto so the modal hugs its content and
only grows/scrolls once the log exceeds the 85vh cap.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…title

Move the In Progress/Finished/Error status out of the title subline into a
distinct, colored state section between the title and the log: blue while
running, green when finished, red on error (with an icon). openDone/openError
recolor it.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e strip

- height: the swal @media(max-width:1200px){height:95vh} forced the modal tall,
  pooling empty space below the footer. Set .nchan height:auto so it sizes to
  content (capped at 85vh, scrolls beyond). Dismiss now sits at the real bottom.
- state strip: drop the dated pale-color banner for a subtle translucent tint +
  colored icon + theme (--text-color) text, readable on light and dark.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per feedback: a stable button beats a modal that grows and shifts as the log
streams. Fixed height 34rem (capped at 85vh on short screens); the log fills and
scrolls in place, footer pinned to the bottom. Mobile stays fullscreen.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The modal opens running with showConfirmButton:false (data-has-confirm-button=
false), and the footer is hidden by CSS keyed on that attribute. openDone/openError
showed the button but never flipped the attribute, so the footer stayed hidden
until the modal was reopened. Set data-has-confirm-button=true on completion.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ning

Minimizing the progress modal during a plugin update left the row's Update
button visible and clickable (stale) - you could start a duplicate update. Drive
the button off the live task queue: while a running/queued plugins task targets a
plugin, its update/install button shows 'Upgrading' and is disabled. Survives
modal minimize, list reloads and page reloads via a window.onTaskListChanged hook
fired from onTaskListUpdate. Also align the in-progress modal status line with the
new state strip.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…task queue)

Replace the client-side string-matching/busy-toggle with a single source of
truth: make_link() now renders 'Upgrading'/'Installing' (disabled) when the task
queue has a running/queued task for that .plg (plugin_task_busy via task_list,
precise token match). The Plugins page just triggers a cheap status-cell refresh
(loadlist(null,1), no network re-check) when the active plugin-task set changes.
Correct on every render path - page load, reload, completion - and generalizes
better than the per-page client logic.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s rendering

The tabbed content renderer (generateContent) Markdown-processes a page body
unless the header sets Markdown="false". Plugins.page lacked it (unlike the
script-heavy Docker/VM pages), so its inline <script> got Markdown-mangled
(<script> wrapped in <p>, * -> <em>, < -> &lt;), initlist() never ran and the
table rendered empty. Add the flag like the other script pages.

Unrelated to the Upgrading change (diff confirmed) - a latent page-header bug
exposed by the tabbed renderer.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
elibosley and others added 4 commits June 19, 2026 10:37
The mobile carousel tiles and their action icons were small and hard to tap.
Give tiles more padding + a min-height and make the action icons proper ~48px
touch targets (inline-flex, min-width/height, larger glyph).

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hard to tell the mobile tray was swipeable. Make each tile 85% width so the
neighbour peeks in (a swipe cue), end-align the snap, and scroll the carousel to
the newest task on render instead of starting on the leftmost 'Clear finished'
header.

OS-461

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the horizontal swipe carousel on mobile with the iOS/Android
grouped-notification pattern. When multiple background operations are
active the tray collapses to the newest card with the rest peeking
behind it (faux stacked edges + a count badge); tapping it expands the
full vertical list so all operations can be viewed and cleared at once.
A collapse chevron returns to the single card.

Desktop is unchanged (always a full vertical stack). The carousel
scroll-snap/scrollLeft behaviour and its default-to-newest hack are
removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mobile stacked tray (larger card + peeking edges + count badge) sits
higher than the old carousel, so raise the scroll-to-top/bottom buttons
from 6rem to 7.5rem to keep them clear of it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@elibosley elibosley force-pushed the feat/task-modal-buttons branch from 33a9007 to 31b24c4 Compare June 19, 2026 14:37
elibosley added a commit that referenced this pull request Jun 22, 2026
…lds #2669, OS-461)

Squash of feat/task-modal-buttons (PR #2669, 31 iteration commits) into the
consolidated task-queue PR. Adds backgroundable running operations with a
corrected modal button set, drives the plugin "Upgrading" button state from
the server-side task queue, and a round of task-modal/tray styling
(state strip, dismiss placement, mobile grouped-notification carousel).

Touches only the tray/modal UI (BodyInlineJS, HeadInlineJS, default-base.css)
and the Plugins page (Plugins.page, PluginHelpers.php); no changes to the
core queue/daemon files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@elibosley

Copy link
Copy Markdown
Member Author

Folded into #2665 (the consolidated task-queue PR) as a single squashed commit — feat(task-tray): backgroundable running ops + modal button polish (folds #2669, OS-461) (d36abaa…7b4ffa0). The 31 UI-iteration commits are collapsed into one; no functional change dropped. Closing in favor of #2665.

@elibosley elibosley closed this Jun 22, 2026
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