@@ -1713,6 +1811,9 @@
document.getElementById('noteType').value = 'personal';
document.getElementById('jobNumber').value = '';
document.getElementById('jobNumberContainer').classList.add('hidden');
+ document.getElementById('userSearchInput').value = '';
+ selectedUsers = [];
+ renderSelectedUsers();
clearAudio();
pendingNoteAttachments = [];
const noteAttachmentsInput = document.getElementById('noteAttachmentsInput');
@@ -1751,11 +1852,19 @@
card.type = 'button';
card.dataset.noteId = note.id;
const type = (note.type || 'personal').toLowerCase();
- const typeClasses = {
- personal: 'bg-amber-100 border border-amber-300 hover:border-amber-400',
- manager: 'bg-orange-200 border border-orange-400 hover:border-orange-500',
- job: 'bg-pink-200 border border-pink-400 hover:border-pink-500',
- }[type] || 'bg-gray-100 border border-gray-300 hover:border-primary/60';
+
+ // Use green background for shared notes, otherwise use type-based colors
+ let typeClasses;
+ if (note.shared) {
+ typeClasses = 'bg-green-100 border border-green-300 hover:border-green-400';
+ } else {
+ typeClasses = {
+ personal: 'bg-amber-100 border border-amber-300 hover:border-amber-400',
+ manager: 'bg-orange-200 border border-orange-400 hover:border-orange-500',
+ job: 'bg-pink-200 border border-pink-400 hover:border-pink-500',
+ }[type] || 'bg-gray-100 border border-gray-300 hover:border-primary/60';
+ }
+
card.className = `text-left ${typeClasses} hover:shadow-md transition rounded-xl px-2 py-0.5 space-y-0 h-full flex flex-col`;
const title = note.title || 'Untitled';
const plain = note[NOTE_BODY_PLAIN_FIELD] || (note[NOTE_BODY_HTML_FIELD] ? htmlToPlainText(note[NOTE_BODY_HTML_FIELD]) : '');
@@ -1776,15 +1885,25 @@
.filter((k) => typeSet.has(k))
.map(iconBtnHtml)
.join('');
+
+ // Create type indicator badge with matching card background colors
+ const typeColorMap = {
+ personal: { bg: 'bg-amber-200', border: 'border-amber-400', text: 'text-amber-900' },
+ manager: { bg: 'bg-orange-300', border: 'border-orange-500', text: 'text-orange-900' },
+ job: { bg: 'bg-pink-300', border: 'border-pink-500', text: 'text-pink-900' },
+ };
+ const typeColor = typeColorMap[type] || { bg: 'bg-gray-200', border: 'border-gray-400', text: 'text-gray-900' };
+ const typeBadge = `
-
${escapeHtml(meta)}
+
${escapeHtml(snippet || '(empty)')}
+
${attachmentIcons}
+ ${typeBadge}
`;
frag.appendChild(card);
@@ -2148,11 +2267,36 @@
const recorder = await window.recordScreen();
if (recorder) {
- // Auto-stop after 5 minutes
- setTimeout(async () => {
+ // Create stop button overlay
+ const stopOverlay = document.createElement('div');
+ stopOverlay.style.cssText = `position:fixed;top:20px;right:20px;z-index:50000;background:white;padding:15px 20px;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);`;
+
+ const stopBtn = document.createElement('button');
+ stopBtn.textContent = '⏹ Stop Recording';
+ stopBtn.style.cssText = `padding:10px 20px;background:#EF4444;color:white;border:none;border-radius:6px;font-size:14px;font-weight:bold;cursor:pointer;`;
+
+ stopBtn.addEventListener('click', async () => {
const file = await recorder.stop();
- pendingNoteAttachments.push(file);
- renderNoteAttachmentsList();
+ if (file) {
+ pendingNoteAttachments.push(file);
+ renderNoteAttachmentsList();
+ }
+ stopOverlay.remove();
+ });
+
+ stopOverlay.appendChild(stopBtn);
+ document.body.appendChild(stopOverlay);
+
+ // Auto-stop after 5 minutes as fallback
+ setTimeout(async () => {
+ if (recorder.isRecording()) {
+ const file = await recorder.stop();
+ if (file) {
+ pendingNoteAttachments.push(file);
+ renderNoteAttachmentsList();
+ }
+ stopOverlay.remove();
+ }
}, 5 * 60 * 1000);
}
} catch (error) {
@@ -2270,11 +2414,36 @@
const recorder = await window.recordScreen();
if (recorder) {
- // Auto-stop after 5 minutes
- setTimeout(async () => {
+ // Create stop button overlay
+ const stopOverlay = document.createElement('div');
+ stopOverlay.style.cssText = `position:fixed;top:20px;right:20px;z-index:50000;background:white;padding:15px 20px;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);`;
+
+ const stopBtn = document.createElement('button');
+ stopBtn.textContent = '⏹ Stop Recording';
+ stopBtn.style.cssText = `padding:10px 20px;background:#EF4444;color:white;border:none;border-radius:6px;font-size:14px;font-weight:bold;cursor:pointer;`;
+
+ stopBtn.addEventListener('click', async () => {
const file = await recorder.stop();
- pendingConversationAttachments.push(file);
- renderConversationAttachmentsList();
+ if (file) {
+ pendingConversationAttachments.push(file);
+ renderConversationAttachmentsList();
+ }
+ stopOverlay.remove();
+ });
+
+ stopOverlay.appendChild(stopBtn);
+ document.body.appendChild(stopOverlay);
+
+ // Auto-stop after 5 minutes as fallback
+ setTimeout(async () => {
+ if (recorder.isRecording()) {
+ const file = await recorder.stop();
+ if (file) {
+ pendingConversationAttachments.push(file);
+ renderConversationAttachmentsList();
+ }
+ stopOverlay.remove();
+ }
}, 5 * 60 * 1000);
}
} catch (error) {
@@ -2371,6 +2540,98 @@
}
});
+ // --- Share Note Logic ---
+ let selectedUsers = []; // { id, displayName, firstName }
+ const userSearchInput = document.getElementById('userSearchInput');
+ const userSuggestions = document.getElementById('userSuggestions');
+ const selectedUsersContainer = document.getElementById('selectedUsersContainer');
+
+ userSearchInput.addEventListener('input', async (e) => {
+ const query = e.target.value.trim().toLowerCase();
+ userSuggestions.innerHTML = '';
+
+ if (!query) {
+ userSuggestions.classList.add('hidden');
+ return;
+ }
+
+ try {
+ const allUsers = await pb.collection('users').getFullList({
+ sort: 'displayName'
+ });
+
+ const matching = allUsers.filter(u =>
+ u.displayName?.toLowerCase().includes(query) ||
+ u.firstName?.toLowerCase().includes(query) ||
+ u.email?.toLowerCase().includes(query)
+ ).filter(u => !selectedUsers.find(su => su.id === u.id));
+
+ if (matching.length === 0) {
+ userSuggestions.innerHTML = '
No users found
';
+ } else {
+ matching.forEach(user => {
+ const option = document.createElement('button');
+ option.type = 'button';
+ option.className = 'w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition';
+ option.innerHTML = `
${escapeHtml(user.displayName || user.email)}
${escapeHtml(user.email)}
`;
+ option.addEventListener('click', (ev) => {
+ ev.preventDefault();
+ selectUser(user);
+ userSearchInput.value = '';
+ userSuggestions.classList.add('hidden');
+ });
+ userSuggestions.appendChild(option);
+ });
+ }
+ userSuggestions.classList.remove('hidden');
+ } catch (err) {
+ console.error('Failed to load users:', err);
+ userSuggestions.innerHTML = '
Failed to load users
';
+ }
+ });
+
+ function selectUser(user) {
+ if (selectedUsers.find(u => u.id === user.id)) return;
+
+ selectedUsers.push({
+ id: user.id,
+ displayName: user.displayName || user.email,
+ firstName: user.firstName || (user.displayName?.split(' ')[0] || 'User')
+ });
+
+ renderSelectedUsers();
+ }
+
+ function removeUser(userId) {
+ selectedUsers = selectedUsers.filter(u => u.id !== userId);
+ renderSelectedUsers();
+ }
+
+ function renderSelectedUsers() {
+ selectedUsersContainer.innerHTML = '';
+ selectedUsers.forEach(user => {
+ const capsule = document.createElement('div');
+ capsule.className = 'inline-flex items-center gap-2 px-3 py-1 rounded-full bg-green-100 border border-green-300 text-sm text-gray-800';
+ capsule.innerHTML = `
+
${escapeHtml(user.firstName)}
+
+ `;
+ const removeBtn = capsule.querySelector('[data-remove-user]');
+ removeBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ removeUser(user.id);
+ });
+ selectedUsersContainer.appendChild(capsule);
+ });
+ }
+
+ // Close suggestions when clicking outside
+ document.addEventListener('click', (e) => {
+ if (!e.target.closest('#userSearchInput') && !e.target.closest('#userSuggestions')) {
+ userSuggestions.classList.add('hidden');
+ }
+ });
+
// --- Submit note ---
async function submitNote() {
const statusEl = document.getElementById('submitStatus');
@@ -2420,6 +2681,8 @@
job_note: jobNoteValue === 'yes',
type: noteType,
userId: pb.authStore.model?.id || pb.authStore.record?.id,
+ shared: selectedUsers.length > 0,
+ shared_with: selectedUsers.map(u => u.id),
};
// Only include job_number if it has a value
@@ -2466,7 +2729,7 @@
if (audioAttachmentCount) attachmentParts.push(`${audioAttachmentCount} audio`);
if (fileAttachmentCount) attachmentParts.push(`${fileAttachmentCount} file`);
const attachmentMsg = attachmentParts.length ? ` with ${attachmentParts.join(' and ')} attachment(s)` : '';
- statusEl.textContent = `Saved note ${noteRecord.id}${attachmentMsg}`;
+ statusEl.textContent = '';
await clearAudio();
pendingNoteAttachments = [];
document.getElementById('imageInput').value = '';
diff --git a/logs/SESSION_LOG.txt b/logs/SESSION_LOG.txt
index 2f59380..88bbb36 100644
--- a/logs/SESSION_LOG.txt
+++ b/logs/SESSION_LOG.txt
@@ -3,6 +3,21 @@ PRISM NOTES - SESSION LOG
Project history, decisions, and architectural milestones
================================================================================
+[2026-01-11 TODO] Screen Capture Window Visibility Issue - INVESTIGATE LATER
+- Issue: Prism Notes window still appears in getDisplayMedia() dialog options
+ even though visibility is hidden before dialog opens
+- Attempted Fix: Used visibility:hidden on app elements before getDisplayMedia()
+- Result: Not working - window still shows in screen selector
+- Root Cause: Unclear - may be timing issue or browser caching of window list
+- Workaround: User can manually select other screen/window instead
+- Investigation Needed:
+ * Try alternative hiding methods (display:none, opacity:0, positioning off-screen)
+ * Check if timing delay needed before getDisplayMedia()
+ * Verify with different browsers (Chrome vs Edge vs Firefox)
+ * Consider if OS-level window management interferes
+- Impact: Minor UX issue - doesn't prevent functionality, just less elegant
+- Priority: LOW - defer until screen capture feature stabilized otherwise
+
[2026-01-10 20:10] MONICA SESSION: Fixed Container Injection Bug
- Issue: Refactor commit (cee9157) had CONTAINERS + initializeContainers() code
but ALSO kept all old HTML in DOM, creating duplicates and preventing injection