Batch of fixes from the automated QA pass on develop. Each was reproduced and then verified fixed live (browser/curl); logic-bearing fixes have unit tests. Functional bugs: - #122 collab-token was capped by the anonymous public-share-AI throttler (5/min); skip all non-AUTH named throttlers on this auth-guarded, client-cached route. - #123 editor onAuthenticationFailed threw `jwtDecode(undefined)` and never reconnected; read the token via a ref, guard the decode (incl. missing exp), and refetch+reconnect on any auth failure. - #124 a slash command containing a space ("/Heading 1") inserted literal text; enable allowSpaces and close the menu when the query matches no items. - #125 space slug auto-gen produced uppercase initials for multi-word names; computeSpaceSlug now yields a lowercase alphanumeric slug. - #126 AI chat window position/size now persisted (atomWithStorage) across reload; also fixes a latent ResizeObserver-attach bug on first open. - #127 workspace name update accepted URLs; add @NoUrls (parity with setup). - #132 icon-columns 4/5 passed calc() into SVG width/height attrs (console spam); size via style. share-for-page query returns null instead of undefined. - #134 "Reindex now" counter looked stuck: reindex runs async; the client now polls coverage (bounded) so the counter climbs live; misleading server comment reworded. UX / consistency: - #128 add success toasts to favorite/label/avatar/member-(de)activate. - #129 "1 result found" pluralization; hide the single-option Type filter. - #130 replace raw Zod strings with friendly messages (name/password/group). - #131 unify "Untitled" casing in tree/breadcrumb/tab; stop force-uppercasing space-name chips; fix confirm-dialog labels (Cancel / Remove), invite placeholder typo, Export/Move-to-space labels. - #133 disable profile Save when clean; toast on unsupported avatar image; style the invalid-invitation page with a CTA; hide Share for read-only users; align the dictation "not configured" message; "Go to login page" typo. Tests: computeSpaceSlug, workspace-name NoUrls DTO, share-query null normalization, slash getSuggestionItems empty-close. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { atom } from "jotai";
|
|
import { atomWithStorage } from "jotai/utils";
|
|
|
|
/**
|
|
* Persisted floating AI chat window geometry (position + size). Held in
|
|
* localStorage so a drag/resize survives a full page reload. `null` means
|
|
* "never placed yet" — the window then computes an initial top-right placement.
|
|
* On restore the value is clamped to the current viewport (see AiChatWindow).
|
|
*/
|
|
export type AiChatWindowGeom = {
|
|
left: number;
|
|
top: number;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
export const aiChatWindowGeomAtom = atomWithStorage<AiChatWindowGeom | null>(
|
|
"ai-chat-window-geom",
|
|
null,
|
|
);
|
|
|
|
/**
|
|
* The currently selected chat id. `null` means a fresh (not-yet-created) chat:
|
|
* the server creates the chat row on the first streamed message and echoes its
|
|
* id, which the panel then adopts.
|
|
*/
|
|
// Note: declare via a cast default rather than `atom<string | null>(null)`,
|
|
// which mis-resolves the jotai useAtom overload to the read-only signature
|
|
// under this TS/jotai version (the setter would type as `never`).
|
|
export const activeAiChatIdAtom = atom(null as string | null);
|
|
|
|
// Whether the floating AI chat window is open. Non-persistent (resets per session).
|
|
export const aiChatWindowOpenAtom = atom<boolean>(false);
|
|
|
|
/**
|
|
* The agent role selected for the NEXT new chat. `null` = "Universal assistant"
|
|
* (no role). Consulted ONLY when creating a chat (its first message): the server
|
|
* persists it to ai_chats.role_id and the role is immutable afterwards. Reset to
|
|
* null when starting a new chat. It does NOT affect already-created chats.
|
|
*/
|
|
// Cast default for the same jotai overload reason as activeAiChatIdAtom above.
|
|
export const selectedAiRoleIdAtom = atom(null as string | null);
|
|
|
|
// The AI chat composer draft (text typed but not yet sent). Held here — OUTSIDE
|
|
// ChatThread — so it survives the thread remount that happens when a brand-new
|
|
// chat adopts its freshly created id after the first turn finishes. If it lived
|
|
// in ChatInput's local state, that remount would wipe text the user typed while
|
|
// the agent was still streaming. Reset on deliberate chat switches.
|
|
export const aiChatDraftAtom = atom<string>("");
|