<bg-jobs>
bg-jobs.mjs
· the queue underneath it: Job queue
· the row reconciler it renders through: StableTable
An operator console for the background job queue: a nine-column
table of every job the tab can see, a row of counts by status, a Retry all action, and a
per-row delete. Use it on a page for inspecting jobs and resolving queue problems. It is not a progress indicator for one piece of work — for that, hold the
{id, done} the enqueue call returned, or poll
query_group(), and show a status indicator beside the feature that started the work.
The element exposes no attributes, properties, slots, events, or initialization options that change its behavior. <bg-jobs></bg-jobs> after a
<script type="module" src="/ui/components/bg-jobs.mjs"></script> is all the setup required — see Getting started for the page structure and Conventions for automatic registration on import.
The component cannot filter rows, choose columns, restrict itself to one group_id, hide destructive actions, or translate labels. If you need those features, use query_all() or query_group(), subscribe to events, and render a separate table. StableTable can provide the same row stability.
| Element | Attributes | Events | CSS custom properties |
|---|---|---|---|
<bg-jobs> |
none | none | none — it styles itself from the shared tokens and the .badge / .btn classes |
Each row uses the job's optional label, falling back to its type.
Suspended volatile work, and persistent work parked under a held lock (job queue § Yielding), shows paused in the row and summary;
it resumes when the feature releases its suspension.
The table is filled from query_all(), which
returns this tab's volatile jobs plus every persistent record currently in the database —
active and historic, whichever tab created them, whichever session. There is no filter of any
kind, so what the reader sees is the whole queue for the browser origin.
Both actions affect stored jobs directly. The
per-row delete calls delete_job() on that row.
It reaches a running job too: the run is stopped first and then the record goes,
which is the only way to end a job whose executor will not finish. Retry all calls
retry_all(), which resets and reprocesses
every pending, paused and failed job in the database — not the rows on screen, not a selection,
and not only the ones that arrived since the page loaded. For a persistent fetch job that means
the stored request is sent again, so pressing it resends requests and may repeat writes.
Use this element on a dedicated operator or diagnostics page. Adding it beside an ordinary feature would let users replay failed requests from unrelated work in the same browser.
This page has no live demo because it shares the application’s queue. These docs are served from the
same origin as the application around them and run on the same
configured queue names, so a <bg-jobs>
mounted here would list the reader's real jobs and its buttons would delete and replay those real jobs. The component has no option to isolate demo data. To try queue operations, use the demos on Job queue; to try the table’s row-update behaviour,
use the demos on StableTable.
The component's own stylesheet carries a rule about its host page, not about itself:
body > main:has(bg-jobs) { max-width: none; }
Nine columns do not fit a reading column, so wherever the element mounts inside a page's
<main>, that page goes full width. Three consequences are worth knowing before
you place it:
<main>. Other content in that
<main> also expands beyond the centred column, so prose elsewhere in the same main region also spans the available width.
body > main, so it uses the full selector instead of reducing its specificity through
:where().
A page that must keep its reading column therefore does not mount this element inside
<main>. The selector is anchored at body > main, so a
<main> nested deeper — inside a dialog, inside a demo — is not the one it
widens; these docs pages have no top-level <main>, which is why nothing here
changes width.
Nine columns, fixed widths from a <colgroup> so the table does not re-measure
as content changes:
| Column | Shows |
|---|---|
| ID | The first 8 characters of the job's UUID, monospaced — enough to match a row against a log line, not the key itself |
| Type | The job's type label, or empty when the caller supplied none |
| Group | The first 8 characters of group_id, or an em dash when the job is ungrouped |
| Status | A .badge carrying pending, running, paused, done, failed or cancelled |
| Attempts | attempts over max_attempts, defaulting to 0 and 5 |
| Error | The last error, truncated to one line; the full text is the cell's title |
| Created / Updated | Time then date, both formatted for the locale l10n currently reports; an em dash when the timestamp is absent |
| (unlabelled) | The delete button, an icon-only .btn-icon with an aria-label |
With no jobs at all the table is hidden and an empty-state line takes its place — see Status messages for the vocabulary that line belongs to.
Jobs are sorted newest-first by created_at and then re-grouped: every job
sharing a group_id is emitted as one contiguous block, groups are ordered by their newest job, and the ungrouped jobs come last however new they are. As a result, individual rows need not follow chronological order: an older job can appear near the top because a newer job belongs to the same group. This keeps each batch together. The Created column therefore does not imply a strict chronological order for the whole table.
A total and one badge per status are recomputed on each render from the same job array as the table. The counts appear beside Retry all.
A queue under load re-renders constantly, and reflow could move a delete button while the user is trying to click it. The element renders through
StableTable, which is what prevents that: while the
pointer is over the rows or focus is inside them, changes to the row set are deferred and column widths stay fixed. Cell contents can still update. Leaving the table applies the deferred changes.
A click inside the rows allows structural updates for one second. Deleting a job explicitly requests a row change. Apply it immediately so the deleted row disappears without requiring the user to move the pointer.
This element needs a notification surface on the page to be fully usable. While
an update is held back, the table registers a short message and an action to apply the update in the
notifications store — which is only visible if the page also
mounts a <ui-statusbar> or a
<ui-notifications>. Without one, the message has no visible display and the table simply looks frozen for as long as the
pointer rests on it. Mount a surface next to it; the store is shared, so any surface on the page
will do.
Safe to mount, unmount and re-mount — a tab switcher does exactly that — because each connect
rebuilds the element from scratch: fresh markup, a fresh row reconciler, a fresh subscription,
and a fresh query_all(). Disconnecting removes the subscription and destroys the reconciler, releasing its resources.
Preserve the generation guard if you reuse this pattern.
A generation counter is bumped on both connect and disconnect, and a
query_all() that resolves against an older generation is discarded. Without it, a
snapshot requested before a disconnect could arrive after reconnection and restore jobs deleted while the update subscription was detached. Any component that starts an async query in
connectedCallback can encounter this race; a boolean isConnected check cannot detect it because the element has already reconnected.
The heading, the column names, the button and the empty-state line are written into the element's
own template in English, and there is no attribute to override them — unlike
<ui-statusbar>, whose accessible name is an
observed attribute a page can set. Only the timestamps follow the active locale.
So an application that needs this table in another language cannot get there by translating an
attribute; it either accepts English here, or renders its own table over the same queue. See
Localization for how a page's own markup is translated, and why an element
that builds its content in connectedCallback is outside that mechanism's reach.