desk: served row order, one open count #84

Merged
void merged 1 commit from void/desk-order-count into dev 2026-11-10 21:13:24 +00:00
4 changed files with 48 additions and 32 deletions

View file

@ -100,8 +100,18 @@ describe('DeskView', () => {
const html = view('noAnswers', ScreenSize.Desktop, 'void', new Set(['c:question:plain']));
expect(html).toContain('does not match the hash it was published with');
expect(html).not.toContain('First way');
expect(html).not.toContain('Just one fork');
expect(html).toContain('Nothing is waiting');
expect(html).toContain('1 open.');
expect(html).not.toContain('Nothing is waiting');
});
it('never claims an empty desk while the list says forks are open, whatever rows it could show', () => {
setDeskSource(() => undefined);
const html = renderToStaticMarkup(
<ScreenSizeProvider value={ScreenSize.Desktop}>
<DeskView mind="void" label="Void" list={{ ...STATES.empty, open_count: 3 }} />
</ScreenSizeProvider>
);
expect(html).toContain('3 open.');
expect(html).not.toContain('Nothing is waiting');
});
it('shows no booth for a Mind without art rather than another face', () => {
expect(view('noAnswers', ScreenSize.Desktop, 'nobody')).not.toContain('data-pose');

View file

@ -43,9 +43,18 @@ describe('buildDesk', () => {
]);
expect(d.open.map((r) => r.forkId)).toEqual(['c:question:live']);
});
it('orders open rows by the card order, oldest admission first, whatever order the list came in', () => {
const d = buildDesk(list([entry('late', { order: 9 }), entry('early', { order: 2 })]));
expect(d.open.map((r) => r.forkId)).toEqual(['c:question:early', 'c:question:late']);
it('keeps the served order (newest first by card.order) and sorts nothing itself', () => {
const served = list([entry('late', { order: 9 }), entry('early', { order: 2 })]);
expect(buildDesk(served).open.map((r) => r.forkId)).toEqual([
'c:question:late',
'c:question:early',
]);
const odd = list([entry('a', { order: 2 }), entry('b', { order: 9 })]);
expect(buildDesk(odd).open.map((r) => r.forkId)).toEqual(['c:question:a', 'c:question:b']);
});
it("takes the open count from the list's open_count, not from the rows it could verify or carries", () => {
const d = buildDesk({ ...list([entry('only')]), open_count: 4 }, new Set(['c:question:only']));
expect(d.counts.open).toBe(4);
});
it('marks the answer the record names as counted, and no other', () => {
const [row] = questions(buildDesk(STATES.answered));
@ -92,6 +101,5 @@ describe('buildDesk', () => {
it('withdraws an unverified open card and does not count it', () => {
const d = buildDesk(STATES.noAnswers, new Set(['c:question:plain']));
expect(d.open).toEqual([{ kind: 'unverified', forkId: 'c:question:plain' }]);
expect(d.counts.open).toBe(0);
});
});

View file

@ -39,7 +39,7 @@ export type Desk = {
open: DeskRow[];
/** Closed with a ruling id. A card closed with no ruling (withdrawn) is not listed. */
recorded: { forkId: string; title: string; rulingId: string }[];
/** Verified open questions on the page, for the booth mood. */
/** The list's own open_count: the one count the navigator, the status line and the booth share. */
counts: { open: number };
};
@ -64,34 +64,32 @@ export const buildDesk = (
if (!list) return { published: false, open: [], recorded: [], counts: { open: 0 } };
const open: DeskRow[] = [];
const recorded: Desk['recorded'] = [];
[...list.forks]
.sort((a, b) => a.card.order - b.card.order)
.forEach((entry) => {
const { card, record } = entry;
if (card.status === 'closed') {
if (card.ruling !== null)
recorded.push({ forkId: card.fork_id, title: card.title, rulingId: card.ruling });
return;
}
if (unverified.has(card.fork_id)) {
open.push({ kind: 'unverified', forkId: card.fork_id });
return;
}
open.push({
kind: 'question',
forkId: card.fork_id,
title: card.title,
context: card.context,
options: card.options.map((o) => ({ ...o, recommended: o.id === card.recommendation })),
recommended: card.options.find((o) => o.id === card.recommendation)?.text,
depends: card.depends,
answers: record.answers.map((a) => answerRow(a, entry)),
});
list.forks.forEach((entry) => {
const { card, record } = entry;
if (card.status === 'closed') {
if (card.ruling !== null)
recorded.push({ forkId: card.fork_id, title: card.title, rulingId: card.ruling });
return;
}
if (unverified.has(card.fork_id)) {
open.push({ kind: 'unverified', forkId: card.fork_id });
return;
}
open.push({
kind: 'question',
forkId: card.fork_id,
title: card.title,
context: card.context,
options: card.options.map((o) => ({ ...o, recommended: o.id === card.recommendation })),
recommended: card.options.find((o) => o.id === card.recommendation)?.text,
depends: card.depends,
answers: record.answers.map((a) => answerRow(a, entry)),
});
});
return {
published: true,
open,
recorded,
counts: { open: open.filter((r) => r.kind === 'question').length },
counts: { open: list.open_count },
};
};

View file

@ -84,7 +84,7 @@ const list = (mind: string, forks: ForkEntry[], stamp: string): ForkList => ({
stamp,
open_count: forks.filter((f) => f.card.status === 'open').length,
recorded_count: forks.filter((f) => f.card.status === 'closed').length,
forks,
forks: [...forks].sort((a, b) => b.card.order - a.card.order),
next_cursor: null,
});