Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions ERRORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Monkey Test Report

Date: 2025-12-29

## Summary

Tested all pages and interactive elements across the application.

## Issues Found and Fixed

### ~~High Priority~~ ✅ FIXED

#### ~~1. "untitled page" text displayed on all pages~~

- **Location**: Bottom of every page (svelte-announcer accessibility element)
- **Root cause**: No page titles set, causing SvelteKit's screen reader announcer to say "untitled page"
- **Fix**: Added `<svelte:head><title>...</title></svelte:head>` to all pages

### ~~Medium Priority~~ ✅ FIXED

#### ~~2. "Loading messages..." persists in empty channels~~

- **Location**: `apps/desktop/src/components/chat/MessageList.svelte`
- **Root cause**: Condition checked `messagesData.length > 0` instead of checking loading state
- **Fix**: Added proper `isLoading` check from useQuery hook

#### ~~3. Add Member uses browser prompt() dialog~~

- **Location**: `apps/desktop/src/routes/orgs/[orgId]/settings/`
- **Root cause**: Used native `prompt()` and `confirm()` for user input
- **Fix**: Created proper DaisyUI modal in MemberList.svelte

### ~~Low Priority~~ ✅ FIXED

#### ~~4. DM search shows no results indication~~

- **Location**: `apps/desktop/src/components/dms/UserSearch.svelte`
- **Root cause**: No else condition for empty results
- **Fix**: Added "No users found" message when search completes with no results

## Features Tested (All Working)

- [x] Organization selection page
- [x] Organization switching
- [x] Channel navigation (general, test-channel)
- [x] Channel creation modal
- [x] Message input and sending
- [x] Message reactions (add/remove)
- [x] Message menu (Reply, Add Reaction, Show Reactions, Pin, Edit, Delete)
- [x] Message pinning
- [x] Search messages
- [x] Poll creation form
- [x] Emoji picker (all tabs, search, favorites)
- [x] User profile/personalization settings (name change, profile picture)
- [x] Organization settings (edit name/description)
- [x] Organization member list
- [x] New organization creation page
- [x] Signin redirect (when already logged in)

## Files Modified

1. `apps/desktop/src/routes/+page.svelte` - Added page title
2. `apps/desktop/src/routes/signin/+page.svelte` - Added page title
3. `apps/desktop/src/routes/signout/+page.svelte` - Added page title
4. `apps/desktop/src/routes/orgs/new/+page.svelte` - Added page title
5. `apps/desktop/src/routes/orgs/[orgId]/+page.svelte` - Added page title
6. `apps/desktop/src/routes/orgs/[orgId]/settings/+page.svelte` - Added page title, updated onAddMember callback
7. `apps/desktop/src/routes/orgs/[orgId]/personalization/+page.svelte` - Added page title
8. `apps/desktop/src/routes/orgs/[orgId]/chat/[channelId]/+page.svelte` - Added page title
9. `apps/desktop/src/components/chat/MessageList.svelte` - Fixed loading state logic
10. `apps/desktop/src/routes/orgs/[orgId]/settings/MemberList.svelte` - Added modal for Add Member
11. `apps/desktop/src/routes/orgs/[orgId]/settings/member-utils.ts` - Refactored to throw errors instead of using alerts
12. `apps/desktop/src/routes/orgs/[orgId]/settings/settings-controller.svelte.ts` - Updated to accept email parameter
13. `apps/desktop/src/components/dms/UserSearch.svelte` - Added "No users found" message

## Test Environment

- Browser: Chrome (via DevTools MCP)
- URL: http://localhost:5173
- User: Dev User (dev@example.com) - Admin role
46 changes: 46 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# TODOS

Generated: 2025-12-24

## Batch 1: Critical Security Fixes

- [ ] Fix SQL injection in message search (`apps/server/src/domains/messages/service.ts` - escape `ilike` pattern)
- [ ] Add file path sanitization (`apps/server/src/domains/files/service.ts` - prevent `../` traversal)
- [ ] Add WebSocket auth re-validation on subscribe (`apps/server/src/ws/index.ts` - check permissions on subscribe)
- [ ] Add rate limiting middleware (`apps/server/src/middleware/` - create rate-limit.ts)

## Batch 2: High-Impact Performance Fixes

- [ ] Fix N+1 query in message list (`apps/server/src/domains/messages/service.ts` - include reactions/attachments in query)
- [ ] Remove duplicate useQuery per message (`apps/desktop/src/components/MessageItem.svelte` - pass reactions as props)
- [ ] Deduplicate permission checks (`apps/server/src/domains/permissions/service.ts` - cache within request context)
- [ ] Lazy load CodeMirror and emoji-picker (`apps/desktop/src/components/` - use dynamic imports)

## Batch 3: Code Quality & Dead Code Removal

