a86e5f409f
The dictation mic could be grey/disabled while silently showing "Start
dictation", and Mantine's native `disabled` set pointer-events:none so the
Tooltip never fired at all — the UI knew the cause but told the user nothing.
Runtime error strings were also duplicated verbatim across the two dictation
hooks.
- New dictation-status.ts: the single source of truth. A DictationUnavailableReason
enum (connecting/offline/read-only/unsupported/busy) + a DictationErrorCode enum,
pure classifiers (classifyGetUserMediaError / classifyTranscriptionError) and
resolvers (resolveUnavailableLabel / dictationErrorMessage). All user-facing
dictation strings are formed here; the verbatim server message still wins for
transcription errors.
- page-editor publishes dictationAvailabilityAtom { isEditable, reason } computed
at the source (editable/edit-mode/showStatic/collab status): connecting vs
offline (stuck) vs read-only. DictationGroup forwards the reason to MicButton.
- MicButton is reason-aware: a disabled mic shows the cause-specific tooltip. The
disabled-hover silence is fixed by marking disabled the Mantine way
(data-disabled/aria-disabled + click guard) instead of the native attribute, so
the Tooltip fires — applied to both the idle (reason) and error (errorMessage)
states.
- Both hooks route every error through the shared resolver (deleting the
duplicated transcriptionErrorMessage), and expose errorMessage for the tooltip.
Wording is byte-identical to each hook's original (incl. the batch hook's
DOMException name prefix and the verbatim server message).
- i18n: 3 new reason keys in en-US + ru-RU, and the previously-missing ru-RU
dictation error translations.
Tests: dictation-status.test.ts (all classifier/resolver branches, incl. server
message passthrough) + mic-button.test.tsx (disabled mic shows the reason text,
uses data-disabled not native disabled — fails against the pre-fix code).
vitest: 5 files / 32 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
31 lines
1.1 KiB
TypeScript
31 lines
1.1 KiB
TypeScript
import { atom } from "jotai";
|
|
import { Editor } from "@tiptap/core";
|
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
|
import type { DictationUnavailableReason } from "@/features/dictation/dictation-status";
|
|
|
|
export const pageEditorAtom = atom<Editor | null>(null);
|
|
|
|
export const titleEditorAtom = atom<Editor | null>(null);
|
|
|
|
export const readOnlyEditorAtom = atom<Editor | null>(null);
|
|
|
|
export const yjsConnectionStatusAtom = atom<string>("");
|
|
|
|
export const showLinkMenuAtom = atom(false);
|
|
|
|
// Current page's edit mode — initialized from the user's saved preference on
|
|
// first load, can be toggled locally without persisting to the server.
|
|
export const currentPageEditModeAtom = atom<PageEditMode>(PageEditMode.Edit);
|
|
|
|
// Whether the dictation mic can start, and (when it can't) the cause-specific
|
|
// reason the mic button surfaces as a tooltip. Published by the page editor,
|
|
// consumed by DictationGroup -> MicButton.
|
|
export type DictationAvailability = {
|
|
isEditable: boolean;
|
|
reason: DictationUnavailableReason | null;
|
|
};
|
|
export const dictationAvailabilityAtom = atom<DictationAvailability>({
|
|
isEditable: false,
|
|
reason: null,
|
|
});
|