Repository navigation
Calendar Icon #636
Description
Calendar Icon:
This is a calendar centric notepad for users to add time sensitive context regarding their geospatial workflows whilst collaborating with other users. Users may move dates from a top scroll bar into future or past dates retrieving notes or adding reminders for a future event.
Users may tag locations or tag their collaborators.
Information is indexed asynchronously to the language model into the context.
It should be a minimal design that retains information and enhances knowledge discovery.
Example:
User is indexing a plot for a commercial property with collaborators in Norway.
The collaborators are working asynchronously in the map render and prompting the system for discovery. The users have ragged additional documents. The users realize they need to revisit the site in a week when the local Municipal has performed their land divisions. They input this into the notepad with their survey requirements, tagging each other and the plot as well as this event’s future date.
The system understands and retains context and seeks additional knowledge asynchronously.
I need to clarify what you're asking about. Based on the codebase context I have access to, you appear to be describing a feature request or design specification for a calendar-based notepad component that doesn't currently exist in the QCX codebase.
Current State of Calendar Icon
The calendar icon (CalendarDays) currently exists in the UI but has no functionality implemented:
- In the desktop header at
components/header.tsx:38-40, there's aCalendarDaysbutton with no click handler 1 - In the mobile icons bar at
components/mobile-icons-bar.tsx:43-45, the same icon appears without any associated functionality 2
What Doesn't Exist Yet
The features you're describing are not implemented in the current codebase:
- Calendar-centric notepad interface - No component exists for this
- Date scrolling/navigation - Not present
- Note creation with location/collaborator tagging - Not implemented
- Asynchronous indexing to language model - No such system exists
- Context retention for geospatial workflows - Not built
What Would Need to Be Built
To implement your specification, you would need to create:
- A new calendar component that renders when the
CalendarDaysicon is clicked - A data model for storing notes with timestamps, location tags, and user tags
- Integration with the existing
MapDataContextto link notes to map features 3 - A persistence layer (likely extending the existing Supabase schema)
- An indexing mechanism to feed note content into the AI context
Notes
This appears to be a feature specification rather than a question about existing code. The QCX application currently focuses on AI-powered geospatial chat with map integration, but does not have any calendar or time-based note-taking functionality. If you're looking to implement this feature, I'd be happy to help design the architecture or discuss how it could integrate with the existing map and AI systems.
Wiki pages you might want to explore:
Citations
File: components/header.tsx (L38-40)
<Button variant="ghost" size="icon">
<CalendarDays className="h-[1.2rem] w-[1.2rem]" />
</Button>File: components/mobile-icons-bar.tsx (L43-45)
<Button variant="ghost" size="icon">
<CalendarDays className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>File: components/map/map-query-handler.tsx (L5-5)
import { useMapData } from './map-data-context'; Implementation Overview: Calendar Notepad System
Based on your specification, you're asking how to implement a calendar-centric notepad feature that integrates with QCX's existing geospatial chat and map system.
System Architecture
The calendar notepad would connect to three core systems in QCX:
- Map System - For location tagging and spatial context
- Chat/Message System - For persistence and AI context
- AI Agent System - For asynchronous knowledge indexing 1 2
Implementation Components
1. Calendar UI Component
Create a new component components/calendar-notepad.tsx that renders when the CalendarDays icon is clicked. 3
Key features:
- Date navigation bar (horizontal scroll)
- Note creation/editing interface
- Tag input for locations and collaborators
- Integration with existing UI patterns
interface CalendarNote {
id: string;
userId: string;
date: Date;
content: string;
locationTags: [number, number][]; // [lng, lat] coordinates
userTags: string[]; // User IDs
mapFeatureId?: string; // Link to drawn features
createdAt: Date;
updatedAt: Date;
}2. Database Schema Extension
Extend the existing Supabase schema to store calendar notes. The current system uses Drizzle ORM with chats and messages tables. 4
Add a new table:
CREATE TABLE calendar_notes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id),
chat_id UUID REFERENCES chats(id), -- Link to chat context
date TIMESTAMP NOT NULL,
content TEXT NOT NULL,
location_tags JSONB, -- Array of coordinates
user_tags TEXT[], -- Array of user IDs
map_feature_id TEXT, -- Reference to drawn features
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);3. Context Integration with MapDataContext
The calendar notes need to interact with the map system through MapDataContext. 1
Integration points:
- Location tagging: When a user tags a location, capture
mapData.targetPositionormapData.drawnFeatures - Feature linking: Store references to drawn polygons/lines from the map
- Bidirectional updates: Clicking a calendar note should update
setMapData()to navigate to tagged locations 5
4. AI Context Indexing
The system already has a pattern for feeding context to AI agents through the chat system. 6
Implementation approach:
-
Store notes as special message types: Similar to how
updateDrawingContextstores drawn features as messages withrole: 'data'7 -
Create a calendar context message type:
const calendarContextMessage: Omit<DbNewMessage, 'chatId'> = {
userId: userId,
role: 'data',
content: JSON.stringify({
type: 'calendar_note',
note: calendarNote,
date: note.date,
locationTags: note.locationTags,
userTags: note.userTags
}),
createdAt: new Date()
};- Asynchronous indexing: When notes are created/updated, append them to the chat's message history so the AI agents can access them in future queries 8
5. Geospatial Tool Integration
The existing geospatialQueryTool can be extended to understand calendar context. 9
When the AI processes a query, it would:
- Check for relevant calendar notes based on date/location
- Include note content in the context window
- Use
MapQueryHandlerto navigate to tagged locations 10
6. Collaboration Features
For user tagging and collaboration:
- User mentions: Parse
@usernamesyntax in note content - Notifications: Create a notification system (new table) when users are tagged
- Shared context: Notes are associated with
chatId, so collaborators in the same chat see the same notes - Real-time updates: Use Supabase real-time subscriptions to sync notes across users
Implementation Flow
graph TB
User["User clicks CalendarDays icon"]
CalendarUI["Calendar Notepad Component"]
CreateNote["Create/Edit Note"]
TagLocation["Tag Location from Map"]
TagUsers["Tag Collaborators"]
SaveNote["Save to Database"]
CreateMessage["Create Calendar Context Message"]
AIIndex["AI Agent Indexes Content"]
MapUpdate["Update Map View"]
User --> CalendarUI
CalendarUI --> CreateNote
CreateNote --> TagLocation
CreateNote --> TagUsers
TagLocation --> MapUpdate
CreateNote --> SaveNote
SaveNote --> CreateMessage
CreateMessage --> AIIndex
MapUpdate -.->|"setMapData()"| MapDataContext["MapDataContext"]
TagLocation -.->|"reads"| MapDataContext
Key Integration Points
-
Map Context: Use
useMapData()hook to read/write map state 11 -
Chat Persistence: Use
saveChat()andcreateMessage()functions 12 -
AI State: Notes become part of
AIStatemessages array, accessible to all agents 13 -
Map Features: Link to
drawnFeaturesfromMapDataContext5
Notes
The implementation leverages existing patterns in QCX:
- The
MapDataContextalready handles bidirectional map-chat communication - The message system with
role: 'data'provides a precedent for storing structured context - The
MapQueryHandlerpattern shows how to trigger map updates from AI responses
The calendar notepad would be a new top-level feature but integrates naturally with the existing architecture. The main new components are the UI layer and database schema; the integration points already exist in the codebase.
Wiki pages you might want to explore:
Citations
File: lib/actions/chat-db.ts (L74-148)
/**
* Saves a chat and its messages. If the chat exists, it updates it.
* This function should handle both creating new chats and appending messages.
* The PR implies complex logic for saving, including message IDs.
* This is a simplified version; PR #533 might have more granular message saving.
* @param chatData - The chat data to save.
* @param messagesData - An array of messages to save with the chat.
* @returns The saved chat ID.
*/
export async function saveChat(chatData: NewChat, messagesData: Omit<NewMessage, 'chatId'>[]): Promise<string | null> {
if (!chatData.userId) {
console.error('Cannot save chat without a userId');
return null;
}
// Transaction to ensure atomicity
return db.transaction(async (tx) => {
let chatId = chatData.id;
if (chatId) { // If chat ID is provided, assume update or append messages
const existingChat = await tx.select({ id: chats.id }).from(chats).where(eq(chats.id, chatId)).limit(1);
if (!existingChat.length) {
// Chat doesn't exist, so create it
const newChatResult = await tx.insert(chats).values(chatData).returning({ id: chats.id });
chatId = newChatResult[0].id;
} else {
// Optionally update chat metadata here if needed, e.g., title
if (chatData.title) {
await tx.update(chats).set({ title: chatData.title }).where(eq(chats.id, chatId));
}
}
} else { // No chat ID, create new chat
const newChatResult = await tx.insert(chats).values(chatData).returning({ id: chats.id });
chatId = newChatResult[0].id;
}
if (!chatId) {
// console.error('Failed to establish chatId within transaction.'); // Optional: for server logs
throw new Error('Failed to establish chatId for chat operation.');
}
// Save messages
if (messagesData && messagesData.length > 0) {
const messagesToInsert = messagesData.map(msg => ({
...msg,
chatId: chatId!, // Ensure chatId is set for all messages
userId: msg.userId || chatData.userId!, // Ensure userId is set
}));
await tx.insert(messages).values(messagesToInsert);
}
return chatId;
});
}
/**
* Creates a single message within a chat.
* PR #533 has commits like "feat: Add message update and trailing deletion logic",
* suggesting more granular message operations. This is a basic create.
* @param messageData - The message data to save.
* @returns The created message object or null if error.
*/
export async function createMessage(messageData: NewMessage): Promise<Message | null> {
if (!messageData.chatId || !messageData.userId || !messageData.role || !messageData.content) {
console.error('Missing required fields for creating a message.');
return null;
}
try {
const result = await db.insert(messages).values(messageData).returning();
return result[0] || null;
} catch (error) {
console.error('Error creating message:', error);
return null;
}
}File: components/header.tsx (L38-40)
<Button variant="ghost" size="icon">
<CalendarDays className="h-[1.2rem] w-[1.2rem]" />
</Button>File: components/chat.tsx (L74-83)
// Get mapData to access drawnFeatures
const { mapData } = useMapData();
// useEffect to call the server action when drawnFeatures changes
useEffect(() => {
if (id && mapData.drawnFeatures && mapData.drawnFeatures.length > 0) {
console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures);
updateDrawingContext(id, mapData.drawnFeatures);
}
}, [id, mapData.drawnFeatures]);File: app/actions.tsx (L1-29)
import {
StreamableValue,
createAI,
createStreamableUI,
createStreamableValue,
getAIState,
getMutableAIState
} from 'ai/rsc'
import { CoreMessage, ToolResultPart } from 'ai'
import { nanoid } from 'nanoid'
import type { FeatureCollection } from 'geojson'
import { Spinner } from '@/components/ui/spinner'
import { Section } from '@/components/section'
import { FollowupPanel } from '@/components/followup-panel'
import { inquire, researcher, taskManager, querySuggestor, resolutionSearch } from '@/lib/agents'
// Removed import of useGeospatialToolMcp as it no longer exists and was incorrectly used here.
// The geospatialTool (if used by agents like researcher) now manages its own MCP client.
import { writer } from '@/lib/agents/writer'
import { saveChat, getSystemPrompt } from '@/lib/actions/chat' // Added getSystemPrompt
import { Chat, AIMessage } from '@/lib/types'
import { UserMessage } from '@/components/user-message'
import { BotMessage } from '@/components/message'
import { SearchSection } from '@/components/search-section'
import SearchRelated from '@/components/search-related'
import { GeoJsonLayer } from '@/components/map/geojson-layer'
import { CopilotDisplay } from '@/components/copilot-display'
import RetrieveSection from '@/components/retrieve-section'
import { VideoSearchSection } from '@/components/video-search-section'
import { MapQueryHandler } from '@/components/map/map-query-handler' // Add this importFile: app/actions.tsx (L640-655)
case 'tool':
try {
const toolOutput = JSON.parse(content as string)
const isCollapsed = createStreamableValue()
isCollapsed.done(true)
if (
toolOutput.type === 'MAP_QUERY_TRIGGER' &&
name === 'geospatialQueryTool'
) {
return {
id,
component: <MapQueryHandler toolOutput={toolOutput} />,
isCollapsed: false
}
}File: lib/actions/chat.ts (L165-205)
export async function updateDrawingContext(chatId: string, drawnFeatures: any[]) {
'use server';
console.log('[Action] updateDrawingContext called for chatId:', chatId);
const userId = await getCurrentUserIdOnServer(); // Essential for creating a user-associated message
if (!userId) {
console.error('updateDrawingContext: Could not get current user ID. User must be authenticated.');
return { error: 'User not authenticated' };
}
// The old version fetched the whole chat. Now we just create a new message.
// The AIMessage type might be from '@/lib/types' and need mapping to DbNewMessage
const newDrawingMessage: Omit<DbNewMessage, 'chatId'> = {
// id: `drawnData-${Date.now().toString()}`, // Let DB generate UUID
userId: userId,
role: 'data' as 'user' | 'assistant' | 'system' | 'tool' | 'data', // Cast 'data' if not in standard roles
content: JSON.stringify(drawnFeatures), // Store features as stringified JSON
// type: 'drawing_context', // This field is not in the Drizzle 'messages' schema.
// If `type` is important, the schema needs to be updated or content needs to reflect it.
// For now, we'll assume 'content' holds the necessary info and role='data' signifies it.
createdAt: new Date(),
};
try {
// We need to ensure the message is associated with the chat.
// dbCreateMessage requires chatId.
const messageToSave: DbNewMessage = {
...newDrawingMessage,
chatId: chatId,
};
const savedMessage = await dbCreateMessage(messageToSave);
if (!savedMessage) {
throw new Error('Failed to save drawing context message.');
}
console.log('Drawing context message added to chat:', chatId, 'messageId:', savedMessage.id);
return { success: true, messageId: savedMessage.id };
} catch (error) {
console.error('updateDrawingContext: Error saving drawing context message:', error);
return { error: 'Failed to save drawing context message' };
}
}File: components/map/map-query-handler.tsx (L30-74)
export const MapQueryHandler: React.FC<MapQueryHandlerProps> = ({ toolOutput }) => {
const { setMapData } = useMapData();
useEffect(() => {
if (toolOutput && toolOutput.mcp_response && toolOutput.mcp_response.location) {
const { latitude, longitude, place_name } = toolOutput.mcp_response.location;
if (typeof latitude === 'number' && typeof longitude === 'number') {
console.log(`MapQueryHandler: Received data from geospatialTool. Place: ${place_name}, Lat: ${latitude}, Lng: ${longitude}`);
setMapData(prevData => ({
...prevData,
// Ensure coordinates are in [lng, lat] format for MapboxGL
targetPosition: [longitude, latitude],
// Optionally store more info from mcp_response if needed by MapboxMap component later
mapFeature: {
place_name,
// Potentially add mapUrl or other details from toolOutput.mcp_response
mapUrl: toolOutput.mcp_response?.mapUrl
}
}));
} else {
console.warn("MapQueryHandler: Invalid latitude/longitude in toolOutput.mcp_response:", toolOutput.mcp_response.location);
// Clear target position if data is invalid
setMapData(prevData => ({
...prevData,
targetPosition: null,
mapFeature: null
}));
}
} else {
// This case handles when toolOutput or its critical parts are missing.
// Depending on requirements, could fall back to originalUserInput and useMCPMapClient,
// or simply log that no valid data was provided from the tool.
// For this subtask, we primarily focus on using the new toolOutput.
if (toolOutput) { // It exists, but data is not as expected
console.warn("MapQueryHandler: toolOutput provided, but mcp_response or location data is missing.", toolOutput);
}
// If toolOutput is null/undefined, this component might not need to do anything,
// or it's an indication that it shouldn't have been rendered/triggered.
// For now, if no valid toolOutput, we clear map data or leave it as is.
// setMapData(prevData => ({ ...prevData, targetPosition: null, mapFeature: null }));
}
// The dependencies for this useEffect should be based on the props that trigger its logic.
// If originalUserInput and the old MCP client were still used as a fallback, they'd be dependencies.
}, [toolOutput, setMapData]);File: components/map/mapbox-map.tsx (L34-34)
const { mapData, setMapData } = useMapData(); // Consume the new context, get setMapDataFile: app/search/[id]/page.tsx (L44-60)
// Fetch messages for the chat
const dbMessages: DrizzleMessage[] = await getChatMessages(chat.id);
// Transform DrizzleMessages to AIMessages
const initialMessages: AIMessage[] = dbMessages.map((dbMsg): AIMessage => {
return {
id: dbMsg.id,
role: dbMsg.role as AIMessage['role'], // Cast role, ensure AIMessage['role'] includes all dbMsg.role possibilities
content: dbMsg.content,
createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined,
// 'type' and 'name' are not in the basic Drizzle 'messages' schema.
// These would be undefined unless specific logic is added to derive them.
// For instance, if a message with role 'tool' should have a 'name',
// or if some messages have a specific 'type' based on content or other flags.
// This mapping assumes standard user/assistant messages primarily.
};
});
Activity
ngoiyaeric commentedon Jun 1, 2026
@coderabbitai plan
coderabbitai commentedon Jun 1, 2026
Coding Plan
Summary
@mentionautocomplete using the existingsearchUsersaction and integrate into the calendar notepad textareadrawnFeaturespattern for AI context injection by fetching notes server-side and embedding them in agent system promptsDesign Choices
Design Choice 1: How should calendar notes be injected into AI context?
Options Considered:
role: 'data'messages from AIState and extract calendar notesChosen Option: 3
Rationale: Fetch notes directly from database in submit action — this ensures freshness, avoids formData complexity, and the
getNotesaction already exists.💡 User Tips
Regenerate the plan with different choices with
@coderabbitai <feedback>.Implementation Steps
Phase 1: Wire Calendar Toggle Buttons
Connect the existing CalendarDays icon buttons in the header and mobile navigation to the CalendarToggleContext, enabling users to open/close the calendar notepad panel.
Task 1: Update CalendarToggleContext API
Enhance the toggle context to expose a
closeCalendarmethod for consistency with other toggle contexts (UsageToggleContext pattern).components/calendar-toggle-context.tsxto addcloseCalendar: () => voidto the context typecloseCalendaras() => setIsCalendarOpen(false)wrapped instartTransitionisCalendarOpenandtoggleCalendarTask 2: Wire Header Calendar Button
Connect the desktop header's CalendarDays button to the toggle context.
components/header.tsxto importuseCalendarTogglehookonClick={toggleCalendar}handler to the CalendarDays<Button>elementTask 3: Wire Mobile Icons Bar Calendar Button
Connect the mobile navigation's CalendarDays button to the toggle context.
components/mobile-icons-bar.tsxto importuseCalendarTogglehookonClick={toggleCalendar}handler to the CalendarDays<Button>elementhandleUsageTogglepattern🤖 Prompt for AI agents
Phase 2: User Tagging UI
Implement
@mentionautocomplete functionality allowing users to tag collaborators in calendar notes, populating the existinguserTagsfield.Task 1: Create User Mention Autocomplete Component
Build a reusable mention autocomplete dropdown that triggers on @ symbol.
components/user-mention-input.tsxas a controlled textarea wrappersearchUsersaction fromlib/actions/users.tsfor user lookup (debounced)@user-emailinto textarea content and collecting user IDTask 2: Integrate Mention Input into Calendar Notepad
Replace the plain textarea with the mention-enabled input component.
components/calendar-notepad.tsxto use the new mention input componentuserTagsarray to thesaveNotecall instead of hardcodednullTask 3: Render User Tags on Existing Notes
Display tagged collaborators when viewing saved notes.
components/calendar-notepad.tsxto showuserTags🤖 Prompt for AI agents
Phase 3: AI Context Integration
Inject calendar notes into the AI agent's system prompt so the language model has access to time-sensitive context during queries.
Task 1: Fetch Calendar Notes in Submit Action
Load relevant calendar notes when processing a user query.
app/actions.tsxsubmit action to importgetNotesfromlib/actions/calendarchatIdfrom AIState, callgetNotesto fetch recent notes for the chatTask 2: Inject Notes into Researcher System Prompt
Pass calendar notes to the researcher agent for inclusion in the system prompt.
researcherfunction signature inlib/agents/researcher.tsxto accept optionalcalendarNotesparametergetDefaultSystemPromptto include a calendar notes section when notes are presentTask 3: Handle Calendar Notes in Resolution Search Agent
Ensure calendar context is available to the resolution search agent.
lib/agents/resolution-search.tsxto accept and format calendar notes in its system prompt🤖 Prompt for AI agents
Research
The calendar notepad system is partially implemented. A
CalendarNotepadcomponent exists with week-based date navigation, note creation, and location tagging via MapPin. TheCalendarToggleContextprovidesisCalendarOpenandtoggleCalendar()state management. The database schema (calendarNotestable) and server actions (getNotes,saveNote) are complete, including AI context message creation for chat-linked notes. Key gaps: header/mobile toggle buttons are not wired, user tagging UI is missing (field is alwaysnull), and calendar notes are not injected into the AI system prompt for agent consumption.🚀 Next Steps
🤖 All AI agent prompts combined
💡 Iterate on the plan with:
`@coderabbitai` <feedback>💬 Have feedback or questions? Drop into our discord!