- [ ] Delete unused example/ directory (`.storybook`, `apps/desktop/src/example/`)
- [ ] Remove console.log/error statements (9 files - use proper logger or delete)
- [ ] Split large components: `MessageItem.svelte` (143 lines → ~50 lines each)
- [ ] Split large components: `ChannelList.svelte` (141 lines → ~50 lines each)

## Batch 4: Accessibility Fixes

- [ ] Add aria-labels to icon buttons (`apps/desktop/src/components/` - all IconButton components)
- [ ] Replace alert() with toast notifications (`apps/desktop/src/` - create toast utility)
- [ ] Add form labels for WCAG compliance (`apps/desktop/src/routes/` - all form inputs)
- [ ] Standardize placeholder text language (`apps/desktop/src/` - choose Japanese or English consistently)

## Deferred (Needs User Confirmation)

- [ ] DISABLE_AUTH flag - clarify production usage policy (`apps/server/src/middleware/auth.ts`)
- [ ] Add CSRF protection - confirm if needed for API-only backend (`apps/server/src/middleware/`)
- [ ] Add security headers - confirm headers policy (`apps/server/src/index.ts`)
- [ ] shadow-2xl usage - confirm if Clarity design principles apply (`apps/desktop/src/components/`)
- [ ] Empty state CTAs - requires design decisions (multiple files)

## Rejected (Low Value / Out of Scope)

- Duplicate logic in unread.ts - minimal impact, refactor during feature work
- Test coverage improvements - separate test-focused sprint needed
- Flaky waitForTimeout(500) - address when writing new E2E tests
- Page Object Model - requires significant test refactoring
4 changes: 2 additions & 2 deletions apps/desktop/src/components/app/ChatApp.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
{:else}
<div class="flex flex-1 items-center justify-center">
<div class="text-center">
<p class="text-muted text-lg">チャンネルを選択</p>
<p class="text-muted text-lg">Select a Channel</p>
<p class="text-muted mt-1 text-sm opacity-60">
左のサイドバーからチャンネルを選んでください
Choose a channel from the sidebar
</p>
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/components/app/OrganizationSidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@
<aside class="border-subtle bg-base-200 flex h-full w-72 flex-col border-r">
<!-- Organization header -->
<header
class="border-subtle flex items-center justify-between border-b px-4 py-3"
class="border-subtle flex items-center justify-between border-b px-4 py-4"
>
<div class="min-w-0 flex-1">
<h1 class="truncate font-semibold tracking-tight">
{organization?.name ?? "Loading..."}
</h1>
</div>
<div class="flex items-center gap-1">
<div class="flex items-center gap-2">
<a
href="/orgs/{organizationId}/settings"
class="btn btn-ghost btn-sm btn-square hover-highlight"
title="組織設定"
title="Organization Settings"
>
<Settings class="text-muted size-4" />
</a>
<a
href="/"
class="btn btn-ghost btn-sm btn-square hover-highlight"
title="組織を切り替え"
title="Switch Organization"
>
<ArrowLeftRight class="text-muted size-4" />
</a>
Expand Down
23 changes: 14 additions & 9 deletions apps/desktop/src/components/channels/Channel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@
() => selectedChannelId,
() => organizationId,
);

function autofocus(node: HTMLElement) {
node.focus();
}
</script>

