---
phase: 14-chat-integration-and-display
plan: 02
type: execute
wave: 2
depends_on: ["14-01"]
files_modified:
  - jam-ui/src/components/client/chat/JKChatMessage.js

# autonomous: true because tasks are straightforward implementation (similar to Phase 13-03)
# The checkpoint is included for final integration verification since this is the last plan
# of the phase and involves user-visible behavior (clicking to download files)
autonomous: false

must_haves:
  truths:
    - "Filename is clickable link that opens file in new browser tab"
    - "Browser handles view/download based on file type (PDF viewer, image display, audio player)"
    - "Chat history includes attachments (visible when joining session, persists across refresh)"
    - "Long filenames truncate with ellipsis at appropriate window sizes"
    - "Attachment messages display correctly at different window sizes (responsive)"
  artifacts:
    - path: "jam-ui/src/components/client/chat/JKChatMessage.js"
      provides: "Clickable attachment links with signed URL fetching"
      contains: "handleAttachmentClick"
  key_links:
    - from: "JKChatMessage.js"
      to: "getMusicNotationUrl"
      via: "import and fetch on click"
      pattern: "getMusicNotationUrl.*attachmentId"
    - from: "JKChatMessage.js"
      to: "window.open"
      via: "open signed URL in new tab"
      pattern: "window\\.open.*_blank"
---

<objective>
Add clickable attachment links with signed URL fetching and responsive layout for attachment messages.

Purpose: Users need to click attachment filenames to view/download files, and the UI must work well at different window sizes with proper filename truncation.

Output: Attachment filenames are clickable links that fetch signed S3 URLs and open files in new browser tabs. Long filenames truncate gracefully, and layout is responsive.
</objective>

<execution_context>
@/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md
@/Users/nuwan/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/14-chat-integration-and-display/14-01-SUMMARY.md
@jam-ui/src/components/client/chat/JKChatMessage.js
@jam-ui/src/helpers/rest.js
@.planning/phases/12-attachment-research-&-backend-validation/12-RESEARCH.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add click handler to fetch signed URL and open file</name>
  <files>jam-ui/src/components/client/chat/JKChatMessage.js</files>
  <action>
Add click handler for attachment filename that:
1. Fetches signed S3 URL via getMusicNotationUrl REST helper (already exists in rest.js line 1054)
2. Opens URL in new browser tab (target="_blank")

IMPORTANT: getMusicNotationUrl already exists in rest.js (created in Phase 12-02). Verify it returns {url: string} format before using.

Import the REST helper:
```javascript
import { getMusicNotationUrl } from '../../../helpers/rest';
```

Add click handler inside the component (wrap in useCallback for memoization):
```javascript
import React, { useCallback, useState } from 'react';

// Inside component:
const [isLoadingUrl, setIsLoadingUrl] = useState(false);

const handleAttachmentClick = useCallback(async (attachmentId) => {
  if (isLoadingUrl) return; // Prevent rapid clicks

  try {
    setIsLoadingUrl(true);
    const response = await getMusicNotationUrl(attachmentId);
    // Response is { url: "https://s3.amazonaws.com/...?signature=..." }
    if (response && response.url) {
      window.open(response.url, '_blank');
    } else {
      console.error('No URL in response:', response);
    }
  } catch (error) {
    console.error('Failed to fetch attachment URL:', error);
    // Could show toast error here, but keep it simple for now
  } finally {
    setIsLoadingUrl(false);
  }
}, [isLoadingUrl]);
```

Update the attachment name rendering to be a clickable link:
```javascript
<span
  onClick={() => handleAttachmentClick(message.attachmentId)}
  style={{
    cursor: 'pointer',
    color: '#1976d2',
    textDecoration: 'underline',
    // Truncation for long filenames
    maxWidth: '200px',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
    display: 'inline-block',
    verticalAlign: 'bottom'
  }}
  title={message.attachmentName} // Full filename on hover
>
  {message.attachmentName}
</span>
```

Note: Browser will handle file display based on Content-Type returned by S3:
- PDF: Opens in browser's PDF viewer
- Images (PNG, JPG, GIF): Displays in browser
- Audio (MP3, WAV): Opens browser's audio player
- Other types (XML, TXT): Typically downloads

The backend sets Content-Disposition based on file type.
  </action>
  <verify>
1. Verify getMusicNotationUrl exists in rest.js and returns {url: string}
2. Code compiles without errors
3. Clicking attachment filename triggers handleAttachmentClick
4. isLoadingUrl state prevents rapid multiple clicks
5. Error handling logs to console (no crash on failure)
  </verify>
  <done>
Attachment filenames are clickable links that fetch signed S3 URLs and open in new browser tabs. Loading state prevents rapid clicks. Errors logged to console without crashing.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add responsive styling with filename truncation</name>
  <files>jam-ui/src/components/client/chat/JKChatMessage.js</files>
  <action>
Enhance attachment message styling for responsive behavior:

1. Update attachment content container for better responsiveness:
```javascript
const attachmentContentStyle = {
  flex: 1,
  minWidth: 0, // Required for text-overflow to work in flex containers
  overflow: 'hidden'
};

const attachmentTextStyle = {
  fontSize: '14px',
  color: '#212529',
  display: 'flex',
  alignItems: 'center',
  flexWrap: 'wrap', // Allow wrapping on very narrow windows
  gap: '4px'
};

const filenameLinkStyle = {
  cursor: isLoadingUrl ? 'wait' : 'pointer',
  color: '#1976d2',
  textDecoration: 'underline',
  fontWeight: 600,
  // Truncation
  maxWidth: '100%',  // Take available width
  overflow: 'hidden',
  textOverflow: 'ellipsis',
  whiteSpace: 'nowrap',
  display: 'inline-block',
  verticalAlign: 'bottom'
};

const fileSizeStyle = {
  color: '#6c757d',
  fontSize: '13px',
  flexShrink: 0 // Prevent size from shrinking
};
```

2. Update the attachment render:
```javascript
if (isAttachmentMessage(message)) {
  const { senderName, createdAt, attachmentId, attachmentName, attachmentSize } = message;

  return (
    <div style={{
      display: 'flex',
      gap: '12px',
      marginBottom: '16px',
      padding: '8px',
      borderRadius: '4px',
      backgroundColor: '#e3f2fd',
      border: '1px solid #bbdefb'
    }}>
      {/* Avatar */}
      <div style={{
        width: '36px',
        height: '36px',
        borderRadius: '50%',
        backgroundColor: '#007bff',
        color: 'white',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontWeight: 'bold',
        fontSize: '14px',
        flexShrink: 0
      }}>
        {getInitials(senderName)}
      </div>

      {/* Content */}
      <div style={attachmentContentStyle}>
        {/* Header: Name + Timestamp */}
        <div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '4px', flexWrap: 'wrap' }}>
          <span style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>
            {senderName || 'Unknown'}
          </span>
          <span style={{ fontSize: '12px', color: '#6c757d' }}>
            {formatTimestamp(createdAt)}
          </span>
        </div>

        {/* Attachment info */}
        <div style={attachmentTextStyle}>
          <span style={{ fontSize: '16px', flexShrink: 0 }}>📎</span>
          <span>attached</span>
          <span
            onClick={() => handleAttachmentClick(attachmentId)}
            style={filenameLinkStyle}
            title={attachmentName}
          >
            {attachmentName}
          </span>
          {attachmentSize && (
            <span style={fileSizeStyle}>({formatFileSize(attachmentSize)})</span>
          )}
        </div>
      </div>
    </div>
  );
}
```

3. Ensure minWidth: 0 is set on all flex children that need truncation (this is critical for text-overflow: ellipsis to work).
  </action>
  <verify>
1. Long filenames truncate with ellipsis ("verylongfilename..." instead of overflow)
2. Full filename visible on hover (title attribute)
3. File size stays visible and doesn't truncate
4. Layout works at different window widths (resize chat window)
5. Content doesn't overflow container bounds
  </verify>
  <done>
Attachment messages display correctly at different window sizes. Long filenames truncate with ellipsis, full name shown on hover. File size and timestamp always visible.
  </done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>
Complete attachment message display with clickable links, signed URL fetching, and responsive layout
  </what-built>
  <how-to-verify>
1. Start the app: `cd jam-ui && npm start`
2. Start backend: `./runweb` (or appropriate Rails command)
3. Login and join a music session
4. Upload a file using the Attach button (from Phase 13)
5. Verify attachment message appears with:
   - Light blue background (distinct from text messages)
   - Paperclip icon
   - "[YourName] attached [filename]" format
   - Clickable filename (underlined, blue color)
6. Click the filename:
   - Should open new browser tab
   - PDF/images should display in browser
   - Other files should download
7. Test long filename: Upload a file with a very long name
   - Should truncate with ellipsis
   - Hover should show full name
8. Resize chat window:
   - Content should remain readable
   - No overflow issues
9. Refresh page:
   - Attachment should still be visible in chat history
10. Test regular text message still works normally
  </how-to-verify>
  <resume-signal>Type "approved" if all checks pass, or describe any issues found</resume-signal>
</task>

</tasks>

<verification>
1. Attachment filename is clickable link
2. Clicking fetches signed S3 URL and opens in new tab
3. Browser displays/downloads file based on type
4. Long filenames truncate with ellipsis, full name on hover
5. Layout responsive at different window sizes
6. Chat history includes attachments after refresh
7. Regular text messages still render correctly
</verification>

<success_criteria>
- Filename is clickable link opening file in new browser tab
- Browser handles view/download based on file type (PDF views, images display, others download)
- Long filenames truncate with ellipsis, full name shown on hover
- Responsive layout at different window sizes
- Chat history includes attachments (persists across refresh)
- Auto-scroll works when new attachment appears (existing behavior)
</success_criteria>

<output>
After completion, create `.planning/phases/14-chat-integration-and-display/14-02-SUMMARY.md`
</output>
