Files
gitmost/apps/client/src/features/ai-chat/atoms/ai-chat-atom.ts
claude code agent 227 9e1d057878 fix(qa): resolve QA-pass issues #122–#134
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>
2026-06-22 20:47:40 +03:00

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>("");