The DesktopSidebar previously had no entry point for /collective/manage,
making member-management and the new common-items / tags subsections
unreachable from the chrome on desktop. The MobileDrawer already linked
it from inside the collective switcher block; desktop didn't.
- Adds a `<div aria-hidden>` divider with `my-2 border-t` to separate
the section nav (lists / tasks / notes / search) from the manage
entry.
- Adds a `Users`-iconed link to `/collective/manage` as the last nav
item, with the same active-state styling as the other entries.
- Not gated by `$enabledSections` (membership management is orthogonal
to feature toggles — same call as the Admin entry in the footer).
- Reuses the existing `m.manage_title()` Paraglide string; no new
strings.
Tests: tests/e2e/sidebar-manage-link.test.ts adds SBM-01 (link visible
and navigates) + SBM-02 (divider present in <nav>).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds an OfflineChip surfaced in the MobileTopBar (compact, icon-only)
and at the bottom of the DesktopSidebar (full pill with label). Hidden
while online. When `navigator.onLine` is false:
- The chip mounts with the existing amber palette
- A title tooltip explains the implication ("changes will sync when
you reconnect")
- If `pendingOpsCount > 0`, a small numeric badge is appended so the
user sees the actual backlog size at a glance
`pendingOpsCount` is the existing store (kept in sync by SyncQueue);
nothing new wires up — we only surface what was already tracked.
OF-01 e2e: drives `context.setOffline(true)`, asserts the chip
appears, queues an INSERT, asserts the numeric badge renders, and
verifies the chip disappears when back online. Added alongside the
existing O-01/O-02 (banner-based) so we keep coverage of both surfaces.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
13.3 admin UI:
- (admin) route group with own layout + red banner + sidebar (Collectives,
Admins, Audit log, Server). +layout.ts is client-rendered (ssr=false),
same rationale as the (app) group.
- /admin/collectives — paginated list via admin_list_collectives, search
by name, soft-delete / restore / hard-delete modals (hard-delete needs
explicit checkbox; force toggle bypasses the 30-day wait server-side).
- /admin/collectives/[id] — members list with kick action; recent actions
for the collective filtered from admin_actions.
- /admin/admins — list via server_admins join to users (disambiguated by
FK name — there are TWO FKs to users so the embed needs the explicit
server_admins_user_id_fkey); promote modal does an email lookup; revoke
button is replaced by "you (cannot revoke yourself while sole admin)"
when applicable.
- /admin/audit — paginated feed (default 50), action filter.
- /admin/server — server-layer default-section toggles backed by
admin_set_default_section + the new admin_clear_default_section RPC
(added because patching the JSONB to `true` is NOT equivalent to "no
opinion" — `true` explicitly overrides a collective OFF).
13.4 sidebar/drawer entry:
- $isServerAdmin store (writable; +$isServerAdminLoaded for the gate
race) refreshed on every onAuthStateChange in root layout, cleared on
sign-out. Cached so the sidebar tile is synchronous.
- DesktopSidebar + MobileDrawer render the entry only when the store is
true. Distinct red icon so it never blends with normal nav.
Stores wiring:
- features.ts gains serverSectionDefaults writable + enabledSections
derived now reads server > collective > user > default. Loader
piggybacks on loadCurrentUserFeatures so the server layer is fetched
once per sign-in.
- serverAdmin.ts new module — refreshServerAdminFlag() + clearServerAdminFlag().
- Tracks isServerAdminLoaded so the (admin) layout doesn't redirect away
during the millisecond between SIGNED_IN and the RPC resolving (caught
empirically — every hard-load to /admin/* bounced to / without it).
13.5 tests:
- admin.test.ts (SA-01..SA-04) with new loginAsAdmin fixture. Ana is the
seed admin (server_admins seeded via supabase/seed.sql).
- SA-04 + SV-02 reset their server-layer JSONB via the new
admin_clear_default_section RPC so the suites don't leak state into
each other.
Migration 025 adds admin_clear_default_section(text) — same SECURITY DEFINER
+ audit pattern as the rest. pgTAP 017 updated (23 plans, AR-T13b + the
SECURITY DEFINER list now includes the clear RPC).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UI side:
* DesktopSidebar / BottomTabBar filter their entries by $enabledSections,
tag each entry with data-section, and BottomTabBar exposes a
data-section-count attribute so the grid is observable from tests.
* "lists" is force-shown unconditionally — the §12.3.4 always-one-
landing rule covers the edge case where every layer says OFF.
* (app)/+layout.svelte adds a $effect that, when the URL matches a
disabled section (/tasks, /notes, /search), goto's /lists and shows
a transient `section_disabled_for_collective` toast.
* Root +layout.svelte exposes the supabase singleton on window.__sb
in dev so the new Playwright spec can patch rows without a parallel
client; dead-code-eliminated in production builds.
DB side:
* Migration 023 grows by ALTER PUBLICATION supabase_realtime ADD TABLE
public.users + public.collectives + REPLICA IDENTITY FULL on both.
Without this membership the features.ts subscriptions get zero
events and SV-02 (collective toggle → realtime → member's nav
updates) silently fails. Caught while running the spec.
* pgTAP 015 grows from 16 to 20 assertions to cover publication
membership + REPLICA IDENTITY for both new realtime tables.
Paraglide messages: section_disabled_for_collective, the visibility
section titles + blurbs, section_label_* per SectionKey. Both en + es.
Playwright tests/e2e/section-visibility.test.ts (SV-01..SV-03):
SV-01 user toggle → nav reflects → /tasks redirects to /lists
SV-02 admin collective toggle → member sees it disappear in realtime
SV-03 collective ON beats user OFF — per-collective resolution
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a new "+ New collective" entry at the bottom of the sidebar
collective-switcher dropdown (and the mobile drawer for parity).
Clicking it opens a CreateCollectiveModal that reuses the same
onboarding form — name + emoji grid — and calls the existing
create_collective() RPC (migration 012). On success, the new
collective is appended to $userCollectives, set active in
$currentCollective, persisted to localStorage, and the user is sent
to /lists.
Side effects:
- Switcher now opens even with a single collective (previously gated
on >1) so the entry is always reachable; the chevron is always
shown.
- DesktopSidebar gets a data-testid on the switcher button so the
Playwright suite can target it without text matching.
O-04 (rescued from Fase 7): Eva creates her first collective via
onboarding, then opens the switcher and creates a second one without
leaving /lists; both collectives end up in the switcher and the DB.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>