fix(webui): preserve room refusal and recover session controls (#1843)
This commit is contained in:
1 parent
5558fe0204
commit
a1ba9cd566
5 files changed
+53
-1
No files matched your search
@@ -6147,3 +6147,6 @@ Lead-only live window checks, not executed here: (1) explicitly authorize and se
|
||||
|
||||
2026-11-04 | Codex implementation sub-agent | #815 / Gitea #1843 Task 2, before (recorded during implementation) | Started in T3-prepared branch t3/channel-disabled-chat-recovery at controller base 3572bf9b32f414283686e828155ae859a45c537e, source baseline 51525a31d. Scope: explicit Case 1 CLI/WebUI opt-in and Console channel-refusal/failed-read recovery; preserve global defaults, enforcement and no-tracker starter. Controller owns coordination and live repair; no live config, role, queue or transport changes.
|
||||
2026-11-04 | Codex implementation sub-agent | #815 / Gitea #1843 Task 2, after | Case 1 explicitly opts into cli and webui; no-tracker starter and global defaults remain unchanged. Console renders a fixed disabled-channel explanation, retains in-memory drafts and distinguishes definite refusal from unknown outcome, disables known-refused creation/send actions, marks cached reads stale and does not claim empty on failed reads. TDD red logs and green gates under /mnt/storage/scratch/tmp/mosaic-815-logs/: final targeted chat/room HTTP and Chromium 100/100, full WebUI with MOSAIC_REQUIRE_EDITOR_BUILD=1 574/574, business 307/307, zero skips/cancellations. First full WebUI after root npm ci: 569 passed, 1 missing WebUI esbuild failure, 1 editor-build skip; WebUI lockfile install restored the pinned dependency. Concurrent run: 571/572 with one unrelated decide-browser I1 wait timeout; isolated decide-browser 9/9, subsequent full gate green. Superseded full gates were stopped when later regression corrections changed the source. No bus.mjs/proof pin, role, policy, auth, live host/config, queue, push or deployment change. Draft recovery tested through the Console Refresh action; full browser reload does not persist in-memory drafts.
|
||||
2026-11-04 | Codex implementation sub-agent | #815 / Gitea #1843 source fix round 1, stopped | At base 5558fe020 reproduced room channel state loss and sticky legacy-session controls in real Chromium, then made the scoped state corrections; focused red/green logs and the 47/47 covering-browser pass are under /mnt/storage/scratch/tmp/mosaic-815-r1/. The single authorized full WebUI run with MOSAIC_REQUIRE_EDITOR_BUILD=1 and concurrency 2 finished 575/576, zero skips/cancellations: unrelated thread-claims-browser row278 timed out waiting for its table; that entire file then passed 6/6 alone. The unchanged vendored-editor import warning remained, and the editor rebuild passed. No commit: the full applicable suite is not green, and the brief specified one full run. Controller owns the decision on further verification. Minor cached-room stale-label finding remains unchanged. No live, bus/proof, authentication, policy, role, schema, tracker, push, merge or PR change.
|
||||
|
||||
2026-11-04 | #815 / Gitea #1843 source fix round 1 controller verification | Existing authorization covered another verification run after the unchanged row278 browser timeout. Same command and test settings: MOSAIC_REQUIRE_EDITOR_BUILD=1 node --test --test-concurrency=2 packages/webui/tests/; 576 passed, zero failures/cancellations/skips, required editor rebuild passed, duration 168128 ms. Source/test blob hashes unchanged from pre-run snapshot; no test weakening or unrelated source changes. Both earlier full failure and isolated 6/6 evidence retained. Log: /mnt/storage/scratch/tmp/mosaic-815-r1/full-webui-controller-retry.log. Local commit now permitted; independent I1/I2 re-review next.
|
||||
@@ -2030,3 +2030,5 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-11-04 | GPT-6 Luna implementation sub-agent, t3/lifecycle-task-7b-r2 | Gitea #1839 lifecycle Task 7b rebase | Stopped before branch creation: packages/webui/src/bus.mjs differs between requested base 34e9af616 and source 1904c9775 (blob hashes 2d19a538b809a808a53307f049363e62e854ea96 vs 593d12b1582a4a3a285e0b89b32d9ee6e3ab726d); pinned-hash guard requires reporting. No cherry-pick, tests, or commit.
|
||||
2026-11-04 | Lead Codex GPT-6.1 Sol, provider recovery | #815 / Gitea #1843 live MVP 3 /chat failure | Recovered through .53 Window D at 51525a31d/schema 3s; canonical fast-forwarded from b5a58b48b, prior Luna uncommitted append preserved. Diagnosed absent plugins.channel and channel-disabled before project dispatch; typed tester plugins read succeeded. Repair limited to validated channel setting and source error/template regressions; no owner acceptance or secret access.
|
||||
2026-11-04 | Codex implementation sub-agent, t3/channel-disabled-chat-recovery | #815 / Gitea #1843 Task 2 source fix | Explicit cli/webui Case 1 opt-in and fixed disabled-channel/error/empty-state UI, draft retention and safe retry distinction; targeted chat/room 100, full WebUI 574 and business 307 passed with zero skips. Earlier dependency and concurrent browser timeout results and logs recorded in BUILD-LOG; Console Refresh preserves in-memory drafts, full browser reload does not. Local commit only; controller owns independent review and integration.
|
||||
2026-11-04 | Codex implementation sub-agent, t3/channel-disabled-chat-recovery | #815 / Gitea #1843 source fix round 1 | Both browser regressions red then green; covering 47/47. Single full WebUI run 575/576, zero skips, unrelated thread-claims browser timeout; that file passed 6/6 isolated. No local commit because applicable full suite did not pass and brief specified one full run. Logs /mnt/storage/scratch/tmp/mosaic-815-r1/; controller to decide further verification. No live or policy changes.
|
||||
2026-11-04 | Lead Codex GPT-6.1 Sol, controller verification | #815 / Gitea #1843 fix round 1 | Repeated unchanged full affected WebUI gate in completed implementer checkout: 576/576, zero skips/cancellations, required editor rebuild passed; candidate source/test hashes unchanged. Preserved prior 575/576 and isolated 6/6 results; local commit and independent re-review next. No live change or acceptance claim.
|
||||
@@ -282,7 +282,7 @@
|
||||
const signal = controller.signal, g = ++generation;
|
||||
const match = /^\/chat\/rooms\/([0-9a-f-]+)$/i.exec(path), id = match?.[1];
|
||||
current = { kind: 'room', id, key: 'room:' + id, path, container, canSend, user, tester,
|
||||
projects: [], threads: [], members: [], roomReady: false, refused: false, error: '' };
|
||||
projects: [], threads: [], members: [], roomReady: false, channelDisabled: current?.channelDisabled ?? false, refused: false, error: '' };
|
||||
if (!UUID.test(id ?? '') || !roomOwner()) {
|
||||
current.error = UUID.test(id ?? '') ? 'Rooms require the business owner.' : 'Invalid room route.';
|
||||
current.refused = true; paint(navigated); return;
|
||||
@@ -302,6 +302,7 @@
|
||||
const page = await request(`rooms/${id}/messages?limit=100${after ? '&after=' + after : ''}`, undefined, signal);
|
||||
if (g !== generation || out()) return;
|
||||
if (typeof page.hasMore !== 'boolean' || !(page.after === null || UUID.test(page.after)) || page.rows.some(m => !UUID.test(m?.id) || !Array.isArray(m.ids) || !m.ids.every(id => UUID.test(id)) || !Array.isArray(m.to))) throw new ChatError('invalid-response');
|
||||
current.channelDisabled = false;
|
||||
s.rows = [...new Map((after ? [...s.rows, ...page.rows] : page.rows).map(m => [m.id, m])).values()].slice(-200);
|
||||
s.after = page.after ?? s.after; s.more = page.hasMore; s.loaded = true;
|
||||
current.roomReady = current.members.length > 0;
|
||||
@@ -506,6 +507,7 @@
|
||||
}
|
||||
if (s) {
|
||||
if (typeof page.hasMore !== 'boolean' || !(page.after === null || UUID.test(page.after))) throw new ChatError('invalid-response');
|
||||
if (catalog) current.legacyDefault = false;
|
||||
const rows = after ? [...s.rows, ...page.rows] : page.rows;
|
||||
s.rows = [...new Map(rows.map(m => [m.id, m])).values()].slice(-200);
|
||||
s.after = page.after ?? s.after; s.more = page.hasMore; s.loaded = true;
|
||||
|
||||
@@ -191,6 +191,27 @@ test('chat browser: a refused share explains the disabled channel without exposi
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-share-target").value'), 'user:reader');
|
||||
assert.equal(await b.evaluate('window.__writes.length'), 1);
|
||||
});
|
||||
test('chat browser: session catalog recovery restores controls and scopes drafts and messages', async t => {
|
||||
const { b, wait, set, click, route, refresh } = await setup(t);
|
||||
await b.evaluate('window.__failurePath="/sessions";window.__readFailure="access-refused";true');
|
||||
await route(A);
|
||||
assert.match(await b.evaluate('document.querySelector(".chat-area").textContent'), /sessions.*not.*available/i);
|
||||
await set('#chat-body', 'legacy draft');
|
||||
await b.evaluate(`window.__readFailure=null;window.__sessions['${A}']=[{id:'${B}',name:'default',default:true,state:'active',revision:2}];window.__pages.push({rows:[{id:'${M}',body:'session history',from_role:'assistant',at:'2026-10-18T00:00:00Z'}],after:'${M}',hasMore:false});true`);
|
||||
await refresh();
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-session")?.value'), B);
|
||||
assert.equal(await b.evaluate('!!document.querySelector("#chat-session-form") && !!document.querySelector("#chat-clear")'), true);
|
||||
assert.match(await b.evaluate('document.querySelector(".chat-log").textContent'), /session history/);
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-body").value'), '', 'legacy draft is not copied into another session');
|
||||
await set('#chat-body', 'session draft');
|
||||
await b.evaluate(`window.__answers.push({body:{id:'${P}'}}),true`);
|
||||
await click('#chat-send-form button');
|
||||
await wait('document.querySelector("#chat-receipt").textContent.includes("Accepted")');
|
||||
assert.deepEqual(await b.evaluate('window.__writes[0].body'), { body: 'session draft', session: B });
|
||||
await b.evaluate('window.__readFailure="access-refused";true');
|
||||
await refresh();
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-body").value'), 'legacy draft');
|
||||
});
|
||||
test('chat browser: list/create named projects and threads, hostile labels inert and no implicit writes', async t => {
|
||||
const { b, wait, set, click } = await setup(t);
|
||||
assert.equal(await b.evaluate('window.__writes.length'), 0);
|
||||
|
||||
@@ -40,6 +40,7 @@ const stub = `(() => {
|
||||
if(path==='/api/library/revert'){window.__writes.push({path,body:JSON.parse(init.body)});return answer({project:'books',path:'Notes/One.md',reverted:'${SHA}',commit:'${LAST}'});}
|
||||
if(!path.startsWith('/api/chat/'))return real(url,init);
|
||||
if(method==='GET')window.__reads.push(path);
|
||||
if(method==='GET' && window.__chatOffline)throw new TypeError('offline');
|
||||
if(path==='/api/chat/projects')return answer({rows:window.__projects});
|
||||
if(path==='/api/chat/instances')return answer({rows:window.__instances});
|
||||
if(path.includes('/rooms/')){
|
||||
@@ -84,6 +85,29 @@ test('disabled room read shows unavailable history and disables opening', async
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-room-open").disabled'), true);
|
||||
assert.equal(await b.evaluate('window.__writes.length'), 0);
|
||||
});
|
||||
test('room keeps a known disabled channel through a failed refresh and recovers on a successful read', async t => {
|
||||
const { b, wait, text } = await setup(t);
|
||||
const refresh = async () => {
|
||||
await wait('!document.querySelector("#refresh").disabled');
|
||||
await b.evaluate('document.querySelector(".chat-area").dataset.beforeRefresh="yes";document.querySelector("#refresh").click();true');
|
||||
await wait('!document.querySelector(".chat-area")?.dataset.beforeRefresh');
|
||||
};
|
||||
assert.match(await text('.chat-room-log'), /Broadcast once/);
|
||||
await b.evaluate('window.__readError="channel-disabled";true');
|
||||
await refresh();
|
||||
assert.match(await text('.chat-error'), /channel.*disabled/i);
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-room-open").disabled'), true);
|
||||
await b.evaluate('window.__readError=null;window.__chatOffline=true;true');
|
||||
await refresh();
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-room-open").disabled'), true);
|
||||
await b.evaluate('document.querySelector("#chat-room-open").dispatchEvent(new MouseEvent("click",{bubbles:true}));true');
|
||||
assert.equal(await b.evaluate('window.__writes.length'), 0, 'the write guard also refuses a synthetic click');
|
||||
assert.match(await text('.chat-room-log'), /Broadcast once/);
|
||||
await b.evaluate('window.__chatOffline=false;true');
|
||||
await refresh();
|
||||
assert.equal(await b.evaluate('document.querySelector("#chat-room-open").disabled'), false);
|
||||
assert.equal(await b.evaluate('window.__writes.length'), 0, 'recovery reads never open a room');
|
||||
});
|
||||
test('C2-10c room merges members, shows a broadcast once, reuses cited renderer and suppresses checked notice forgery', async t => {
|
||||
const { b, wait, text } = await setup(t);
|
||||
await wait('document.querySelector(".chat-cites")');
|
||||
|
||||
Reference in new issue
Block a user