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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

#### Mobile Responsive Styling for Settings and Badge Widgets (Issue #690)
- **Badge component z-index optimization** (`frontend/src/components/badges/Badge.tsx:47,107`): Lowered z-index values from 9999/10000 to 200/201 to avoid conflicts with other UI elements while maintaining proper layering
- **Unified mobile behavior detection** (`frontend/src/components/badges/Badge.tsx:148-152`): Combined touch device detection with viewport width check to ensure mobile UX works consistently across real devices and test environments
- **Test wrapper extraction** (`frontend/tests/UserBadgesTestWrapper.tsx`, `frontend/tests/GlobalSettingsPanelTestWrapper.tsx`): Moved test wrappers to separate files following Playwright component testing best practices
- **Improved test reliability** (`frontend/tests/mobile-responsive.ct.tsx`): Fixed element disambiguation issues using proper locator strategies

#### Agent Chat Processing Indicator (PR #687)
- **Added visual feedback for agent processing** (`frontend/src/components/widgets/chat/ChatMessage.tsx:1342-1405`): When an agent starts processing a response, an animated "Agent is thinking..." indicator now displays instead of an empty message bubble
- **Processing indicator conditions**: Shows when assistant message is incomplete with no content and no timeline entries
Expand Down Expand Up @@ -229,6 +235,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **FAB z-index layering** (`frontend/src/views/Corpuses.tsx:1320`): Raised FAB z-index from 100 to 150 to ensure visibility above folder sidebar toggle (z-index: 101)
- **Explicit z-index layering**: Made mobile sidebar z-index layering explicit (backdrop: 98, toggle button: 99) to prevent fragile DOM-order-dependent behavior

#### Mobile Responsive Styling for Settings and Badge Widgets (PR #690)
- **UserSettingsModal responsive styling** (`frontend/src/components/modals/UserSettingsModal.tsx:14-80`):
- Modal takes 95% width on mobile (≤768px) with reduced padding
- Form groups stack vertically on small screens (≤480px) for single-column layout
- Action buttons display full-width and stack vertically (Save above Close) on mobile
- Added `styled-components` import and styled wrapper components
- **Badge component touch support** (`frontend/src/components/badges/Badge.tsx:23-41, 96-112, 145-199`):
- Added tap-to-toggle tooltip on touch devices (detects via `ontouchstart`)
- Created `MobileOverlay` backdrop for dismissing badge popups by tapping outside
- Popup centers on mobile screens using fixed positioning instead of floating-ui
- Increased touch target size (min-height 36px, larger padding)
- Disabled hover transforms on touch devices using `@media (hover: none)`
- **UserBadges container responsive layout** (`frontend/src/components/badges/UserBadges.tsx:18-27, 37-48, 58-61`):
- Reduced padding and gap on mobile viewports
- Badges center-aligned on mobile for better visual balance
- Empty state and header text sizes reduced on mobile
- **GlobalSettingsPanel responsive grid** (`frontend/src/components/admin/GlobalSettingsPanel.tsx:11-67, 82-104, 119-123, 137-139, 148-150, 163-168`):
- Container padding reduced on mobile (2rem → 1rem → 0.75rem)
- Settings grid switches to single column on small mobile (≤480px)
- Card content padding reduced progressively on smaller screens
- Touch-friendly card interactions with active state feedback (scale 0.98)
- "Coming Soon" badge displays on its own line on very small screens

### Changed

#### Mobile UI/UX Refactoring
Expand Down
131 changes: 131 additions & 0 deletions docs/frontend/discussions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Discussions System (Threads & Messages)

This document covers the frontend architecture for OpenContracts' discussion/threading system, including the rich text composer and mention system.

## Overview

The discussions system allows users to have threaded conversations within corpuses. Key components:

- **ThreadDetail**: Displays a conversation with nested message tree
- **MessageTree**: Recursive component for nested replies
- **ReplyForm**: Wrapper for creating new messages/replies
- **MessageComposer**: Rich text editor with @mention support

## MessageComposer Architecture

Located at: `frontend/src/components/threads/MessageComposer.tsx`

The MessageComposer is a TipTap-based rich text editor with custom @mention functionality for linking to internal resources.

### TipTap Extensions Used

| Extension | Purpose | Configuration |
|-----------|---------|---------------|
| `StarterKit` | Basic formatting (bold, italic, lists) | Code blocks and blockquotes disabled |
| `Markdown` | Export content as Markdown | `linkify: false` (see Link extension) |
| `Link` | Handle all link marks | `autolink: true` for URL detection |
| `Placeholder` | Show placeholder text | Standard usage |
| `Mention` | @mention suggestion UI only | Custom render/selection (see below) |

### Custom Mention → Link Architecture

**Why we diverge from default TipTap Mention behavior:**

TipTap's default Mention extension inserts **Mention nodes**:
```html
<span data-type="mention" data-id="123" data-label="@username">@username</span>
```

We instead insert **Link marks** on text:
```html
<a href="/users/user-slug">@username</a>
```

**Reasons for this design:**

1. **Deep linking**: Mentions become clickable links to users, documents, annotations, etc.
2. **Markdown compatibility**: Links serialize naturally as `[text](url)` via tiptap-markdown
3. **Multi-resource support**: Same pattern works for 5 different resource types
4. **No custom serialization**: Standard Link mark needs no special handling

### Mention Flow

