feat(jobs): let a panel answer for every kind, at either density

Three properties the phone's band needs, added here so it is the same
panel rather than a second job UI -- which is what keeps
`applyJobControl` and its "you will discard hours of downloading"
confirmation in the picture.

`kinds="*"` is every kind, which is what the header indicator was for.
Spelled as a star rather than taken as the meaning of an empty
attribute, because empty is what a typo and a dropped binding both
produce and "show everything" is the wrong thing to do by accident;
empty still shows nothing.

`density` is passed to `job-row`, whose `compact` variant its own
source calls "the popover density" -- which is exactly what the band
replaces. `full` stays the default, so the four settings call sites are
untouched.

`active-only` drops terminal rows. The band is in the layout, so a
finished row there holds the content down after the work is done;
Settings keeps them, because that is where "did the last scan work" is
asked and a finished row there dismisses itself.
This commit is contained in:
2026-08-20 18:17:20 -04:00
parent c19a806298
commit 502b814a65
2 changed files with 103 additions and 3 deletions
@@ -76,6 +76,68 @@ describe('<job-panel>', () => {
expect(titles(el)).toEqual(['Building the index', 'Filling in artists']);
});
/**
* #62. The phone's band has no kinds to name: it is standing in for
* the header indicator, whose whole job was to be the one view of
* everything at once.
*/
it('answers for every kind when asked with a star', async () => {
const el = await fixture<LitElement>('job-panel', { kinds: '*' });
await snapshot([
job({ id: 'scan:1', kind: 'library-scan', title: 'Scanning Music' }),
job({ id: 'idx', kind: 'index-build', title: 'Building the index' }),
job({ id: 'dl:1', kind: 'download', title: 'Downloading Glass Harbour' }),
]);
await el.updateComplete;
expect(titles(el)).toEqual([
'Scanning Music',
'Building the index',
'Downloading Glass Harbour',
]);
});
/**
* The other half of that, and the reason it is a star rather than the
* meaning of an empty attribute: empty is what a typo and a dropped
* binding both produce, and "show everything" is the wrong thing to
* do by accident.
*/
it('still shows nothing when asked for nothing', async () => {
const el = await fixture<LitElement>('job-panel', { kinds: '' });
await snapshot([job({ id: 'scan:1', kind: 'library-scan' })]);
await el.updateComplete;
expect([el.hidden, rows(el)].map(String)).toEqual(['true', '']);
});
/**
* `full` stays the default so the four settings call sites are
* untouched; the band asks for the density `job-row` calls "the
* popover density", because on the phone this panel *is* the popover.
*/
it('passes its density to the rows, defaulting to full', async () => {
const settings = await fixture<LitElement>('job-panel', { kinds: '*' });
await snapshot([job()]);
await settings.updateComplete;
const band = await fixture<LitElement>('job-panel', {
kinds: '*',
density: 'compact',
});
await snapshot([job()]);
await band.updateComplete;
expect([
rows(settings)[0]?.getAttribute('variant'),
rows(band)[0]?.getAttribute('variant'),
]).toEqual(['full', 'compact']);
});
/**
* An idle panel in four places is four pieces of furniture describing
* an absence — and `hidden` rather than an empty render, because the