<div class="flex h-full flex-col">
<!-- Channel header -->
<header
class="border-subtle flex items-center justify-between border-b px-6 py-4"
>
<div class="flex items-center gap-3">
<Hash class="size-5 opacity-50" />
<div class="flex items-center gap-4">
<Hash class="size-5 opacity-60" />
<h1 class="text-base font-semibold">
{controller.selectedChannel.data?.name ?? "..."}
</h1>
{#if controller.selectedChannel.data?.description}
<span class="hidden text-sm opacity-40 sm:inline">
<span class="hidden text-sm opacity-60 sm:inline">
— {controller.selectedChannel.data.description}
</span>
{/if}
Expand All @@ -44,10 +48,11 @@
>
<input
type="text"
placeholder="メッセージを検索..."
class="input input-sm input-bordered bg-base-200 w-56 pr-8 text-sm transition-all duration-150 focus:w-64"
placeholder="Search messages..."
class="input input-sm input-bordered bg-base-200 w-56 pr-8 text-sm transition-all duration-200 focus:w-64"
bind:value={controller.searchQuery}
onkeydown={(e) => e.key === "Enter" && controller.handleSearch()}
use:autofocus
/>
{#if controller.isSearching}
<span
Expand All @@ -57,20 +62,20 @@
</div>
{/if}
<button
class="btn btn-ghost btn-sm btn-square transition-all duration-150"
title="検索"
class="btn btn-ghost btn-sm btn-square transition-all duration-200"
title="Search"
onclick={() => (controller.showSearch = !controller.showSearch)}
>
<Search
class="size-5 opacity-50 transition-opacity duration-150 hover:opacity-80"
class="size-5 opacity-60 transition-opacity duration-200 hover:opacity-80"
/>
</button>
</div>
</header>

<!-- Search results -->
{#if controller.searchResults.length > 0}
<div class="border-subtle bg-base-200/50 border-b p-2">
<div class="border-subtle bg-base-200/50 border-b p-4">
<SearchResults
results={controller.searchResults}
onResultClick={controller.handleResultClick}
Expand Down
20 changes: 10 additions & 10 deletions apps/desktop/src/components/channels/ChannelList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<div class="flex h-full flex-col">
<!-- Channels section -->
<section class="flex-1 overflow-y-auto">
<header class="flex items-center justify-between px-3 py-2">
<header class="flex items-center justify-between px-4 py-2">
<span class="text-muted text-xs font-medium tracking-wider uppercase">
Channels
</span>
Expand All @@ -59,7 +59,7 @@
<a
href={`/orgs/${organizationId}/chat/${channel.id}`}
class={[
"group flex items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors",
"group flex items-center gap-2 rounded px-2 py-2 text-sm transition-colors duration-200",
active
? "bg-primary/15 text-primary"
: "text-base-content/80 hover:bg-base-300 hover:text-base-content",
Expand All @@ -84,28 +84,28 @@
</a>
{/each}
{:else}
<div class="text-muted px-2 py-4 text-center text-sm">
読み込み中...
<div class="text-muted px-2 py-4 text-center text-sm opacity-60">
Loading...
</div>
{/if}

{#if channels.data?.length === 0}
<div class="text-muted px-2 py-4 text-center text-sm">
チャンネルがありません
<div class="text-muted px-2 py-4 text-center text-sm opacity-60">
No channels
</div>
{/if}
</nav>
</section>

<!-- DM section -->
<section class="border-subtle border-t">
<header class="flex items-center justify-between px-3 py-2">
<header class="flex items-center justify-between px-4 py-2">
<span class="text-muted text-xs font-medium tracking-wider uppercase">
Direct Messages
</span>
<button
class="btn btn-ghost btn-xs btn-square"
title="新しいDM"
title="New DM"
onclick={() => (showUserSearch = !showUserSearch)}
>
<Plus class="text-muted size-4" />
Expand All @@ -132,10 +132,10 @@
<footer class="border-subtle border-t p-2">
<a
href={`/orgs/${organizationId}/personalization`}
class="text-muted hover:bg-base-300 hover:text-base-content flex items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors"
class="text-muted hover:bg-base-300 hover:text-base-content flex items-center gap-2 rounded px-2 py-2 text-sm transition-colors duration-200"
>
<User class="size-4" />
<span>個人設定</span>
<span>Settings</span>
</a>
</footer>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

<button
class="btn btn-ghost btn-xs btn-square"
title="新しいチャンネル"
title="New channel"
onclick={() => {
modalManager.dispatch(createChannelModalContent);
}}
Expand All @@ -58,17 +58,17 @@
>
<input
type="text"
placeholder="チャンネル名"
placeholder="Channel name"
class="input input-bordered"
bind:value={newChannelName}
/>
{#if disabled}
<button type="submit" class="btn btn-primary btn-sm" disabled>
作成中...
Creating...
<span class="loading loading-spinner"></span>
</button>
{:else}
<button type="submit" class="btn btn-primary btn-sm">作成</button>
<button type="submit" class="btn btn-primary btn-sm">Create</button>
{/if}
</form>
{/snippet}
8 changes: 7 additions & 1 deletion apps/desktop/src/components/chat/EmojiButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@
Button to toggle the emoji palette.
Displays a smiling face emoji icon.
-->
<button class="btn btn-secondary self-end" {onclick}>😀</button>
<button
class="btn btn-ghost btn-sm self-end"
{onclick}
aria-label="Open emoji picker"
>
😀
</button>
9 changes: 6 additions & 3 deletions apps/desktop/src/components/chat/EmojiPalette.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { Picker } from "emoji-picker-element";
import type { EmojiClickEvent } from "emoji-picker-element/shared";

interface Props {
onClose: () => void;
Expand Down Expand Up @@ -49,16 +50,18 @@

const emojiPicker = document.querySelector("emoji-picker");

emojiPicker?.addEventListener("emoji-click", (event) => {
const handleEmojiClick = (event: EmojiClickEvent) => {
const emoji = event.detail.unicode;
if (!emoji) return;
onEmojiSelected(emoji);
});
};

emojiPicker?.addEventListener("emoji-click", handleEmojiClick);

return () => {
document.removeEventListener("click", handleClickOutside);
if (emojiPicker) {
emojiPicker.removeEventListener("emoji-click", () => {});
emojiPicker.removeEventListener("emoji-click", handleEmojiClick);
}
};
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
-->
{#if files.length > 0}
<div class="space-y-2">
<h4 class="text-base-content/70 text-sm font-medium">添付ファイル:</h4>
<h4 class="text-base-content/70 text-sm font-medium">Attached Files:</h4>
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4">
{#each files as file, index (file.name)}
<FilePreview
Expand Down
Loading