```
User types "@foo"
TipTap Mention suggestion triggers
useUnifiedMentionSearch(query, corpusId) → GraphQL search
UnifiedMentionPicker shows results (users, corpuses, documents, annotations, agents)
User selects a result
getMentionData(resource) builds deep link URL based on resource type
Insert text with Link mark (NOT Mention node)
Markdown export produces: [Label](deep-link-url)
```

### Resource Types and Deep Links

| Resource | Label Format | URL Pattern |
|----------|--------------|-------------|
| User | `@username` | `/users/{slug}` |
| Corpus | `{title}` | `/c/{creator}/{corpus}` |
| Document | `{title} (in {corpus})` | `/d/{creator}/{corpus}/{doc}` |
| Annotation | `"text..." (Label)` | `/d/.../doc?ann={id}&structural=true` |
| Agent | `@agent:{slug}` | `/agents/{slug}` |

### Key Implementation Details

**We use Mention extension for:**
- `@` character trigger detection
- Suggestion popup positioning (via floating-ui)
- Keyboard navigation (up/down/enter/escape)

**We override Mention's default behavior:**
- `suggestion.render().onSelect` inserts Link marks instead of Mention nodes
- Results come from `useUnifiedMentionSearch` hook (GraphQL backend search)

**Link extension handles:**
- All link mark rendering and behavior
- `autolink: true` for automatic URL detection while typing
- `openOnClick: false` to prevent navigation while editing

### Configuration Notes

**Avoiding duplicate 'link' extension warning:**

The `tiptap-markdown` package with `linkify: true` can conflict with the `Link` extension. To avoid duplication:

```typescript
// CORRECT: Use Link's autolink, disable Markdown's linkify
Markdown.configure({
linkify: false, // ← Disable here
}),
Link.configure({
autolink: true, // ← URL detection happens here
}),
```

**Autofocus:**

Use TipTap's built-in `autofocus` option instead of manual `useEffect`:

```typescript
const editor = useEditor({
autofocus: autoFocus, // ← Built-in, handles mount timing correctly
// ...
});
```

## Related Files

- `frontend/src/components/threads/MessageComposer.tsx` - Rich text editor
- `frontend/src/components/threads/UnifiedMentionPicker.tsx` - Mention suggestion dropdown
- `frontend/src/components/threads/hooks/useUnifiedMentionSearch.ts` - GraphQL search hook
- `frontend/src/components/threads/ThreadDetail.tsx` - Thread display
- `frontend/src/components/threads/MessageTree.tsx` - Nested message rendering
- `frontend/src/components/threads/ReplyForm.tsx` - Reply form wrapper
77 changes: 77 additions & 0 deletions frontend/src/components/admin/GlobalSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,64 @@ const Container = styled.div`
padding: 2rem;
max-width: 1200px;
margin: 0 auto;

@media (max-width: 768px) {
padding: 1rem;
}

@media (max-width: 480px) {
padding: 0.75rem;
}
`;

const PageHeader = styled.div`
margin-bottom: 2rem;

@media (max-width: 768px) {
margin-bottom: 1.5rem;
text-align: center;
}
`;

const PageTitle = styled(Header)`
&.ui.header {
margin-bottom: 0.5rem;
color: #1e293b;

@media (max-width: 768px) {
font-size: 1.5rem !important;
}

@media (max-width: 480px) {
font-size: 1.3rem !important;
}
}
`;

const PageDescription = styled.p`
color: #64748b;
font-size: 1rem;
margin: 0;

@media (max-width: 768px) {
font-size: 0.9rem;
}
`;

const SettingsGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;

@media (max-width: 768px) {
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 1rem;
}

@media (max-width: 480px) {
grid-template-columns: 1fr;
gap: 0.75rem;
}
`;

const SettingsCard = styled(Card)`
Expand All @@ -44,8 +79,29 @@ const SettingsCard = styled(Card)`
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Disable hover transforms on touch devices */
@media (hover: none) {
&:hover {
transform: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

&:active {
transform: scale(0.98);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
}

.content {
padding: 1.5rem;

@media (max-width: 768px) {
padding: 1.25rem;
}

@media (max-width: 480px) {
padding: 1rem;
}
}
}
`;
Expand All @@ -60,6 +116,12 @@ const CardIcon = styled.div<{ $color: string }>`
justify-content: center;
margin-bottom: 1rem;

@media (max-width: 768px) {
width: 44px;
height: 44px;
margin-bottom: 0.75rem;
}

i.icon {
color: white;
margin: 0;
Expand All @@ -71,13 +133,21 @@ const CardTitle = styled.h3`
font-weight: 600;
color: #1e293b;
margin: 0 0 0.5rem 0;

@media (max-width: 768px) {
font-size: 1rem;
}
`;

const CardDescription = styled.p`
font-size: 0.875rem;
color: #64748b;
margin: 0;
line-height: 1.5;

@media (max-width: 768px) {
font-size: 0.8rem;
}
`;

const ComingSoonBadge = styled.span`
Expand All @@ -89,6 +159,13 @@ const ComingSoonBadge = styled.span`
padding: 0.25rem 0.75rem;
border-radius: 9999px;
margin-left: 0.5rem;

@media (max-width: 480px) {
display: block;
margin-left: 0;
margin-top: 0.5rem;
width: fit-content;
}
`;

interface SettingItem {
Expand Down
Loading
Loading