Fase 16.2 + 16.3 — replace the bare `m.loading()` text at every
pre-existing async-loading site with `<Spinner size=...> + <span>`.
Text is kept (visual fallback + tooltip / SR redundancy). No new
loading semantics — only existing flags get a visual indicator.
Sites:
- (app)/+layout.svelte: auth splash gets `size="lg"` centred under the
loading label.
- (app)/search/+page.svelte: inline `size="sm"` next to the search-in-flight
copy.
- (app)/notes/archive/+page.svelte: inline `size="sm"` next to the
archive-load copy.
- lib/components/CreateCollectiveModal.svelte: inline `size="sm"` inside
the disabled Save button while the create RPC is in flight.
- (app)/collective/manage/+page.svelte: three sites — members-list load,
invitation-generate button, and add-common-item Save button.
E2E (tests/e2e/spinner.test.ts):
- SP-E-01 throttles search_in_collective RPC by 500ms and asserts the
spinner is visible in the loading paragraph.
- SP-E-02 throttles set_item_frequency_weight RPC and asserts the spinner
is scoped inside the Save button.
- SP-E-03 seeds an expired `sb-192-auth-token` so GoTrue is forced to
call /auth/v1/token, then hangs that endpoint indefinitely and
asserts the splash spinner is visible. Storage key derivation matches
Supabase's `sb-${hostname.split('.')[0]}-auth-token` default; we
populate both the LAN-IP variant and a loopback fallback for
resilience.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the client mirror of section_enabled():
* SectionKey / FeatureFlags types + SECTION_KEYS constant in
@colectivo/types so the SQL and TS sides share the same vocabulary.
* apps/web/src/lib/stores/features.ts:
- currentUserFeatures writable, loaded by root layout
- enabledSections derived map { section → boolean }
- enabledSectionList ordered SectionKey[] visible to UI;
forces 'lists' if every layer says OFF
- setUserFeature per-user toggle (optimistic + rollback)
- setCollectiveFeature admin toggle (rolls back on RLS deny)
- realtime subscriptions one channel for the user's row, one
for the active collective; rebuilt
whenever those targets change
Wired into the root layout: loadCurrentUserFeatures runs from inside
the existing setTimeout-deferred onAuthStateChange callback (the
documented gotcha), and currentCollective.subscribe drives
subscribeCollectiveFeatures so collective switches re-target the
realtime filter without an extra auth event.
loadUserCollectives + every other place that fabricates a Collective
object now carries feature_flags (defaulting to {} for freshly created
collectives) so the Collective interface stays sound.
Integration spec (6 tests, SV-01..SV-06) covers default-ON, user-only,
collective-beats-user, RLS writes (user_update_own + collectives_update),
and per-collective isolation for a user in two collectives.
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>