fix(qa): resolve QA-pass issues #122–#134 #135

Merged
vvzvlad merged 2 commits from fix/qa-issues-122-134 into develop 2026-06-22 21:07:19 +03:00

Fixes the issues found in the automated QA pass (#122–#134). Branched off the latest develop. Every fix was reproduced, then verified fixed live (browser/curl, screenshots captured); the logic-bearing fixes also have new unit tests.

Functional bugs

  • Closes #122 — collab-token throttled by the anonymous public-share-AI limiter (5/min) → editor sync broke after a few page opens. Skips all non-AUTH named throttlers on this auth-guarded, client-cached route. (verified: route now skips all 4 buckets)
  • Closes #123 — uncaught jwtDecode(undefined) on collab auth failure + no reconnect → now reads the token via a ref, guards the decode (incl. missing/non-numeric exp), and refetch+reconnects on any failure. (verified: forced 429 → 0 PAGEERROR)
  • Closes #124 — slash command with a space (/Heading 1) inserted literal text → allowSpaces: true + the menu closes when nothing matches. (verified live)
  • Closes #125 — space slug auto-gen gave uppercase initials for multi-word names → lowercase alphanumeric slug. (verified: "Product Team" → productteam; +unit tests)
  • Closes #126 — AI chat window geometry not persisted → persisted via atomWithStorage, clamped to viewport (also fixes a latent observer-attach bug). (verified: survives reload)
  • Closes #127 — workspace name update accepted URLs → @NoUrls (parity with setup). (verified: now 400; +DTO tests)
  • Closes #132 — console errors on editor load: SVG calc() width/height (icon-columns 4/5) and share-for-page query returning undefined → fixed. (verified: 0 errors; +test)
  • Closes #134 — "Reindex now" counter looked stuck (reindex is async; UI read once) → client now polls coverage (bounded) so the counter climbs live. (verified: counter climbed 72→84 without reload)

UX / consistency

  • Closes #128 — added success toasts (favorite/label/avatar/member de-/activate).
  • Closes #129 — "1 result found" pluralization; hid the single-option Type filter. (the "duplicate no-results" report was retracted — it's the intentional aria-live announcement.)
  • Closes #130 — friendly validation messages instead of raw Zod strings (name/password/group).
  • Closes #131 — unified Untitled casing (tree/breadcrumb/tab), stopped force-uppercasing space-name chips, fixed confirm-dialog labels (Cancel / Remove), invite placeholder typo, Export/Move-to-space labels.
  • Closes #133 — profile Save disabled when clean; toast on unsupported avatar image; styled invalid-invitation page with CTA; Share hidden for read-only users; dictation "not configured" wording; "Go to login page" typo.

Tests

New unit tests: computeSpaceSlug, workspace-name NoUrls DTO validation, share-query null normalization, slash getSuggestionItems empty-close. Client tsc/vitest and server tsc/jest for the touched areas all pass.

🤖 Generated with Claude Code

Fixes the issues found in the automated QA pass (#122–#134). Branched off the latest `develop`. Every fix was **reproduced, then verified fixed live** (browser/curl, screenshots captured); the logic-bearing fixes also have new unit tests. ### Functional bugs - Closes #122 — collab-token throttled by the anonymous public-share-AI limiter (5/min) → editor sync broke after a few page opens. Skips all non-AUTH named throttlers on this auth-guarded, client-cached route. *(verified: route now skips all 4 buckets)* - Closes #123 — uncaught `jwtDecode(undefined)` on collab auth failure + no reconnect → now reads the token via a ref, guards the decode (incl. missing/non-numeric `exp`), and refetch+reconnects on any failure. *(verified: forced 429 → 0 PAGEERROR)* - Closes #124 — slash command with a space (`/Heading 1`) inserted literal text → `allowSpaces: true` + the menu closes when nothing matches. *(verified live)* - Closes #125 — space slug auto-gen gave uppercase initials for multi-word names → lowercase alphanumeric slug. *(verified: "Product Team" → `productteam`; +unit tests)* - Closes #126 — AI chat window geometry not persisted → persisted via `atomWithStorage`, clamped to viewport (also fixes a latent observer-attach bug). *(verified: survives reload)* - Closes #127 — workspace name update accepted URLs → `@NoUrls` (parity with setup). *(verified: now 400; +DTO tests)* - Closes #132 — console errors on editor load: SVG `calc()` width/height (icon-columns 4/5) and `share-for-page` query returning `undefined` → fixed. *(verified: 0 errors; +test)* - Closes #134 — "Reindex now" counter looked stuck (reindex is async; UI read once) → client now polls coverage (bounded) so the counter climbs live. *(verified: counter climbed 72→84 without reload)* ### UX / consistency - Closes #128 — added success toasts (favorite/label/avatar/member de-/activate). - Closes #129 — "1 result found" pluralization; hid the single-option Type filter. *(the "duplicate no-results" report was retracted — it's the intentional aria-live announcement.)* - Closes #130 — friendly validation messages instead of raw Zod strings (name/password/group). - Closes #131 — unified `Untitled` casing (tree/breadcrumb/tab), stopped force-uppercasing space-name chips, fixed confirm-dialog labels (Cancel / Remove), invite placeholder typo, Export/Move-to-space labels. - Closes #133 — profile Save disabled when clean; toast on unsupported avatar image; styled invalid-invitation page with CTA; Share hidden for read-only users; dictation "not configured" wording; "Go to login page" typo. ### Tests New unit tests: `computeSpaceSlug`, workspace-name `NoUrls` DTO validation, `share-query` null normalization, slash `getSuggestionItems` empty-close. Client `tsc`/`vitest` and server `tsc`/`jest` for the touched areas all pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-06-22 20:48:21 +03:00
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>
Owner

Ревью PR #135

Прочитал весь дифф (45 файлов, +657/−124) и проверил самые рискованные места против реальных файлов ветки.

Вердикт

PR качественный и аккуратный: правки точечные, с понятными англоязычными комментариями, тесты осмысленные. Критичных проблем не нашёл. Несколько мелких замечаний ниже, ни одно не блокирует мердж.

Что проверил отдельно (всё ок)

  • #122 (throttler) — в throttle.module.ts ровно 4 именованных throttler-а (auth, ai-chat, page-template, public-share-ai), безымянного дефолтного нет. Фикс в auth.controller.ts пропускает все 4 → collab-token больше не ловит ложные 429 от IP-лимитера public-share-AI (5/мин). Исправление полное и корректное.
  • #123 (collab auth)page-editor.tsx: свежий токен берётся через ref, jwtDecode обёрнут в try/catch, отсутствующий/нечисловой exp трактуется как «истёк» → реконнект сработает даже если первичный фетч токена упал. Хорошая обработка краёв.
  • #124 (slash-меню)getSuggestionItems действительно импортирован в slash-command.ts; allowSpaces: true + allow, закрывающий меню при отсутствии совпадений; есть тест на контракт.
  • #126 (геометрия AI-окна) — восстановленная из localStorage геометрия клампится к вьюпорту через useLayoutEffect+clampGeom на каждом открытии, окно не уедет за экран; правка зависимости geom !== null у ResizeObserver — реальный тонкий баг, поймали верно.
  • #132 (SVG calc)style={{ width: rem(size) }} соответствует конвенции остальных иконок.
  • Импорты AuthLayout/Container в invite-sign-up-form.tsx и валидатор no-urls.validator на месте.

Замечания (некритичные)

1. i18n: две новые строки не добавлены в локали — low.
Ключи "Go to login page" (password-reset.tsx, invite-форма) и "Move to space" (page-header-menu.tsx, space-tree-node-menu.tsx) отсутствуют в en-US/translation.json, хотя PR добавил туда десяток других новых строк. В английском отрисуются через fallback-на-ключ (корректно, кодовая база уже так делает — старый "Goto login page" тоже не был ключом), но в остальных 11 локалях останутся непереведёнными. Для консистентности стоит добавить оба ключа в en-US.

2. avatar-uploader: список типов захардкожен — low / возможная регрессия.
В avatar-uploader.tsx допускаются только image/png|jpeg|jpg. Раньше клиентской проверки типа не было; если сервер принимал webp/gif, теперь они отклонятся на клиенте. "image/jpg" — нестандартный MIME (безвреден). Стоит сверить набор с тем, что реально поддерживает сервер, либо расширить (минимум webp).

3. reindex-поллинг: два разных условия «готово» — low.
В ai-provider-settings.tsx колбэк refetchInterval останавливается при indexedPages >= totalPages без проверки totalPages > 0, а очищающий useEffect требует totalPages > 0. Для пустого воркспейса (totalPages=0) поллинг не стартует (ок), но reindexDeadline сбросится только по таймауту, а не по «готово». Безвредно, но условия лучше выровнять.

4. #133: Share полностью скрыт для read-only — дизайн-заметка, не баг.
Раньше читатель видел модалку Share (с неактивным тоглом), теперь точка входа убрана совсем → читатель больше не может открыть диалог, чтобы скопировать уже существующую публичную ссылку. Соответствует тексту issue, но это поведенческое изменение — отметьте, если «дать читателю копировать существующую ссылку» было нужно.

5. Мелочи — very low.
В change-password.tsx потеряно отдельное сообщение «current password is required» (теперь оба поля показывают «Password must be at least 8 characters»). Схемы валидации (account-name / create-group / change-password) пересоздаются на каждый рендер, т.к. переехали внутрь компонента ради t() — функционально ок, можно обернуть в useMemo.

Итог

Мерджить можно. Перед мерджем имеет смысл закрыть только п.1 (добавить 2 ключа в en-US) и проверить п.2 (webp в аватарах) — остальное на усмотрение. Тесты в PR (computeSpaceSlug property-тест, NoUrls DTO, нормализация share-query к null, закрытие slash-меню) релевантны и покрывают именно логику фиксов.

## Ревью PR #135 Прочитал весь дифф (45 файлов, +657/−124) и проверил самые рискованные места против реальных файлов ветки. ### Вердикт PR качественный и аккуратный: правки точечные, с понятными англоязычными комментариями, тесты осмысленные. Критичных проблем не нашёл. Несколько мелких замечаний ниже, ни одно не блокирует мердж. ### Что проверил отдельно (всё ок) - **#122 (throttler)** — в `throttle.module.ts` ровно 4 именованных throttler-а (`auth`, `ai-chat`, `page-template`, `public-share-ai`), безымянного дефолтного нет. Фикс в `auth.controller.ts` пропускает все 4 → `collab-token` больше не ловит ложные 429 от IP-лимитера public-share-AI (5/мин). Исправление **полное и корректное**. - **#123 (collab auth)** — `page-editor.tsx`: свежий токен берётся через `ref`, `jwtDecode` обёрнут в try/catch, отсутствующий/нечисловой `exp` трактуется как «истёк» → реконнект сработает даже если первичный фетч токена упал. Хорошая обработка краёв. - **#124 (slash-меню)** — `getSuggestionItems` действительно импортирован в `slash-command.ts`; `allowSpaces: true` + `allow`, закрывающий меню при отсутствии совпадений; есть тест на контракт. - **#126 (геометрия AI-окна)** — восстановленная из localStorage геометрия клампится к вьюпорту через `useLayoutEffect`+`clampGeom` на каждом открытии, окно не уедет за экран; правка зависимости `geom !== null` у `ResizeObserver` — реальный тонкий баг, поймали верно. - **#132 (SVG calc)** — `style={{ width: rem(size) }}` соответствует конвенции остальных иконок. - Импорты `AuthLayout`/`Container` в `invite-sign-up-form.tsx` и валидатор `no-urls.validator` на месте. ### Замечания (некритичные) **1. i18n: две новые строки не добавлены в локали — low.** Ключи `"Go to login page"` (`password-reset.tsx`, invite-форма) и `"Move to space"` (`page-header-menu.tsx`, `space-tree-node-menu.tsx`) отсутствуют в `en-US/translation.json`, хотя PR добавил туда десяток других новых строк. В английском отрисуются через fallback-на-ключ (корректно, кодовая база уже так делает — старый `"Goto login page"` тоже не был ключом), но в остальных 11 локалях останутся непереведёнными. Для консистентности стоит добавить оба ключа в en-US. **2. avatar-uploader: список типов захардкожен — low / возможная регрессия.** В `avatar-uploader.tsx` допускаются только `image/png|jpeg|jpg`. Раньше клиентской проверки типа не было; если сервер принимал `webp`/`gif`, теперь они отклонятся на клиенте. `"image/jpg"` — нестандартный MIME (безвреден). Стоит сверить набор с тем, что реально поддерживает сервер, либо расширить (минимум webp). **3. reindex-поллинг: два разных условия «готово» — low.** В `ai-provider-settings.tsx` колбэк `refetchInterval` останавливается при `indexedPages >= totalPages` без проверки `totalPages > 0`, а очищающий `useEffect` требует `totalPages > 0`. Для пустого воркспейса (totalPages=0) поллинг не стартует (ок), но `reindexDeadline` сбросится только по таймауту, а не по «готово». Безвредно, но условия лучше выровнять. **4. #133: Share полностью скрыт для read-only — дизайн-заметка, не баг.** Раньше читатель видел модалку Share (с неактивным тоглом), теперь точка входа убрана совсем → читатель больше не может открыть диалог, чтобы скопировать уже существующую публичную ссылку. Соответствует тексту issue, но это поведенческое изменение — отметьте, если «дать читателю копировать существующую ссылку» было нужно. **5. Мелочи — very low.** В `change-password.tsx` потеряно отдельное сообщение «current password is required» (теперь оба поля показывают «Password must be at least 8 characters»). Схемы валидации (account-name / create-group / change-password) пересоздаются на каждый рендер, т.к. переехали внутрь компонента ради `t()` — функционально ок, можно обернуть в `useMemo`. ### Итог Мерджить можно. Перед мерджем имеет смысл закрыть только п.1 (добавить 2 ключа в en-US) и проверить п.2 (webp в аватарах) — остальное на усмотрение. Тесты в PR (`computeSpaceSlug` property-тест, `NoUrls` DTO, нормализация `share`-query к `null`, закрытие slash-меню) релевантны и покрывают именно логику фиксов.
Ghost added 1 commit 2026-06-22 21:05:56 +03:00
- Add the two new strings to en-US locale ('Go to login page', 'Move to
  space') so they aren't missing from the base locale (review note 1).
- Avatar upload: accept any image/* MIME instead of a hardcoded png/jpeg/jpg
  list, so webp/gif/etc. are no longer wrongly rejected client-side while
  genuine non-images still surface the error (review note 2).
- Reindex polling: align the deadline-clearing effect with the refetchInterval
  stop condition (indexed >= total, empty workspace included) so the deadline
  clears promptly instead of waiting out the cap (review note 3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vvzvlad merged commit 86bb2742c7 into develop 2026-06-22 21:07:19 +03:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#135