mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 17:41:09 +02:00
fix: silence two cosmetic boot-time devtools warnings
1. /api/auth/organization/get-active-member 400
The Better-Auth org plugin returns 400 ("active organization not
found") whenever the session has no activeOrganizationId yet — i.e.
on every fresh inkognito login. The fetch was already tolerated
(fetchActiveMember returns null on 400), but the network panel
logged it as a noisy red row.
Fix: gate the call on the localStorage hint. The hint is set by
writeActiveSpaceHint() after every successful set-active, so its
presence is a reliable proxy for "session has activeOrganizationId
set". Without a hint we go straight to list + auto-activate
Personal — same effective outcome, no 400.
2. Chrome "Autofocus processing was blocked" on /onboarding/name and
/onboarding/wish
The static `autofocus` attribute races the previous route's focus
owner across the SvelteKit transition. Chrome refuses to honour
autofocus when a document already has a focused element and warns.
Fix: replace the attribute with `bind:this={el}` + a $effect that
imperatively `el.focus()`s after `tick()` — by then the outgoing
page has unmounted and there's no competing focus claim. The
svelte-ignore directives are no longer needed and have been removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
537719032e
commit
eaa1d7432b
3 changed files with 55 additions and 20 deletions
|
|
@ -186,13 +186,22 @@ export function writeActiveSpaceHint(id: string | null): void {
|
|||
|
||||
/**
|
||||
* Resolve the user's active space. Order of truth:
|
||||
* 1. Server-side active member (get-active-member) — trustworthy when
|
||||
* Better Auth's activeOrganizationId landed in the session.
|
||||
* 2. Client-side hint from localStorage — survives cookie drops across
|
||||
* page reloads in dev. If present, we call set-active to re-sync
|
||||
* the server to match.
|
||||
* 1. **Localstorage hint present** — we've already gone through the
|
||||
* list+set-active flow on this device, so `get-active-member` is
|
||||
* certain to return 200 (Better Auth's session has an
|
||||
* activeOrganizationId). Call it as the source of truth (reflects
|
||||
* cross-tab switches the user may have made elsewhere).
|
||||
* 2. **No hint** — fresh session. `get-active-member` would return
|
||||
* 400 ("active organization not found") because nothing has been
|
||||
* set yet. Skip the call entirely so the network panel stays clean,
|
||||
* and go straight to list + activate-Personal-or-localStorage-hint.
|
||||
* 3. Personal space fallback — brand-new session, no previous choice.
|
||||
*
|
||||
* The skip-on-fresh-session optimisation eliminates the noisy 400 the
|
||||
* webapp used to log on every fresh inkognito login. Functionally
|
||||
* equivalent — `fetchActiveMember()` returned `null` on 400 anyway and
|
||||
* we fell through to the same auto-activate path.
|
||||
*
|
||||
* Errors captured in `lastError`; status flips to 'error'. Callers can
|
||||
* retry via `loadActiveSpace({ force: true })`.
|
||||
*/
|
||||
|
|
@ -203,20 +212,25 @@ export async function loadActiveSpace(opts: { force?: boolean } = {}): Promise<A
|
|||
lastError = null;
|
||||
|
||||
try {
|
||||
const member = await fetchActiveMember();
|
||||
if (member) {
|
||||
writeActiveSpaceHint(member.id);
|
||||
applyActiveSpace(member);
|
||||
return member;
|
||||
// Only call `get-active-member` when we have a localStorage hint —
|
||||
// the hint is set by writeActiveSpaceHint() after every successful
|
||||
// set-active, so its presence is a reliable proxy for "the server's
|
||||
// session has activeOrganizationId set". Without a hint, the server
|
||||
// would 400 and we'd fall through to the activation path anyway.
|
||||
const hintId = readActiveSpaceHint();
|
||||
if (hintId) {
|
||||
const member = await fetchActiveMember();
|
||||
if (member) {
|
||||
writeActiveSpaceHint(member.id);
|
||||
applyActiveSpace(member);
|
||||
return member;
|
||||
}
|
||||
// Hint present but server returned no active org (cookie wipe,
|
||||
// session reset, …) — fall through to list + auto-activate so
|
||||
// the hint is honoured if it still points at a real space.
|
||||
}
|
||||
|
||||
// Server says no active org. Two reasons we might still know one:
|
||||
// (a) the user switched to a non-personal space earlier and the
|
||||
// hint survived in localStorage even though the cookie didn't.
|
||||
// (b) it's truly a fresh session, in which case we activate
|
||||
// Personal.
|
||||
const orgs = await fetchOrganizations();
|
||||
const hintId = readActiveSpaceHint();
|
||||
const hinted = hintId ? orgs.find((o) => o.id === hintId) : null;
|
||||
|
||||
const chosen = hinted ?? orgs.find((o) => o.type === 'personal') ?? null;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
-->
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { tick } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { onboardingFlow } from '$lib/stores/onboarding-flow.svelte';
|
||||
|
|
@ -26,9 +27,18 @@
|
|||
let name = $state((onboardingFlow.pendingName ?? authStore.user?.name ?? '').trim());
|
||||
let saving = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let inputEl = $state<HTMLInputElement | null>(null);
|
||||
|
||||
let canSubmit = $derived(name.trim().length >= 1 && name.trim().length <= 40 && !saving);
|
||||
|
||||
// Imperative focus after the previous route's focus owner has fully
|
||||
// unmounted. Using `<input autofocus>` would race the router and
|
||||
// trigger Chrome's "Autofocus processing was blocked because a
|
||||
// document already has a focused element" warning.
|
||||
$effect(() => {
|
||||
void tick().then(() => inputEl?.focus());
|
||||
});
|
||||
|
||||
async function saveName(value: string) {
|
||||
const token = await authStore.getValidToken();
|
||||
if (!token) throw new Error('Not authenticated');
|
||||
|
|
@ -75,15 +85,14 @@
|
|||
</div>
|
||||
|
||||
<div class="field">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
bind:this={inputEl}
|
||||
bind:value={name}
|
||||
onkeydown={handleKeydown}
|
||||
placeholder="z. B. Till"
|
||||
maxlength={40}
|
||||
autocomplete="given-name"
|
||||
autofocus
|
||||
aria-label="Dein Name"
|
||||
/>
|
||||
{#if error}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
-->
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { tick } from 'svelte';
|
||||
import { ArrowLeft, Check, Globe, Lock } from '@mana/shared-icons';
|
||||
import { onboardingFlow } from '$lib/stores/onboarding-flow.svelte';
|
||||
import { onboardingStatus } from '$lib/stores/onboarding-status.svelte';
|
||||
|
|
@ -24,10 +25,22 @@
|
|||
let isPublic = $state(true);
|
||||
let saving = $state(false);
|
||||
let submittedDisplayName = $state<string | null>(null);
|
||||
let textareaEl = $state<HTMLTextAreaElement | null>(null);
|
||||
|
||||
let trimmed = $derived(wish.trim());
|
||||
let charsLeft = $derived(MAX_LEN - wish.length);
|
||||
|
||||
// Imperative focus after the previous onboarding screen has fully
|
||||
// unmounted. Using the static `autofocus` attribute would race the
|
||||
// outgoing route's focus owner (Chrome warns "Autofocus processing
|
||||
// was blocked because a document already has a focused element"
|
||||
// when a router transition leaves a button focused). `tick()` waits
|
||||
// for the next microtask so the textarea is mounted and has no
|
||||
// competing focus claim.
|
||||
$effect(() => {
|
||||
void tick().then(() => textareaEl?.focus());
|
||||
});
|
||||
|
||||
async function handleFinish() {
|
||||
if (saving) return;
|
||||
saving = true;
|
||||
|
|
@ -78,13 +91,12 @@
|
|||
</div>
|
||||
|
||||
<div class="field">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<textarea
|
||||
bind:this={textareaEl}
|
||||
bind:value={wish}
|
||||
maxlength={MAX_LEN}
|
||||
placeholder="Ich möchte Mana nutzen, um …"
|
||||
rows="6"
|
||||
autofocus
|
||||
aria-label="Was du dir von Mana wünschst"
|
||||
></textarea>
|
||||
<div class="counter" class:warn={charsLeft < 100}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue