fix(settings): Conventions and Playbooks start unchecked when a member is restricted (TASK-3384) (#1759)

Dave's ruling on TASK-3376's picker default: switching a member from All
to Specific collections no longer pre-checks the system collections. They
start unchecked, like every other collection. The hint under the list now
names the consequence: unchecked, they stay hidden from that member, and
their agents won't load the workspace's conventions or playbooks.

Migration 108's backfill is unchanged: members restricted before TASK-3376
keep exactly the access they had. A member already restricted keeps their
saved list.
This commit is contained in:
xarmian
2026-10-03 10:17:27 -04:00
committed by GitHub
parent 98871c7e01
commit cd6dfdb318
2 changed files with 22 additions and 23 deletions
@@ -703,17 +703,6 @@
}
}
// TASK-3376: system collections are ordinary choices for a restricted
// member. Moving a member from All to Specific starts with them checked,
// so restricting someone does not silently stop their agents loading the
// workspace's rules. A member already restricted keeps their saved list.
function setAccessMode(next: 'all' | 'specific') {
if (next === 'specific' && accessMode !== 'specific' && accessCollectionIds.length === 0) {
accessCollectionIds = systemCollections.map((c) => c.id);
}
accessMode = next;
}
function toggleAccessCollection(collId: string) {
if (accessCollectionIds.includes(collId)) {
accessCollectionIds = accessCollectionIds.filter(id => id !== collId);
@@ -1053,7 +1042,7 @@
id="access-mode-{member.user_id}"
class="role-select"
value={accessMode}
onchange={(e) => { setAccessMode((e.target as HTMLSelectElement).value as 'all' | 'specific'); }}
onchange={(e) => { accessMode = (e.target as HTMLSelectElement).value as 'all' | 'specific'; }}
>
<option value="all">All collections</option>
<option value="specific">Specific collections</option>
@@ -1086,7 +1075,8 @@
{/each}
{#if systemCollections.length > 0}
<p class="access-coll-hint">
{systemCollections.map((c) => c.name).join(' and ')} hold the rules and procedures agents load. Unchecked, this member's agents won't receive them.
<!-- TASK-3376/3384: system collections are ordinary choices for a restricted member and start unchecked, like every other collection. -->
Unchecked, {systemCollections.map((c) => c.name).join(' and ')} stay hidden from this member, and their agents won't load the workspace's conventions or playbooks.
</p>
{/if}
</div>
@@ -6,10 +6,12 @@ import SettingsPage from './+page.svelte';
/**
* TASK-3376: system collections (Conventions, Playbooks) are ordinary
* collections for a restricted member. In the member access picker they are
* real checkboxes, not a disabled always-checked row, and they start CHECKED
* when an owner switches a member from All to Specific, so that restricting
* someone does not silently stop their agents loading the workspace's rules.
* A member who is ALREADY restricted keeps their saved list as is.
* real checkboxes, not a disabled always-checked row. They start UNCHECKED
* when an owner switches a member from All to Specific (TASK-3384, Dave's
* ruling), like every other collection, and a hint names what leaving them
* unchecked means: that member's agents won't load the workspace's
* conventions or playbooks. A member who is ALREADY restricted keeps their
* saved list as is.
*/
const meCalls: Array<(value: unknown) => void> = [];
@@ -112,21 +114,28 @@ describe('TASK-3376: system collections in the member access picker', () => {
window.location.hash = '';
});
it('All to Specific starts with the system collections checked, and they can be unchecked', async () => {
it('All to Specific starts with nothing checked, the system collections included, and the hint names the consequence', async () => {
await openAccessPanel();
setAccessMode('specific');
await waitFor(() => expect(checkbox('Conventions').checked).toBe(true));
expect(checkbox('Playbooks').checked).toBe(true);
await waitFor(() => expect(checkbox('Conventions')).toBeTruthy());
expect(checkbox('Conventions').checked).toBe(false);
expect(checkbox('Playbooks').checked).toBe(false);
expect(checkbox('Tasks').checked).toBe(false);
// Real choices, not the old disabled always-checked row.
expect(checkbox('Conventions').disabled).toBe(false);
expect(checkbox('Playbooks').disabled).toBe(false);
const hint = document.querySelector('.access-coll-hint');
expect(hint?.textContent).toMatch(/agents won.t load .*conventions.*playbooks/i);
checkbox('Conventions').click();
await waitFor(() => expect(checkbox('Conventions').checked).toBe(false));
// Checking one is an ordinary choice, saved like any collection.
checkbox('Playbooks').click();
await waitFor(() => expect(checkbox('Playbooks').checked).toBe(true));
checkbox('Tasks').click();
await waitFor(() => expect(checkbox('Tasks').checked).toBe(true));
screen.getByRole('button', { name: /^Save$/ }).click();
await waitFor(() => expect(saves.length).toBe(1));
expect(saves[0]).toEqual({ mode: 'specific', ids: ['c-pb'] });
expect([...saves[0].ids].sort()).toEqual(['c-pb', 'c-tasks']);
expect(saves[0].mode).toBe('specific');
});
it('an already-restricted member keeps their saved list; nothing is added for them', async () => {