fix(webui): sp11 item 8, with no organizations #/dashboard is the Dashboard: it says there is nothing to count and offers New organization, which opens the Organizations form at its ID field; #/ stays the Console's own board (#1873, lane sp11-fixes)

This commit is contained in:
jason.woltje committed 2026-11-15 10:27:28 -06:00
1 parent 11d2b43b3a
commit 8b4a26dd2b
3 files changed
+21 -7

No files matched your search

+4 -2
View File
@@ -1691,7 +1691,9 @@
if (parts.includes(null)) return notFound();
const [a, b, c] = parts, n = parts.length;
// #1873 SP1: `#/dashboard` is the system dashboard, every organization on one page.
if ((n === 0 || (a === 'dashboard' && n === 1)) && window.MosaicOrg) return window.MosaicWorkspace.home();
// SP11: with no organization it is still the Dashboard (it says so and offers New organization);
// #/ is then the Console's own board.
if ((n === 0 && window.MosaicOrg) || (a === 'dashboard' && n === 1 && window.MosaicWorkspace)) return window.MosaicWorkspace.home();
if (a === 'projects' && n === 1 && window.MosaicOrg) return window.MosaicWorkspace.projectsPage(q);
if (a === 'organization' && n === 1 && window.MosaicOrg) return window.MosaicWorkspace.dashboard();
// SP3: #/inbox is every organization's items; #/orgs/<id>/inbox is that list for one, with its hire form.
@@ -1739,7 +1741,7 @@
const paused = () => $('pause')?.getAttribute('aria-pressed') === 'true';
// A file open in the editor is never refreshed from under its draft; Reload is the person's to press.
// The cross-organization pages (U3) poll at the workspace read's pollMs.
const wsPage = path => !!window.MosaicOrg && (path === '/' || path === '' || path === '/dashboard' || path === '/projects' || path === '/inbox' || path === '/agents' || path === '/tasks' || /^\/workflows(\/[^/]+)?$/.test(path));
const wsPage = path => (path === '/dashboard' && !!window.MosaicWorkspace) || !!window.MosaicOrg && (path === '/' || path === '' || path === '/dashboard' || path === '/projects' || path === '/inbox' || path === '/agents' || path === '/tasks' || /^\/workflows(\/[^/]+)?$/.test(path));
function schedule() { clearTimeout(timer); if (!paused() && !out() && !routeOf().startsWith('/docs/')) timer = setTimeout(() => render(false), wsPage(routeOf().split('?')[0]) ? window.MosaicWorkspace?.pollMs || 10000 : 10000); }
// Keep focus, open <details> and the copy status across a refresh of the same page.
// In the send form, the focused field and its caret too (its values come from `draft`).
+1 -1
View File
@@ -710,7 +710,7 @@
const why = r.state === 'ok' || !(reason || r.boot?.at) ? '' : `<span class="why">${esc(reason)}${r.boot?.at ? `${reason ? ' · ' : ''}last start ${esc(when(r.boot.at))}` : ''}</span>`;
return `<tr class="ws-o${r.state === 'ok' ? '' : ' off'}" style="${o.style(id)}" data-ws-orgrow="${esc(id)}" data-state="${esc(r.state)}"><td class="who"><a href="${esc(orgHref(id))}/organization" data-focus-key="${esc(`orgrow:${id}`)}">${av(o.meta(id))}<span class="nm">${esc(o.name(id))}</span></a>${dot(cls, glyph, word, true)}${why}</td><td>${agentsCell}</td><td class="hide-m">${projectsCell}</td><td>${needsCell}</td></tr>`;
}).join('');
const orgList = o.rows.length ? `<div class="ws-tbl"><table class="ws-orgtbl" data-ws-orgs><thead><tr><th scope="col">Organization</th><th scope="col">Agents</th><th scope="col" class="hide-m">Projects</th><th scope="col">Needs you</th></tr></thead><tbody>${orgTable}</tbody></table></div>` : '<p class="ws-panel tight note" data-ws-orgs>No organizations yet</p>';
const orgList = o.rows.length ? `<div class="ws-tbl"><table class="ws-orgtbl" data-ws-orgs><thead><tr><th scope="col">Organization</th><th scope="col">Agents</th><th scope="col" class="hide-m">Projects</th><th scope="col">Needs you</th></tr></thead><tbody>${orgTable}</tbody></table></div>` : '<p class="ws-panel tight note" data-ws-orgs>No organizations yet. <a class="ws-btn" href="#/settings/organizations?new=1" data-focus-key="home-org-new">New organization</a></p>';
const recent = projectRows.filter(p => p.lastActivity).slice(0, 6);
const STATE_WORD = { working: 'has a session working', waiting: 'is waiting on you', idle: 'has an idle session', none: 'no live session' };
@@ -170,10 +170,22 @@ test('When no answering organization could read its agents, the Agents tile says
assert.equal(await b.evaluate('document.querySelector("[data-ws-tile=agents]").getAttribute("href")'), '#/agents?state=working');
});
test('With no organizations there is nothing to count, so no tile shows a zero', async t => {
// With no organization the workspace pages are not drawn; the Console's own board keeps its dashes.
const { b, wait } = await setup(t, { none: true });
await wait('document.querySelector("#dashboard-kpis .kpi strong")');
// SP11 (#1873): with no organizations, #/dashboard is the Dashboard, not Not found. It says there is
// nothing to count and offers New organization, which opens the Organizations form at its ID field.
// #/ stays the Console's own board, with its dashes.
test('With no organizations the Dashboard says so, shows no tile, and offers New organization', async t => {
const { b, wait, text } = await setup(t, { none: true });
await wait('document.querySelector("[data-ws-home] h1")');
assert.equal(await text('#bus-view h1'), 'Dashboard');
assert.doesNotMatch(await text('#bus-view'), /Not found/);
assert.equal(await b.evaluate('document.querySelectorAll(".ws-tile, .ws-big").length'), 0);
assert.match(await text('[data-ws-home] [data-ws-tiles]'), /^No organizations yet, so there is nothing to count\./);
assert.equal(await b.evaluate('document.querySelector("[data-ws-home] [data-ws-orgs] a")?.getAttribute("href") ?? null'), '#/settings/organizations?new=1');
assert.equal(await text('[data-ws-home] [data-ws-orgs] a'), 'New organization');
await b.evaluate('document.querySelector("[data-ws-home] [data-ws-orgs] a").click()');
await wait('document.activeElement?.id === "org-new-id"');
await b.evaluate('location.hash = "#/"');
await wait('document.querySelector("#dashboard-kpis .kpi strong") && !document.querySelector("[data-ws-home]")');
assert.equal(await b.evaluate('document.querySelectorAll(".ws-tile, .ws-big").length'), 0);
assert.deepEqual(await b.evaluate('[...new Set([...document.querySelectorAll("#dashboard-kpis .kpi strong")].map(e => e.textContent.trim()))]'), ['–']);
});