Research Platform – Complete Documentation
Version 1.0.0 – A full‑featured, desktop‑style research collaboration environment with real‑time messaging, collaborative document editing, AI‑assisted research tools, and an extensible plugin/app store.
📋 Table of Contents
- Overview
- Key Features
- Architecture
- Getting Started
- Project Structure
- Core Components
- Feature Modules
- App Store & Plugin System
- API Integration
- Security & Permissions
- Performance & Optimization
- Deployment
- Contributing & Developer Guide
- Troubleshooting
- Future Roadmap
Overview
The Research Platform is a full‑stack, desktop‑style web application designed for researchers to collaborate, publish, manage lab work, and extend functionality through custom plugins and apps. It combines real‑time messaging, collaborative editing (Yjs), a knowledge graph for research gaps, grant assistance, peer review, lab inventory, and an App Store where users can create and share lightweight HTML/JS/CSS tools.
The frontend is built with React 19, TypeScript, Vite, and Tailwind CSS, featuring a window manager (draggable, resizable windows with taskbar and start menu) that mimics a desktop operating system. The backend (Java Spring Boot) provides REST APIs and WebSocket for real‑time features.
Key Features
| Category | Features |
|---|---|
| Authentication & User Management | Login, registration, 2FA (TOTP), password reset with security questions, profile editing, follow system |
| Articles & Publications | Create, edit, publish, search, like, comment, analytics (views/likes/comments), citation export (APA/MLA/BibTeX), graph‑based recommendations |
| Messaging | Private & group chats, threads, polls, voice messages, stickers, scheduled messages, global search, read receipts, typing indicators, secret chats (E2EE stubbed) |
| Collaborative Editor | Real‑time document editing with Yjs, LaTeX math, code blocks, tables, task lists, live preview, citation picker, export (PDF/DOCX/LaTeX/HTML) |
| Paper Collections | Organise articles into custom collections (public/private) |
| Reference Manager | Import references from DOI, BibTeX, Zotero, Mendeley; insert citations into editor |
| Preprint Submission | Upload manuscript, AI screening, DOI assignment |
| Provenance / Trust | Audit trail of actions, chain verification (integrity check) |
| Unified Inbox | Aggregated messages from all sources (private, groups, projects, AI, broadcasts) with filters |
| Lab Inventory | Manage chemicals, barcode scanning, low‑stock alerts, NFPA diamond, compatibility checker |
| Peer Review | Submit papers, claim reviews, anonymous review with scores, reviewer credentials |
| Gap Analysis | Knowledge graph (ForceGraph2D) of papers/concepts/gaps, AI‑powered gap detection |
| Grant Assistant | Deadlines, RFP analysis, draft generation (AI) |
| Data Hub | Research objects, lab protocols, preregistrations with DOI |
| Projects & Tasks | Kanban board, members, outputs |
| Events & Networking | Create events, RSVP, calendar export (iCal) |
| Workspace (Desktop) | Window manager with taskbar, start menu, resizable windows, desktop icons, keyboard shortcuts |
| Voice Assistant | Speech recognition, TTS, wake word (stub) |
| Smart Notifications | Real‑time bell, batching, quiet hours, deep work mode |
| Admin Dashboard | Manage users, articles, RAG documents, platform stats |
| Offline Support | IndexedDB persistence for collaborative editor, PWA (service worker) |
| App Store / Plugin System | Users can create, share, install HTML/CSS/JS apps that run in sandboxed iframes; visual no‑code builder (planned) |
Architecture
The platform follows a modular monolith backend and a feature‑based frontend with a desktop window manager.
High‑level Diagram
[Browser] ←→ [Frontend (React + Vite)] ←→ [Backend (Java Spring Boot)]
↕ ↕
[Window Manager] [PostgreSQL]
↕ ↕
[Sandboxed iframe] [WebSocket]
(plugins/apps)
Frontend Technologies
| Area | Technologies |
|---|---|
| Core | React 19, TypeScript, Vite, React Router DOM |
| Styling | Tailwind CSS, CSS Modules, glass‑morphism |
| State | React Context (auth, theme), custom hooks, Zustand (optional) |
| Real‑time | WebSocket (native), Yjs, y‑websocket, y‑indexeddb |
| Editor | TipTap (ProseMirror), Yjs collaboration, LaTeX (KaTeX) |
| Window Manager | react‑draggable, react‑resizable, framer‑motion |
| Charts | Recharts, D3 |
| Graph | react‑force‑graph‑2d |
| Media | PDF.js, mammoth (DOCX), html2canvas, jspdf |
| Voice | Web Speech API, react‑speech‑recognition |
| PWA | vite‑plugin‑pwa |
Backend (Conceptual – Java Spring Boot)
- REST APIs with JWT authentication
- WebSocket (
/ws) for messaging, typing, notifications - Yjs WebSocket server on port 9093 for collaboration
- PostgreSQL database with JDBC repositories
- File storage (AWS S3 or local) for uploads
- Email service (JavaMail)
- AI integration (OpenAI / local LLM via REST)
Getting Started
Prerequisites
- Node.js 20+ and npm/pnpm
- Java 17+ (for backend)
- PostgreSQL 14+ (or your preferred DB)
- Redis (optional, for caching/rate limiting)
Installation (Frontend)
# Clone the repository
git clone https://github.com/your-org/research-platform.git
cd research-platform/front
# Install dependencies
npm install
# Copy environment variables
cp .env.example .env
# Edit .env with your backend URLs
# Start development server
npm run dev
Environment Variables (.env)
VITE_API_BASE_URL=http://localhost:8080/api
VITE_WS_URL=ws://localhost:8080/ws
VITE_COLLAB_WS_URL=ws://localhost:9093
Backend Quick Start (see separate backend repo)
cd backend
./mvnw spring-boot:run
Run with Docker Compose (both frontend + backend + DB)
# docker-compose.yml (provided in backend repo)
Project Structure
src/
├── api/ # API client modules (REST calls)
│ ├── client.ts # Axios instance with JWT refresh
│ ├── auth.ts
│ ├── articles.ts
│ ├── messages.ts
│ ├── users.ts
│ ├── collections.ts
│ ├── references.ts
│ ├── preprints.ts
│ ├── provenance.ts
│ ├── rag.ts
│ ├── admin.ts
│ ├── plugins.ts # App store API
│ └── index.ts
├── components/
│ ├── ui/ # Reusable UI primitives
│ │ ├── GlassCard.tsx
│ │ ├── Skeleton.tsx
│ │ ├── LoadingSkeleton.tsx
│ │ ├── AnimatedBackground.tsx
│ │ └── ParticleBackground.tsx
│ ├── desktop/ # Window manager
│ │ ├── WindowContainer.tsx
│ │ ├── DesktopWindow.tsx
│ │ ├── Taskbar.tsx
│ │ ├── StatusBar.tsx
│ │ └── index.ts
│ ├── workspace/
│ │ ├── SmartGlassWorkspace.tsx
│ │ └── PanelManager.tsx
│ ├── collaboration/
│ │ └── ScientificEditor.tsx
│ ├── messages/ # Chat components
│ ├── collections/ # Paper collections UI
│ ├── references/ # Reference manager UI
│ ├── preprint/ # Preprint submission UI
│ ├── provenance/ # Provenance timeline
│ ├── unified-chat/ # Inbox filter bar
│ ├── admin/ # Admin tables
│ ├── dashboard/ # Dashboard widgets
│ ├── article/ # Article card, markdown renderer
│ ├── voice/ # Voice assistant widget
│ ├── notifications/ # Notification center
│ ├── app-store/ # App store & plugin components
│ │ ├── AppCard.tsx
│ │ ├── AppEditor.tsx
│ │ ├── AppRunner.tsx
│ │ └── PluginStorePage.tsx
│ └── ...
├── pages/ # Route pages (25+)
├── hooks/ # Custom hooks (50+)
├── lib/ # Core libraries (auth, api, websocket, secret chat)
├── services/ # Business logic services
├── security/ # JWT, CSRF, sanitize, rate limiting
├── audit/ # Audit logger
├── utils/ # File, date, string, error helpers
├── config/ # Constants, env helpers
├── seo/ # Site metadata
├── workers/ # Web workers (RAG)
├── styles/ # Global CSS (glass, animations)
├── types/ # TypeScript re‑exports
├── App.tsx
├── main.tsx
├── vite-env.d.ts
└── index.css
Core Components
Desktop Window Manager
WindowContainer: Global state (open/close/minimize/maximize/z‑index). ExposesuseWindowManagerhook.DesktopWindow: Draggable, resizable window with title bar, icon, and control buttons.Taskbar: Start menu, open window icons, system tray (clock).StatusBar: Top bar with Wi‑Fi, battery, volume (mock).
Usage:
const { openWindow } = useWindowManager();
openWindow('My App', <MyComponent />, { width: 800, height: 600 });
Collaborative Editor (ScientificEditor)
- Built on TipTap + Yjs.
- Real‑time collaboration with peers (cursors, presence).
- LaTeX math (KaTeX), code highlighting, tables, task lists.
- Live preview (HTML with rendered math).
- Export to PDF, DOCX, LaTeX, HTML.
- Citation picker (integrates with Reference Manager).
- Offline support via IndexedDB.
Messaging (ChatArea, MessageBubble)
- Private & group chats.
- Attachments (images, videos, files).
- Reactions (emojis).
- Reply to messages, threads.
- Edit/delete/forward (single or multi‑select).
- Voice messages, stickers, polls.
- Scheduled messages.
- Global search (
Ctrl+K).
Window Manager Events
openWindow(custom event) – launch any app from anywhere.closeWindow– close current window.- Desktop icons for installed apps.
Feature Modules
Paper Collections
- Pages:
CollectionsPage,CollectionDetailPage - Modals:
CreateCollectionModal,AddToCollectionModal - Hook:
usePaperCollections - Backend:
/api/collections/*
Reference Manager
- Pages:
ReferenceManagerPage - Modals:
ReferenceImportModal(DOI, BibTeX, Zotero, Mendeley) - Picker:
ReferencePicker(inserts\cite{key}into editor) - Hook:
useReferenceManager
Preprint Submission
- Page:
PreprintSubmissionPage - Component:
ScreeningResultCard - Hook:
usePreprintSubmission - Backend:
/api/preprints/screen,/api/preprints/submit
Provenance (Trust)
- Components:
ProvenanceTimeline,VerifyChainButton - Hook:
useProvenance - Integration: Button in
ArticleDetailPageopens modal.
Unified Inbox
- Page:
UnifiedInboxPage - Filter bar:
InboxFilterBar(tabs: all, unread, critical, direct, groups, projects, papers, AI, broadcasts) - Hook:
useUnifiedInbox - Backend:
/api/unified-inbox
Scheduled Messages
- Modal:
ScheduleModal(datetime picker) - Panel:
ScheduledMessagesPanel(list, cancel) - Integration: Calendar button in
ChatArea, clock icon inMessagesPage.
Global Search Modal
- Component:
GlobalSearchModal - Keyboard shortcut:
Ctrl+K - Backend:
/api/conversations/search,/api/groups/{id}/search - Integration: Button in
MessagesPage.
Admin Dashboard
- Page:
AdminPage - Tables:
UserTable,ArticleTable,RagDocTable - Hooks:
useAdminStats - Backend:
/api/admin/*
Offline Support
- Collaborative editor:
IndexeddbPersistencefromy‑indexeddb - PWA:
vite-plugin-pwa, service worker registration. - Status:
OfflineSyncStatuscomponent.
App Store & Plugin System
Users can build, share, and install HTML/CSS/JS applications that run inside the desktop environment.
How It Works
- Developer writes a self‑contained HTML file (HTML, CSS, JS).
- Uploads to the platform via the App Editor (simple textarea or file upload).
- Admin approves (or auto‑approve for trusted users).
- Other users browse the App Store, click “Install”.
- Installed apps appear as desktop icons.
- Launch opens a new window with a sandboxed
iframethat runs the HTML code.
Security
- Each app runs in an
<iframe>withsandbox="allow-same-origin allow-scripts allow-popups allow-forms". - No access to parent page DOM, cookies, or localStorage.
- API calls must go through
postMessage(optional – can be extended).
Creating an App
Minimal example:
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>My Tool</title></head>
<body>
<h1>Hello Research!</h1>
<button onclick="fetch('/api/articles/trending').then(r=>r.json()).then(console.log)">Fetch</button>
</body>
</html>
Upload via App Editor:
- Name, description, icon (optional), HTML code.
Visual No‑Code Builder (Planned)
- Drag‑and‑drop UI builder.
- Property panels for each component.
- Visual workflow editor (node‑graph) for logic.
- Generates JSON blueprint → backend → code → deployment.
API Integration
All frontend API calls are organized in src/api/. Example:
import { articleApi } from '@/api/articles';
const articles = await articleApi.getAll({ page: 1, limit: 20 });
WebSocket Events
| Event Type | Description |
|---|---|
new_message |
New private message |
new_group_message |
New group message |
typing |
User is typing |
message_status |
Delivered/seen status |
reaction |
Emoji reaction added/removed |
message_edited |
Message content edited |
message_deleted |
Message soft‑deleted |
message_pinned |
Pin/unpin group message |
new_notification |
Real‑time bell notification |
presence |
Online/offline status |
Security & Permissions
| Layer | Measures |
|---|---|
| Authentication | JWT (access + refresh tokens), httpOnly cookie for refresh |
| 2FA | TOTP (Google Authenticator), backup codes |
| CSRF | Token in meta tag, verified on state‑changing requests |
| Rate Limiting | Token bucket (client‑side stub) – backend recommended |
| Sanitization | DOMPurify for user‑generated HTML |
| Sandbox | iframe sandbox for third‑party apps |
| Audit Log | All critical actions logged (auditLogger.ts) |
Performance & Optimization
- Lazy loading: All pages and heavy components are lazy‑loaded (
React.lazy). - Memoization:
useMemo,useCallback,React.memofor expensive renderers. - Virtualization: Planned for long lists (messages, articles) –
react‑virtuoso. - Asset caching: Service worker caches static assets (PWA).
- Code splitting: Vite automatically splits chunks.
- Image lazy loading:
loading="lazy"on images.
Deployment
Build for Production
npm run build
# Output: dist/ folder
Serve with Nginx (example)
server {
listen 80;
server_name research.yourdomain.com;
root /var/www/research-platform/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
}
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Dockerfile
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Environment Variables for Production
VITE_API_BASE_URL=https://api.research.yourdomain.comVITE_WS_URL=wss://api.research.yourdomain.com/wsVITE_COLLAB_WS_URL=wss://collab.research.yourdomain.com
Contributing & Developer Guide
Adding a New Feature
- Create feature branch from
main. - Implement using the existing patterns:
- API calls in
src/api/ - Hook in
src/hooks/ - Components in
src/components/features/ - Page in
src/pages/ - Add route in
App.tsx
- API calls in
- Write tests (Jest + React Testing Library).
- Submit pull request.
Code Style
- ESLint (React Hooks, TypeScript)
- Prettier
- Tailwind CSS for styling
Building a New Desktop App
- Add entry to
DESKTOP_APPSinSmartGlassWorkspace.tsx. - Provide component (or page).
- If the app needs to be installed, use the App Store mechanism.
Troubleshooting
Common Issues
| Issue | Likely Cause | Solution |
|---|---|---|
| WebSocket not connecting | Wrong VITE_WS_URL |
Check .env, ensure backend WebSocket is running |
| Collaborative editor not syncing | Yjs WebSocket port 9093 not running | Start y-websocket server |
| PWA not installing | Missing icons or wrong manifest | Add icon-192.png, icon-512.png to public/ |
| iframe sandbox blocks scripts | Missing allow-scripts |
Add allow-scripts to sandbox attribute |
| Messages not loading | AbortController race condition | Fetch aborts properly; ensure loadMessagesForSelected is memoized |
Debugging
- React DevTools: Inspect component tree, state, props.
- Network tab: Check API calls, WebSocket frames.
- Console: Look for errors in plugin code (if any).
- LocalStorage: View persisted token, app data.
Future Roadmap
| Milestone | Description |
|---|---|
| v1.1 | Complete offline queue for messages; extend PWA caching. |
| v1.2 | Visual no‑code app builder (drag‑drop UI, workflow editor). |
| v1.3 | AI agents (autonomous research tasks). |
| v1.4 | Multi‑language support (i18n). |
| v1.5 | Mobile‑optimized view (responsive desktop windows). |
| v2.0 | Full secret chat with Signal protocol (Double Ratchet). |
License
MIT © IE Research Team
Acknowledgments
- Yjs – CRDTs for collaboration
- TipTap – Headless editor framework
- react‑force‑graph‑2d – Knowledge graph visualisation
- Tailwind CSS
- Lucide Icons
- Framer Motion
This documentation is a living document. For the most up‑to‑date information, refer to the source code and the project’s GitHub wiki.
📁 1. Project Structure (Complete Deep Dive)
The src/ directory is organised into logical layers, ensuring separation of concerns, reusability, and scalability.
1.1 api/ – REST Communication Layer
This folder contains modules that encapsulate all backend API calls. Each module corresponds to a backend controller.
| File | Purpose | Key Functions |
|---|---|---|
client.ts |
Axios instance with JWT refresh logic, error handling, CSRF token injection. | Interceptors for adding Authorization header, refreshing token on 401. |
auth.ts |
Authentication endpoints | login, logout, register, refreshToken, getMe, changePassword, forgotPassword, resetPassword, enable2FA, verify2FA, etc. |
articles.ts |
Article CRUD + search + analytics + recommendations | getAll, getById, create, update, delete, like, comment, search, getAnalytics. |
messages.ts |
Private & group messaging | getConversations, getMessages, sendMessage, editMessage, deleteMessage, addReaction, sendTyping, markAsRead, archive, scheduleMessage, getScheduledMessages, group‑specific endpoints. |
users.ts |
User profiles, follow, contacts | getProfile, follow, unfollow, getFollowers, getFollowing, getContacts, uploadAvatar. |
collections.ts |
Paper collections | getAll, create, delete, addPaper, removePaper. |
references.ts |
Reference manager | getForDocument, add, update, delete, importFromDOI, importBibTeX, connectZotero, connectMendeley. |
preprints.ts |
Preprint submission | screen, submit. |
provenance.ts |
Provenance / trust | get, verify. |
rag.ts |
RAG assistant | chat, indexDocument, getDocuments, deleteDocument, getConversation, clearConversation. |
admin.ts |
Admin endpoints | getStats, getUsers, deleteUser, updateUserRole, getArticles, deleteArticle, getRagDocuments, deleteRagDocument. |
plugins.ts |
App store / plugins | getAll, getById, submit, install, getInstalled. |
index.ts |
Barrel export – re‑exports all modules for convenient imports. |
Why this structure?
- Each module is small and focused – easy to test and maintain.
- Changes to one endpoint family don't affect others.
- The barrel file (
index.ts) allows importing likeimport { articleApi } from '@/api'.
1.2 components/ – Reusable UI Building Blocks
The components/ directory is split by domain/feature, plus a ui/ folder for truly generic components.
| Subdirectory | Purpose | Key Files |
|---|---|---|
ui/ |
Highly reusable, style‑only or behaviour‑only components | GlassCard.tsx (frosted glass card with hover animation), Skeleton.tsx (shimmer placeholder), LoadingSkeleton.tsx (multiple variants), AnimatedBackground.tsx (canvas particles), ParticleBackground.tsx (alternative). |
desktop/ |
Window manager (explained in detail below) | WindowContainer.tsx, DesktopWindow.tsx, Taskbar.tsx, StatusBar.tsx, index.ts. |
workspace/ |
Desktop environment integration | SmartGlassWorkspace.tsx (main desktop), PanelManager.tsx (renders panel content inside windows). |
collaboration/ |
Real‑time editor | ScientificEditor.tsx. |
messages/ |
Chat UI | ChatSidebar.tsx, ChatArea.tsx, MessageBubble.tsx, ScheduleModal.tsx, ScheduledMessagesPanel.tsx, GlobalSearchModal.tsx, VoiceMessageRecorder.tsx, etc. |
collections/ |
Paper collections UI | CollectionCard.tsx, CreateCollectionModal.tsx, AddToCollectionModal.tsx. |
references/ |
Reference manager UI | ReferenceImportModal.tsx, ReferencePicker.tsx. |
preprint/ |
Preprint submission UI | ScreeningResultCard.tsx. |
provenance/ |
Trust UI | ProvenanceTimeline.tsx, VerifyChainButton.tsx. |
unified-chat/ |
Inbox filters | InboxFilterBar.tsx, InboxMessageCard.tsx. |
admin/ |
Admin panel tables | UserTable.tsx, ArticleTable.tsx, RagDocTable.tsx. |
dashboard/ |
Dashboard widgets | widgets/StatCard.tsx, ViewsChart.tsx, RecentArticlesList.tsx. |
article/ |
Article display components | ArticleCard.tsx, MarkdownRenderer.tsx. |
voice/ |
Voice assistant UI | VoiceAssistantWidget.tsx, VoiceSettingsModal.tsx, VoiceMessageRecorder.tsx. |
notifications/ |
Notification centre | SmartNotificationCenter.tsx, NotificationItem.tsx. |
app-store/ |
Plugin/app store | AppCard.tsx, AppEditor.tsx, AppRunner.tsx, PluginStorePage.tsx. |
Why this structure?
- Feature folders keep related components close together.
- Shared UI primitives are in
ui/, reducing duplication. - Desktop‑specific code is isolated in
desktop/andworkspace/.
1.3 pages/ – Route Pages
Each file corresponds to a route (lazy‑loaded). Examples:
| File | Route | Description |
|---|---|---|
HomePage.tsx |
/ |
Landing page with animated background, call to action. |
LoginPage.tsx |
/login |
Glass‑form login with 2FA support. |
RegisterPage.tsx |
/register |
Registration with password strength meter. |
DashboardPage.tsx |
/dashboard |
Stats cards, charts, recent articles. |
ArticlesPage.tsx |
/articles |
Article list with search/filter, infinite scroll. |
ArticleDetailPage.tsx |
/articles/:id |
Full article view, comments, likes, provenance button. |
MessagesPage.tsx |
/messages |
Unified messaging (both private and groups). |
UnifiedInboxPage.tsx |
/inbox |
Aggregated inbox with filter bar. |
CollectionsPage.tsx |
/collections |
List of paper collections. |
CollectionDetailPage.tsx |
/collections/:id |
Single collection with papers. |
ReferenceManagerPage.tsx |
/references/:docId |
Manage references for a document. |
PreprintSubmissionPage.tsx |
/submit-preprint |
Submit preprint with AI screening. |
LikedArticlesPage.tsx |
/liked-articles |
Articles liked by the user. |
AdminPage.tsx |
/admin |
Admin dashboard with user/article/RAG tables. |
SettingsPage.tsx |
/settings |
User settings (profile, security, notifications, appearance). |
ProfilePage.tsx |
/profile |
View own profile. |
GroupsPage.tsx |
/groups |
List of research groups. |
NotFound.tsx |
* |
404 page. |
Why lazy loading?
- Reduces initial bundle size.
- Pages are loaded only when the user navigates to them.
1.4 hooks/ – Custom Hooks
Over 50 custom hooks encapsulating business logic, data fetching, and state management. Examples:
| Hook | Purpose |
|---|---|
useAuth |
Authentication state, login, logout, token refresh. |
useArticles |
Fetch articles with pagination, search, filter. |
useMessaging |
Chat state, send messages, reactions, threads, scheduling, archive. |
usePaperCollections |
Manage user collections. |
useReferenceManager |
Import, delete, insert citations. |
usePreprintSubmission |
Screen and submit preprints. |
useProvenance |
Fetch events and verify integrity. |
useUnifiedInbox |
Aggregated messages from all sources. |
useCollaborativeEditor |
Yjs document, WebSocket, offline persistence, export. |
useSecuritySettings |
2FA, password change, security question. |
useRegister |
Registration form with password strength. |
usePasswordReset |
Security question based password reset. |
useProfile |
Fetch and update user profile. |
useSearch |
Search articles with URL parameters. |
useWebSocket |
WebSocket connection management. |
useVoiceAssistant |
Speech recognition, TTS, wake word. |
useRagSystem |
Document indexing, AI chat, source citations. |
useNotifications |
Fetch, mark read, delete notifications. |
useSmartNotifications |
Batching, quiet hours, deep work mode. |
useDragDropOverlay |
Detect file drag over window. |
useLocalNotificationClassifier |
AI priority prediction (stub). |
useNetworkQuality |
Detect effective network type (4G, 3G, etc.). |
useProjectSpace |
Project details, members, outputs. |
useProjectTaskManager |
Kanban board tasks. |
useLabInventory |
Chemicals, alerts, barcode scanning. |
usePeerReview |
Submissions, reviews, credentials. |
useGapAnalysis |
Knowledge graph nodes/edges, AI gap detection. |
useGrantAssistant |
Grant deadlines, RFP analysis. |
useEventHosting |
Events, RSVPs, reminders. |
useWorkspace / useSmartGlassWorkspace |
Workspace panels, presets. |
useDataHub |
Research objects, protocols, preregistrations. |
Why custom hooks?
- Business logic is separated from UI components → easier testing.
- Logic can be reused across multiple components.
- Hooks can be composed to build complex features.
1.5 lib/ – Core Libraries
Low‑level utilities that are used throughout the app.
| File | Purpose |
|---|---|
api.ts |
Re‑export of api/client (backward compatibility). |
auth.tsx |
React context for authentication (AuthProvider, useAuth). |
useWebSocket.ts |
Custom hook for WebSocket management (reconnection, message handler). |
useSecretChat.ts |
E2EE stub (Signal protocol placeholder). |
types/ |
All TypeScript interfaces and types (mirrors backend DTOs). |
1.6 services/, security/, audit/, utils/, config/, seo/, workers/, styles/, types/
These directories provide cross‑cutting concerns:
| Directory | Purpose | Example Files |
|---|---|---|
services/ |
Complex business logic (export, notifications, search, RAG) | exportService.ts, notificationService.ts, ragService.ts, searchService.ts. |
security/ |
Client‑side security helpers | jwt.ts (decode/verify), csrf.ts (get token), rateLimit.ts (client‑side limiter), sanitize.ts (DOMPurify wrapper). |
audit/ |
Audit logging (sends events to backend) | auditLogger.ts. |
utils/ |
Pure helper functions | fileUtils.ts (file size, type, dataURL), dateUtils.ts (relative time, formatting), stringUtils.ts (truncate, slugify), errorHandler.ts, localStorage.ts (typed wrapper). |
config/ |
Environment‑aware constants | constants.ts (API base, WS URL, pagination size), env.ts (isDev, isProd). |
seo/ |
SEO metadata | metadata.ts (site title, description, social tags). |
workers/ |
Web Workers for heavy tasks | rag-worker.ts (PDF/DOCX parsing, embedding generation). |
styles/ |
Global CSS | globals.css (glassmorphism, keyframes, scrollbar, dark mode). |
types/ |
TypeScript type re‑exports | index.ts (re‑exports all types from lib/types/). |
🖥️ 2. Core Components – Desktop Window Manager
The window manager is the heart of the desktop environment. It allows users to open multiple applications (panels) as independent windows that can be moved, resized, minimised, maximised, and closed – similar to Windows or macOS.
2.1 WindowContainer.tsx – Global State Provider
This component provides the global state and actions for all windows using React Context.
State (managed with useState):
windows: DesktopWindow[]– array of all open windows.activeWindowId: string | null– the window that currently has focus (highest z‑index).nextZIndex: number– increments every time a window is brought to front, ensuring proper stacking order.
Actions (exposed via useWindowManager hook):
openWindow(title, content, options): creates a new window with a unique ID, default position (staggered), default size (800×600), and appends to the list.closeWindow(id): removes window from state.minimizeWindow(id): setsisMinimized: true– window disappears from screen but remains in taskbar.maximizeWindow(id): setsisMaximized: true– window expands to full screen (with backdrop overlay).restoreWindow(id): returns from minimized or maximized state.bringToFront(id): updates z‑index of the window to the highest value (nextZIndex++).updateWindow(id, updates): partial update (position, size, etc.).
How it works:
- The provider wraps the entire workspace content.
- Any component inside can call
useWindowManager()to get state and actions. - Window positions are stored in the state and can be persisted to
localStorage(viapersistmiddleware – optional).
Example usage:
const { openWindow } = useWindowManager();
openWindow('My App', <MyComponent />, { width: 600, height: 400 });
2.2 DesktopWindow.tsx – Individual Draggable/Resizable Window
This component renders a single window. It uses react‑draggable and react‑resizable to provide dragging and resizing.
Props:
windowId: string– the ID of the window (used to retrieve the window object from context).
Internal state:
position(x, y) – local copy, updated while dragging, then persisted to global state on stop.size(width, height) – local copy, updated while resizing, then persisted on stop.
Rendering logic:
- If
window.isMinimized→ returnnull(window hidden). - If
window.isMaximized:- Render a full‑screen overlay with a backdrop blur.
- Inside, a full‑size
GlassCardwith the window content. - Clicking the backdrop restores the window.
- Else (normal state):
- Wrap the window in a
Draggablecomponent (drag handle =.window-drag-handle). - Inside, a
Resizablecomponent (handles on edges). - The window has a header (
.window-drag-handle) with title, icon, and control buttons (minimize, maximize, close). - Clicking the header (or anywhere) calls
bringToFront.
- Wrap the window in a
Drag and resize details:
react‑draggableuses the node reference to track mouse movement.onStopcallback updates the global position.react‑resizableprovides handles at edges and corners.onResizeupdates local size;onResizeStopupdates global size.
Styling:
- The window uses the
glass-cardclass (backdrop blur, border, shadow). - Z‑index is set directly from the global state (
style={{ zIndex: window.zIndex }}).
2.3 Taskbar.tsx – Bottom Bar with Start Menu and Open Windows
The taskbar provides:
- Start button (Home icon) – opens a menu listing all available desktop apps.
- Open window buttons – each open (non‑minimized) window has a button in the taskbar. Clicking it brings that window to front (or restores if minimized).
- System tray – clock (updates every second) and mock icons for Wi‑Fi, battery, volume.
State:
showStartMenu(boolean) – controls visibility of the start menu modal.currentTime(Date) – updated every second withsetInterval.
Start menu:
- Fetches the list of apps from the parent (
appsprop). - Each app button triggers the provided
action()(which callsopenWindow). - Modal animates with
framer‑motion.
Open window buttons:
- Derived from the
windowsarray (fromuseWindowManager). - Only non‑minimized windows are shown (though minimized can be shown with a special icon – could be added).
- Active window (the one with highest z‑index) is highlighted with
bg-primary/20.
Clock:
- Uses
toLocaleTimeStringto format time. - Updates every second via
setInterval(cleaned up on unmount).
2.4 StatusBar.tsx – Optional Top Bar
A simple top bar showing:
- App name / version (left).
- Mock system icons (Wi‑Fi, volume, shield, battery).
- No functional logic – just visual polish.
2.5 index.ts – Barrel Export
Exports all desktop components and hooks for convenient imports:
export * from './WindowContainer';
export * from './DesktopWindow';
export * from './Taskbar';
export * from './StatusBar';
🔄 3. How the Window Manager Works Together
Integration in SmartGlassWorkspace.tsx:
- The
WindowContainerprovider wraps the entire content. DESKTOP_APPSdefines all available applications (static).WorkspaceContentcomponent:- Uses
useWindowManagerto accessopenWindow,windows, etc. - Renders desktop icons (from
DESKTOP_APPSand installed apps fromuseAppStore). - Renders all windows (mapping
windowsto<DesktopWindow key={id} windowId={id} />). - Renders
<Taskbar />with the list of apps.
- Uses
- When a user clicks an icon:
launchAppchecks if the window already exists (by title). If yes, brings it to front; otherwise callsopenWindow.
- Windows are draggable, resizable, minimizable, etc. All state is kept in
WindowContainer.
Keyboard shortcuts (can be added via useEffect listening to keydown events):
Ctrl+W– close active window.Ctrl+M– minimize active window.Ctrl+Shift+M– maximize/restore active window.Ctrl+K– open global search modal (already implemented inMessagesPage).
🧩 4. Example Flow: Opening a New App
- User clicks on "Messages" desktop icon.
DESKTOP_APPSentry formessagesprovides component<MessagesPage />.launchApp('Messages', ...)callsopenWindow('Messages', <MessagesPage />, { icon: <MessageSquare />, size: { width: 900, height: 650 } }).openWindowcreates a new window object, adds towindowsarray, setsactiveWindowIdto this new window, and incrementsnextZIndex.- The window is rendered by
<DesktopWindow windowId={newId} />. - The window appears on screen at the calculated position (staggered). User can drag, resize, etc.
- The taskbar shows a new button for "Messages".
- Clicking the taskbar button brings the window to front (if it was behind) or restores it if minimised.
📦 5. Adding a New Desktop App
To add a new app, simply:
- Create a component (or page) that will be the window content.
- Add an entry to
DESKTOP_APPSinSmartGlassWorkspace.tsx:{ id: 'my-app', name: 'My App', icon: <MyIcon />, component: <MyComponent />, defaultSize: { width: 800, height: 600 } } - Optionally, add an icon to the desktop by including it in the
Desktop Iconssection. - The app will automatically appear in the Start Menu (from
Taskbar). - Users can open it, and it will behave like any other window.
For installable apps (via App Store), the process is similar but the app definition comes from the backend, and the icon is dynamically added to the desktop after installation.
🎯 6. Why This Window Manager is Powerful
- Truly desktop‑like: Users can arrange their workspace exactly as they like.
- Multi‑tasking: Open many windows simultaneously, switch via taskbar.
- Extensible: Adding new apps requires only a few lines of code.
- Persistent layout (optional): You can save window positions/sizes to
localStorageand restore on reload. - Accessible: Keyboard shortcuts can be added easily.
- Responsive: Windows are constrained to the viewport (bounds set in
Draggable). - Performance: Windows are only rendered when open; closed windows are removed from DOM.
✅ Conclusion
The Project Structure is modular, feature‑based, and follows best practices for a large React application. The Desktop Window Manager provides a full desktop experience with draggable/resizable windows, a taskbar, start menu, and status bar, all styled with glassmorphism and integrated with the rest of the platform.
This architecture allows you to add new research tools, plugins, and even user‑submitted apps with ease, while maintaining a consistent and beautiful UI. 🚀
Feature Modules – Complete Detailed Explanation
Below is an exhaustive, code‑free explanation of every major feature module in the Research Platform. For each module, you will learn: its purpose, the user journey, the components involved, how data flows between frontend and backend, key integration points, and important design decisions.
1. Paper Collections
Purpose: Allow researchers to organise articles into custom collections (e.g., “Deep Learning Papers”, “Grant References”). Collections can be private or public.
User Journey
- The user navigates to the Collections Page (
/collections). - They see a grid of existing collections (if any) and a “+ New Collection” button.
- Clicking “+ New Collection” opens a modal (
CreateCollectionModal) where they enter a title, optional description, and toggle public/private. - After creation, the collection appears in the grid.
- Clicking a collection card opens the Collection Detail Page (
/collections/:id). - On the detail page, they see a list of papers already in the collection (with links to the articles) and a “Remove” button next to each paper.
- To add a paper, the user goes to an article page (or article card) and clicks “Save to collection”. This opens
AddToCollectionModalwhere they select an existing collection or create a new one on the fly. - The paper is added, and the collection’s paper count increments.
Components
CollectionsPage: Main grid view. UsesusePaperCollectionshook to fetch collections. RendersCollectionCardfor each.CollectionDetailPage: Shows papers inside a collection. CallsremovePaperFromCollectionwhen user clicks remove.CollectionCard: Displays collection title, description, paper count, and privacy icon. Click navigates to detail page. Delete button callsdeleteCollection.CreateCollectionModal: Form modal that callscreateCollectionfrom the hook.AddToCollectionModal: Appears on article pages. Lists user’s collections; allows adding or creating a new collection.
Hook: usePaperCollections
- Fetches collections from
/api/collections. - Provides
createCollection,deleteCollection,addPaperToCollection,removePaperFromCollection. - Optimistically updates local state before backend confirmation, rolls back on error.
Backend Endpoints (conceptual)
GET /api/collections– returns all collections for the authenticated user.POST /api/collections– creates a new collection (body:{ title, description, isPublic }).DELETE /api/collections/{id}– deletes a collection.POST /api/collections/{id}/papers– adds a paper (body:{ articleId, title, authors, addedAt }).DELETE /api/collections/{id}/papers/{paperId}– removes a paper.
Integration Points
- Article detail and article card components include a “Save to collection” button that triggers
AddToCollectionModal. - The modal uses the same
usePaperCollectionshook, ensuring consistency.
2. Reference Manager
Purpose: Manage bibliographic references for a collaborative document. Users can import references from DOI, BibTeX, Zotero, or Mendeley, and insert citations (\cite{key}) into the TipTap editor.
User Journey
- The user opens the Reference Manager Page (
/references/:docId) while working on a document (e.g., in the Scientific Editor). - They see a list of references already added to that document, each with title, authors, year, journal, and DOI.
- They can click “Add Reference” to open
ReferenceImportModal. - In the modal, they choose a source:
- DOI: paste a DOI (e.g.,
10.1038/s41586-020-2649-2). The backend fetches citation data and adds it. - BibTeX: paste a BibTeX entry (plain text). The backend parses it.
- Zotero: provide API key and user ID; the backend imports all references from Zotero.
- Mendeley: provide an access token.
- DOI: paste a DOI (e.g.,
- After import, the reference appears in the list.
- While editing the document, the user clicks the “Insert citation” button in the editor toolbar. This opens
ReferencePicker. - In the picker, they search/filter existing references, click one, and the editor inserts
\cite{key}at the cursor. - The user can delete a reference using the delete button (confirmation dialog).
Components
ReferenceManagerPage: Main page listing references. UsesuseReferenceManager. Renders each reference with title, authors, etc. Buttons for import and citation picker.ReferenceImportModal: Tabbed modal for different import sources. Manages its own input fields and calls the appropriate import function from the hook.ReferencePicker: Modal with search input. Fetches references (already loaded). On selection, dispatches a custominsertCitationevent that the editor listens to.
Hook: useReferenceManager
- Takes
docIdas argument. - Fetches references for that document via
GET /documents/{docId}/references. - Provides
importFromDOI,importBibTeX,connectZotero,connectMendeley,removeReference, andrefetch. - After import, automatically updates the list.
Editor Integration
- The
ScientificEditorcomponent listens forinsertCitationcustom event. - When received, it calls
editor.chain().focus().insertContent(text).run(). - The
ReferencePickerdispatches this event withtext = \cite{${ref.key}}.
Backend Endpoints
GET /documents/{docId}/referencesPOST /documents/{docId}/references(for DOI/BibTeX direct)DELETE /documents/{docId}/references/{refId}POST /references/connect-zoteroPOST /references/connect-mendeley
3. Preprint Submission
Purpose: Allow researchers to submit preprints (manuscripts) to the platform. The backend provides AI‑based screening (format, plagiarism, completeness) and assigns a DOI upon successful submission.
User Journey
- User navigates to Preprint Submission Page (
/submit-preprint). - They fill in a form:
- Title (required)
- Abstract (minimum 100 characters)
- Keywords (optional, can be added via input + “Add” button)
- Manuscript file (PDF, DOC, DOCX; max 50 MB)
- Authors (list of names, emails, optional ORCID). Can add multiple.
- Cover letter (optional)
- They click “Screen Submission”. The frontend calls
screenSubmissionwith the form data (excluding the file? Actually, screening may need only metadata; file can be optional for screening, but typical implementation sends metadata only). The backend returns a screening result object:{ passed, issues, suggestions, score }. - The
ScreeningResultCarddisplays the result – issues to fix, suggestions, and a pass/fail score. - If screening passes (or if the user decides to proceed regardless), they click “Submit Preprint”. The frontend sends a multipart form with all data + manuscript file. The backend assigns a DOI, stores the preprint, and returns success.
- After submission, the form resets, and the user sees a success message.
Components
PreprintSubmissionPage: Container for the entire form. Manages local state, validation, and calls the hook.ScreeningResultCard: Pure presentation component that displays screening feedback. Shows score as percentage, lists issues and suggestions, and uses color coding (red/yellow/green) for severity.
Hook: usePreprintSubmission
screenSubmission(data: any): POST to/api/preprints/screen(only metadata). SetsscreeningResultandscreeningloading state.submitPreprint(formData: FormData): POST to/api/preprints/submit(multipart). Handles upload progress viaonUploadProgress(optional).- Returns
screeningResult,submitting,screening,uploadProgress,submittedDoi.
Backend Endpoints
POST /api/preprints/screen– expects JSON:{ title, abstract, keywords, field }. Returns screening results.POST /api/preprints/submit– expectsmultipart/form-datawith fields:title,abstract,keywords,field,authors(JSON string),coverLetter,manuscript(file). Returns{ doi, status }.
4. Provenance (Trust)
Purpose: Provide an auditable trail of actions on an entity (article, user, chemical, project). Users can see who created, edited, viewed, downloaded, or shared the entity, and can verify the integrity of the chain (e.g., cryptographic hash verification).
User Journey
- While viewing an article, the user clicks a “Provenance” button in the action bar.
- A modal opens, containing two parts:
- ProvenanceTimeline: chronological list of events (each with action, user, timestamp, optional details). Events are fetched from
/api/provenance/article/{articleId}. - Verify Chain Button: user clicks it, the frontend calls
/api/provenance/article/{articleId}/verify. The backend checks the integrity (e.g., hash chain) and returns{ valid: true/false }. A toast notifies the user.
- ProvenanceTimeline: chronological list of events (each with action, user, timestamp, optional details). Events are fetched from
- The user can close the modal.
Components
ProvenanceTimeline: TakesentityIdandentityType. Fetches events usinguseProvenance. Renders a timeline with icons, action names, users, and timestamps.VerifyChainButton: UsesuseProvenanceto callverifyChain(). Shows loading state, then a toast with result. Also updates a localcheckedstate to display “Valid”/“Invalid” on the button.
Hook: useProvenance
fetchProvenance: GET/api/provenance/{entityType}/{entityId}.verifyChain: POST/api/provenance/{entityType}/{entityId}/verify.- Returns
events,loading,verifying,chainValid.
Integration
- Added to
ArticleDetailPageas a button that opens a modal (or collapsible panel). The modal importsProvenanceTimelineandVerifyChainButton.
Backend Endpoints
GET /api/provenance/{entityType}/{entityId}– returns list of events (action, user, timestamp, metadata).POST /api/provenance/{entityType}/{entityId}/verify– returns{ valid: boolean }.
5. Unified Inbox
Purpose: Aggregate messages from all sources – private chats, group chats, project discussions, paper Q&A, AI assistant conversations, and broadcast channels – into a single inbox. Users can filter by type, priority, date, and search.
User Journey
- User navigates to Unified Inbox (
/inbox). - At the top, a tab bar (
InboxFilterBar) shows counts for each message origin (All, Unread, Critical, Direct, Groups, Projects, Papers, AI, Broadcasts). - Below the tabs, a search bar filters messages by content, sender, or origin name.
- Next to the search bar, a “Filter” button opens a panel for priority (critical/high/medium/low) and date range.
- Messages are displayed as cards (
InboxMessageCard), each showing origin icon, origin name, sender, message preview, timestamp, and a “read” indicator. - Clicking a message navigates to the original conversation (opens the relevant chat window or page) and marks it as read.
- Each card has a “Forward” button (optional) to forward the message to another chat.
- At the top of the page, a “Mark All Read” button and a “Refresh” button are available.
Components
UnifiedInboxPage: Main container. UsesuseUnifiedInboxfor state.InboxFilterBar: Renders tabs, counts, priority filter, date range. Communicates via callbacks.InboxMessageCard: Displays one message. Contains logic for marking as read on click, and forwarding.
Hook: useUnifiedInbox
- Fetches aggregated messages from
/api/unified-inbox. - Manages filter state (tab, search, priority, date range).
- Provides
markAsRead,markAllRead,forwardMessage,navigateToOrigin, and refetch. - The backend is responsible for combining messages from different sources (private, groups, projects, etc.) into a unified format.
Backend Endpoint
GET /api/unified-inbox– returns an array of messages with fields:id,origin,originName,from,body,sentAt,priority,isRead,link.
Integration Notes
- The inbox does not replace existing dedicated chat pages; it is an additional view.
- Clicking a message navigates to the appropriate chat (using
navigateToOrigin), which uses thelinkfield from the message.
6. Scheduled Messages
Purpose: Allow users to write a message now but schedule it to be sent automatically at a future date/time. Users can also view and cancel scheduled messages.
User Journey (Scheduling)
- In any chat (private or group), the user types a message.
- Instead of pressing “Send”, they click a calendar icon in the input toolbar.
- A
ScheduleModalopens, showing a datetime picker (HTMLdatetime-local). Only future dates/times are allowed. - The user selects a date/time and clicks “Schedule”.
- The frontend calls the appropriate backend endpoint (
/api/conversations/{username}/scheduleor/api/groups/{groupId}/schedule). - A toast confirms the scheduling, and the input field is cleared.
User Journey (Viewing/Cancelling)
- In the main
MessagesPage, there is a clock icon in the header. - Clicking it opens
ScheduledMessagesPanel. - The panel fetches all scheduled messages for the user (
GET /api/messages/scheduled). - Each scheduled message shows: chat name, message preview, scheduled date/time, and a cancel button.
- Clicking cancel sends
DELETE /api/messages/scheduled/{id}and removes the message from the list.
Components
ScheduleModal: Simple modal with a datetime picker. TakesonSchedulecallback.ScheduledMessagesPanel: Modal that lists scheduled messages. Usesapi.getandapi.delete. Optionally acceptsonCancelandonRefreshprops for custom handling.
Integration in ChatArea
- The calendar button is added next to the send button.
- When clicked,
setScheduleModalOpen(true). onSchedulecallback constructs the appropriate API call based onselectedChat.type.- After scheduling, the input is cleared, and the modal closes.
Integration in MessagesPage
- The clock button is added in the top bar (near the search button).
- It opens
ScheduledMessagesPanel. The panel uses the same API endpoints directly, no extra props needed.
Backend Endpoints
POST /api/conversations/{username}/schedule– body:{ body, scheduledAt }.POST /api/groups/{groupId}/schedule– body:{ body, scheduledAt }.GET /api/messages/scheduled– returns array of scheduled messages for the current user.DELETE /api/messages/scheduled/{id}– cancels a scheduled message.
7. Global Search Modal
Purpose: Search across all private and group messages (full‑text) and jump directly to the relevant conversation, optionally scrolling to the specific message.
User Journey
- User presses
Ctrl+K(orCmd+Kon Mac) anywhere in the app, or clicks a search button in theMessagesPage. - A modal (
GlobalSearchModal) opens with a search input field. - As the user types (debounced 400ms), the frontend calls:
/api/conversations/search?q={query}for private messages.- For groups, it calls
/api/groups/{id}/search?q={query}for each group the user is a member of (limited to first 10 groups for performance). Alternatively, a unified group search endpoint can be used.
- Results are grouped by chat (private conversations and groups).
- Each result shows the message preview, sender, and timestamp.
- User clicks a result. The modal closes, and the main chat page opens with that chat selected. If a
messageIdwas provided, the page scrolls to that specific message (usingdocument.getElementByIdandscrollIntoView).
Components
GlobalSearchModal: Handles input, debouncing, calling search endpoints, and rendering grouped results. Uses a local state for results and loading.
Integration in MessagesPage
- A search button (magnifying glass) is added in the header.
- On click,
setShowSearch(true). - The
GlobalSearchModalreceivesgroups(list of user’s groups) andonSelectChatcallback. onSelectChatfinds the chat object inmessaging.chats, callssetSelectedChat, and optionally scrolls to the message.
Backend Endpoints
GET /api/conversations/search?q={query}– returns array of{ with: string, messages: [...] }.GET /api/groups/{groupId}/search?q={query}– returns array of messages (each withid,body,sentAt,from).- Optional:
GET /api/groups/search?q={query}– unified group search (recommended for scalability).
Keyboard Shortcut
- An effect in
MessagesPagelistens forkeydownevents withctrlKey/metaKeyand key'k'. Prevents default and setsshowSearchto true.
8. Admin Dashboard
Purpose: Provide platform administrators with statistics and management interfaces for users, articles, and RAG‑indexed documents.
User Journey
- Admin logs in and navigates to
/admin. - They see a dashboard with:
- Statistics cards (total users, articles, views, etc.) fetched from
/api/admin/stats. - A recent activity feed (if implemented).
- Tabs below the stats: Users, Articles, RAG Documents.
- Statistics cards (total users, articles, views, etc.) fetched from
- Users tab:
- Table listing users (ID, username, email, role, join date).
- Actions: delete user, promote/demote admin role.
- Search input to filter users by username/email.
- Pagination.
- Articles tab:
- Table listing articles (ID, title, author, status, views, creation date).
- Actions: delete article, view article (opens in new tab).
- Filter by status (published/draft/all).
- Search by title or author.
- RAG Documents tab:
- Table listing RAG‑indexed documents (title, file type, source, added date).
- Action: delete document (removes from RAG index).
- Search by title.
Components
AdminPage: Main container, fetches stats, and handles tab switching.UserTable: Fetches users from/api/admin/users(with pagination and search). Uses delete and role update endpoints.ArticleTable: Similar, with article management endpoints.RagDocTable: Fetches from/api/admin/rag/documentsand calls/documents/{docId}/rag-deletefor deletion.
Hooks
useAdminStats– fetches global stats.- Individual tables use direct
apicalls or separate hooks.
Backend Endpoints
GET /api/admin/statsGET /api/admin/users(with pagination, search, role filters)DELETE /api/admin/users/{id}PUT /api/admin/users/{id}/roleGET /api/admin/articles(with pagination, status filter, search)DELETE /api/admin/articles/{id}GET /api/admin/rag/documentsDELETE /documents/{docId}/rag-delete
Security
- Only users with
role = "admin"can access the admin page. The frontend checksuser?.roleand redirects non‑admins to home.
9. Offline Support
Purpose: Allow the collaborative editor to work offline (no internet connection). Changes are saved locally (IndexedDB) and automatically synced when the connection is restored. The application can also be installed as a PWA, caching static assets for offline use.
How It Works (Collaborative Editor)
- When the user opens a document, the Yjs document (
ydoc) is initialised. - Two providers are attached:
WebsocketProvider– synchronises changes with the server when online.IndexeddbPersistence– persists the document to the browser’s IndexedDB.
- When offline, the
WebsocketProviderdisconnects, but the user can still edit. Changes are stored in IndexedDB. - When the connection is restored, the
WebsocketProviderreconnects and automatically syncs all local changes with the server (and other peers). Yjs handles the merging. - An
OfflineSyncStatuscomponent shows a small indicator (green “Online” or red “Offline” with a “Syncing…” spinner) in the header.
PWA (Service Worker)
- The
vite-plugin-pwaplugin generates a service worker. - It caches static assets (HTML, CSS, JS, images) using a cache‑first strategy.
- When offline, the app can still load and show a cached version of the UI (though dynamic data will be stale).
- The service worker is registered in
main.tsxwithregisterSW({ immediate: true }).
Components
OfflineSyncStatus: Usesnavigator.onLineand browser events to show online/offline status.- The collaborative editor hook (
useCollaborativeEditor) already includesIndexeddbPersistenceand exposesisOfflineSyncing.
Integration Points
useCollaborativeEditoris modified to instantiateIndexeddbPersistenceand to setisOfflineSyncingstate.- The
OfflineSyncStatuscomponent is placed inMainLayout.tsx(header). - The PWA manifest and icons are added to
public/.
Backend / Infrastructure
- The collaboration WebSocket server (y‑websocket) must be running.
- The service worker caches static files; dynamic API calls are not cached (except maybe responses if needed).
10. App Store & Plugin System
Purpose: Allow researchers to build, share, and install lightweight HTML/CSS/JS applications that run inside the desktop environment. This turns the platform into an extensible ecosystem.
User Journey for Developers
- Developer opens the App Store window from the desktop.
- They click the “+” button to open
AppEditor. - They fill in:
- App name
- Description
- Icon (optional, base64 image)
- HTML code (full page) – can be typed or uploaded from a
.htmlfile.
- Click “Submit App”. The frontend sends a
FormDatacontaining the metadata and the code toPOST /api/plugins. - The backend stores the plugin in the database with
approved = false(requires manual approval, or can be auto‑approved for trusted users). - An admin approves the plugin (via admin panel or direct DB edit).
- Once approved, the plugin appears in the App Store for all users.
User Journey for End Users
- Other users open the App Store.
- They browse available plugins (grid of
AppCardcomponents). - Each card shows name, description, author, install count, and an “Install” button.
- Click “Install” sends
POST /api/plugins/user/install/{pluginId}. The backend records the installation. - Installed plugins appear as desktop icons dynamically (fetched via
GET /api/user/installedand added to the desktop area inSmartGlassWorkspace). - Clicking the desktop icon launches the app in a new window. The window content is an
AppRunnercomponent that renders a sandboxed iframe with the plugin’s HTML code. - The app runs inside the iframe, isolated from the main platform.
Components
AppStorePage: Main store UI. UsesuseAppStoreto fetch available and installed plugins. ShowsAppCards.AppCard: Displays a single plugin. Has install/uninstall buttons. Shows install count.AppEditor: Form for submitting a new plugin. Has fields for name, description, icon (file input), and code (textarea with optional file upload). SubmitsFormDatatopluginsApi.submit.AppRunner: TakesappIdandcodeas props. Creates an iframe, setssandboxattributes, and writes the HTML code into it. Also sets up apostMessagelistener for secure API calls (optional).PluginStorePage: In the current implementation, the app store is integrated as a desktop app; it can be a separate page or a window.
Hook: useAppStore
- Fetches available and installed plugins.
- Provides
installPlugin,uninstallPlugin,submitPlugin. - Refreshes lists after mutations.
Security
- The iframe sandbox is crucial:
sandbox="allow-same-origin allow-scripts allow-popups allow-forms". - Never add
allow-top-navigationorallow-modalsunless absolutely necessary. - API calls from the plugin to the main platform must go through
postMessage(the plugin sends a message, the parent window makes the actual fetch, then replies). This keeps API tokens secure.
Backend Endpoints
GET /api/plugins– list of approved plugins.GET /api/plugins/{id}– get plugin details (including code).POST /api/plugins– submit a new plugin (multipart).POST /api/plugins/user/install/{pluginId}– record installation.GET /api/plugins/user/installed– list installed plugins for the current user.- Optional:
DELETE /api/plugins/user/uninstall/{pluginId}(not yet implemented).
Desktop Integration
- In
SmartGlassWorkspace.tsx, an additional desktop icon for “App Store” is added. - After fetching installed plugins (using
useAppStore), the desktop area also renders an icon for each installed plugin. When clicked, it callsopenWindowwithAppRunneras the content (the code is fetched from the plugin’scodefield, which is stored in the plugin object). - The same
AppRunnercomponent is used for all plugins.
Visual No‑Code Builder (Planned Future)
- This will be a separate module that generates the plugin HTML via drag‑and‑drop.
- It would output a JSON blueprint, which is then converted to HTML/CSS/JS by a backend code generator.
- The generated plugin can then be submitted to the App Store.
- This is not yet implemented, but the architecture is designed to accommodate it later.
Summary
Each feature module follows a consistent pattern:
- Page component (if standalone) or modal/panel for auxiliary interactions.
- Custom hook that encapsulates data fetching, state management, and business logic.
- API module with typed functions for backend communication.
- Integration with other modules (e.g., ReferencePicker integrates with ScientificEditor, Provenance button integrates with ArticleDetailPage).
- Backend endpoints that follow REST conventions.
The desktop environment (window manager) ties everything together, allowing users to open any feature as a window, rearrange their workspace, and install custom apps.
This architecture ensures the platform is extensible, maintainable, and user‑friendly for researchers.
This detailed technical overview explains the key client‑server communication methods and the frameworks that power your research platform.
🧱 1. High-Level Architecture
Your platform is a full‑stack web application with a clear separation of concerns:
- Frontend (Client) – A React‑based Single Page Application (SPA) that also acts as a desktop environment with its own window manager, taskbar, and start menu. All user interactions happen here.
- Backend (Server) – A Java Spring Boot application that provides REST APIs, WebSocket endpoints, database access, and business logic.
- Collaboration Server – A separate WebSocket server (Yjs) dedicated to real‑time document editing.
The frontend communicates with the backend through two primary channels: REST over HTTP for standard CRUD operations and a WebSocket for real‑time events (new messages, typing indicators, notifications).
📡 2. Client-Server Communication Methods
🔁 2.1 REST APIs (HTTP)
Purpose: All data persistence, user management, articles, messaging, collections, etc.
Frontend Implementation: The entire API layer is centralized in the src/api/ folder. Each module exports typed functions that use an Axios instance (client.ts) with built‑in JWT authentication and automatic token refresh.
// src/api/client.ts – simplified
import axios from 'axios';
const api = axios.create({ baseURL: '/api' });
api.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`;
return config;
});
api.interceptors.response.use(
res => res,
async err => {
if (err.response?.status === 401) {
// Refresh token and retry
}
return Promise.reject(err);
}
);
Backend Implementation: Spring Boot controllers map HTTP requests to Java methods. Example for article CRUD:
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
@GetMapping
public List<Article> getArticles() { ... }
@PostMapping
public Article createArticle(@RequestBody ArticleDto dto) { ... }
@GetMapping("/{id}")
public Article getArticle(@PathVariable Long id) { ... }
}
Why Axios + Spring Boot:
- Axios provides a consistent, promise‑based API, interceptors for authentication/refresh, and automatic JSON transformation.
- Spring Boot offers a mature, annotation‑driven REST framework with built‑in security (Spring Security), data (Spring Data JPA), and dependency injection.
🔌 2.2 WebSocket for Real‑Time Features
Purpose: Live updates for messaging (new messages, typing, read receipts, reactions) and notifications.
Frontend Implementation: A custom useWebSocket hook manages the connection lifecycle.
// Simplified from src/hooks/useWebSocket.ts
export function useWebSocket(onMessage: (data: any) => void) {
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
const ws = new WebSocket(import.meta.env.VITE_WS_URL);
ws.onmessage = (event) => onMessage(JSON.parse(event.data));
wsRef.current = ws;
return () => ws.close();
}, []);
return wsRef;
}
Backend Implementation: Spring Boot’s WebSocket support with STOMP as the sub‑protocol.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
}
Messages are sent to specific destinations (e.g., /app/chat.sendMessage) and broadcast to all connected clients subscribed to a topic (e.g., /topic/messages/{roomId}).
✍️ 2.3 Yjs WebSocket for Collaborative Editing
Purpose: Real‑time, peer‑to‑peer document editing (used in ScientificEditor).
How it works:
- Yjs is a CRDT (Conflict‑free Replicated Data Type) library. It allows multiple users to edit a shared document simultaneously without locking, merging changes automatically.
- The frontend creates a Yjs document (
ydoc), attaches aWebsocketProviderto sync changes via a separate WebSocket server (port9093), and connects aIndexeddbPersistenceprovider for offline storage. - The TipTap editor is extended with the
CollaborationandCollaborationCursorextensions, which use the Yjs document as the source of truth.
Backend / Infrastructure: A standalone y-websocket server (Node.js) or a Java implementation that relays updates between clients.
Advantages:
- No central locking – edits from different users are merged transparently.
- Offline‑first – thanks to the IndexedDB provider, changes made offline are synced automatically when the connection returns.
- Cursor awareness – users see each other’s cursors and selections in real time.
🎨 3. Frontend Frameworks & Key Libraries
⚛️ 3.1 React 19 & TypeScript
React 19 is the core UI library. The app is structured as a Single Page Application (SPA) with React Router for client‑side routing.
TypeScript provides static typing across the entire codebase, from API responses (src/api/*.ts) to component props (src/components/**/*.tsx). This drastically reduces runtime errors and improves developer experience.
🧩 3.2 UI Components & Styling
- Tailwind CSS – a utility‑first CSS framework. All components use Tailwind classes (e.g.,
flex,p-4,rounded-xl). The custom “glassmorphism” effects (glass-card,glass) are defined insrc/index.css. - Lucide React – a consistent, beautifully designed icon set. Used everywhere (e.g.,
MessageSquare,FileText,Plus). - Framer Motion – powers all animations: window opening/closing (scale + fade), taskbar start menu, message pop‑ins, and smooth transitions between routes.
🖥️ 3.3 Desktop Environment (Window Manager)
The desktop experience is implemented with a custom window manager composed of four main components:
WindowContainer(Context Provider) – holds the global state of all open windows (positions, sizes, z‑index, minimized/maximized).DesktopWindow– usesreact-draggablefor dragging andreact-resizablefor resizing. It wraps any content.Taskbar– shows start menu, open window buttons, and the system clock (updated every second).StatusBar– an optional top bar with mock system icons.
Why not a library like react‑grid‑layout?
Because the requirement was to allow free‑form, overlapping windows (like a real desktop), not a rigid grid.
💬 3.4 Messaging & Real‑Time
useWebSockethook – manages a single WebSocket connection for all real‑time features (chat, typing, notifications).- MessageBubble – each message is rendered with reaction buttons, reply preview, edit/delete menu, and status icons. Uses
framer-motionfor hover animations. - GlobalSearchModal – debounced search across private and group messages, with results grouped by chat.
📝 3.5 Collaborative Editor Stack
- TipTap – a headless editor framework built on ProseMirror. It provides ready‑made extensions (bold, italic, lists, tables, code blocks, math) and a plugin system.
- Yjs – the CRDT that powers real‑time collaboration.
- y-websocket – the provider that syncs Yjs documents over WebSocket.
- y-indexeddb – the provider that persists the document locally for offline editing.
- KaTeX – renders LaTeX math inside the editor and in the live preview.
- Lowlight – provides syntax highlighting for code blocks.
- Citation Picker – a custom modal (
ReferencePicker) that inserts\cite{key}into the editor via a custom event (insertCitation).
📊 3.6 Data Visualization & Graphs
- Recharts – used for the
ViewsChartcomponent (line charts of article views over time). - react‑force‑graph‑2d – powers the knowledge graph in
GapAnalysisDashboard. It renders an interactive force‑directed graph of nodes (papers, concepts, gaps) and edges.
🗄️ 3.7 State Management
Most state is local to components or lifted to custom hooks. The only global state is authentication (AuthProvider), theme (ThemeProvider), and the window manager (WindowContainer). This avoids the complexity of Redux while keeping the app predictable.
🧩 3.8 PWA & Offline Support
- vite‑plugin‑pwa – generates the service worker and manifest. The service worker caches static assets (HTML, JS, CSS, images) using a cache‑first strategy.
- IndexedDB – used by
y-indexeddbto store collaborative documents offline. - OfflineSyncStatus – a small component that displays a green/red indicator in the header based on
navigator.onLine.
🧪 3.9 API Communication & Data Fetching
While the app uses direct axios calls inside custom hooks, a future improvement would be to introduce React Query (@tanstack/react-query) to automatically handle caching, background refetching, and request deduplication.
☕ 4. Backend Frameworks (Conceptual)
Although the backend code is not provided in the frontend repository, the architecture is based on industry standards:
- Spring Boot – the core framework for REST APIs, dependency injection, and security.
- Spring Data JPA – for database access (PostgreSQL).
- Spring Security – for JWT authentication, CSRF protection, and role‑based authorisation.
- Spring WebSocket – for real‑time messaging and notifications.
- HikariCP – the connection pool (default in Spring Boot).
- Flyway – for database migrations (optional but recommended).
🔗 5. How Data Flows – Concrete Example
Let’s trace the flow of a new message:
- User types and sends a message in
ChatArea. ChatAreacallsonSendMessage(which comes fromuseMessaging.sendMessage).sendMessageoptimistically adds the message to the localmessagesstate and shows a “sending” status.- It then makes a
POSTrequest to/api/conversations/{username}/messagesusing the API client. - The backend Spring Boot controller receives the request, validates the user, persists the message to the database, and returns the saved entity.
- At the same time, the backend publishes a WebSocket message to
/topic/messages/{roomId}. - The frontend WebSocket handler receives the message and calls
onMessage. onMessageupdates the local state (replacing the optimistic message with the real one) and triggers a re‑render.- The
MessageBubblecomponent shows the delivered/seen status, reactions, etc.
🧠 6. Why This Stack?
| Requirement | Solution | Reason |
|---|---|---|
| Type safety | TypeScript + Spring Boot (Java) | Catches errors early, improves maintainability. |
| Real‑time updates | WebSocket (STOMP) + Yjs | Efficient, low‑latency, supports offline editing. |
| Desktop‑style UI | React + custom window manager + react‑draggable / react‑resizable | Gives users a familiar workspace without rebuilding an entire OS. |
| Rich text editing | TipTap + Yjs | Ready‑made collaboration extensions, great Markdown/LaTeX support. |
| Graph visualisation | react‑force‑graph‑2d | Interactive, scalable, and well‑documented. |
| Animations | Framer Motion | Declarative API, spring physics, easy exit animations. |
| Styling | Tailwind CSS | Rapid development, consistent design, small bundle size (via PurgeCSS). |
| Offline capability | IndexedDB + service worker (PWA) | Essential for researchers working in the field. |
✅ 7. Summary
The Research Platform is a modern, full‑stack web application that seamlessly blends a desktop‑style React frontend with a Java Spring Boot backend. Communication happens through:
- REST APIs for all persistent data (Axios ↔ Spring MVC).
- WebSockets for real‑time messaging and notifications.
- A separate Yjs WebSocket server for collaborative document editing.
The frontend is built with React 19, TypeScript, Vite, and Tailwind CSS, while the backend relies on Spring Boot, Spring Data JPA, and Spring WebSocket. The combination of these technologies provides a secure, extensible, and highly interactive environment for researchers, with a unique desktop‑style user experience and offline capabilities. 🚀
How AI Agents Can Revolutionise the Development & Operation of Your Research Platform
AI agents – autonomous or semi‑autonomous programs powered by large language models (LLMs), decision‑making algorithms, and tool‑use capabilities – can dramatically accelerate the implementation of your research platform, both during development and at runtime. They can write code, generate documentation, debug issues, optimise performance, and even act as user‑friendly “co‑pilot” tools that run inside the platform itself, helping researchers analyse data, draft papers, or manage projects.
Below is a complete explanation of how agents can be integrated into every stage of the platform lifecycle, and how they can execute either on the server (centralised, heavy compute) or on the client’s own machine (privacy‑sensitive, offline‑friendly).
1. What Are AI Agents in This Context?
An AI agent is a software entity that:
- Perceives its environment (via APIs, user input, file system, or sensor data).
- Reasons about a goal (e.g., “write a React component for a new dashboard widget”).
- Acts by generating code, making API calls, editing files, or sending responses.
- Learns from feedback (via reinforcement learning or in‑context examples).
In your platform, agents can be LLM‑based (e.g., GPT‑4, Code Llama, or local models like Ollama) and can be given access to:
- The codebase (source code, documentation, issue tracker).
- The running application (UI state, backend logs, database).
- External tools (terminal, compiler, linter, test runner).
2. Agents in the Development Phase (Writing Code)
2.1 Automated Code Generation for New Features
Instead of manually writing every React component, API endpoint, or database migration, a developer can describe the feature in natural language, and an agent generates the code.
Example: “Create a new desktop app called ‘Literature Review Mapper’. It should have a draggable canvas where users can add paper nodes and connect them with citation edges. Store the graph in IndexedDB for offline use.”
The agent would:
- Analyse your existing code patterns (component structure, state management, use of
GlassCard, window manager integration). - Generate the new component (
LiteratureReviewMapper.tsx) with the necessary imports, state hooks, canvas logic, and IndexedDB integration. - Add the app to
DESKTOP_APPSinSmartGlassWorkspace.tsx. - Optionally create an API endpoint if cloud sync is required.
- Run the linter and TypeScript compiler, automatically fix simple errors.
How it works: The agent is given access to your project’s source tree, a terminal to execute commands, and a browser to preview the result. It iteratively edits files, runs tests, and corrects mistakes.
2.2 Automatic Refactoring & Code Quality
Agents can identify outdated patterns, duplicate code, or performance bottlenecks and propose (or automatically apply) refactors.
- Example: Detect that many components manually call
useEffectfor data fetching and suggest replacing them with React Query. - Example: Find repetitive Tailwind class combinations and create a reusable
GlassCardcomponent (already done). - Example: Uncover missing error boundaries or incorrect key props in lists.
2.3 Bug Detection & Fixing
When a bug is reported (e.g., “the unified inbox filter does not update counts after marking a message as read”), an agent can:
- Replicate the bug by inspecting the frontend state.
- Trace the code path using static analysis or by simulating user actions.
- Propose a fix – often by editing a few lines in
useUnifiedInbox.tsorInboxMessageCard.tsx. - Run the test suite to verify the fix.
2.4 Documentation & Onboarding
Agents can generate comprehensive documentation from the existing codebase:
- Write JSDoc comments for every hook, component, and API function.
- Create a searchable
README.mdthat explains the structure of theapi/,components/, andhooks/folders. - Produce a visual “dependency graph” of modules.
For new developers, an agent can act as an interactive guide: answer questions (“how does the window manager store window state?”), provide code examples, and even set up their local development environment.
3. Agents Inside the Running Platform (User‑Facing)
Once the platform is deployed, AI agents can be embedded as services that researchers can invoke through the desktop environment – for example, as a dedicated “AI Assistant” window or as a plugin.
3.1 Agents Running on the Server (Centralised)
Advantages:
- Can use large, powerful models (GPT‑4, Claude, etc.).
- Central logging, easier to update, shared across all users.
- Can access the full backend database (with proper authorisation).
Use Cases:
- Research Gap Analyzer: An agent analyses the user’s knowledge graph (from
GapAnalysisDashboard), identifies under‑explored connections, and suggests novel hypotheses. It can then automatically create newhypothesisnodes in the graph. - Grant Proposal Writer: Given an RFP text, the agent drafts a full proposal structure, pulls relevant citations from the reference manager, and writes a first draft in the collaborative editor.
- Peer Review Assistant: After a user submits a review, the agent checks for bias, missing criteria, or contradictory statements, and suggests improvements before final submission.
- Automated Literature Summariser: For a list of paper DOIs or a search query, the agent fetches abstracts, reads the full texts (if accessible), and produces a structured summary with key findings and limitations.
- Smart Notification Classifier: The agent examines incoming notifications and decides whether to show them immediately, batch them, or delay them based on the user’s current deep‑work mode.
Integration: These agents run as separate microservices (or as background jobs) that expose REST APIs. The frontend communicates with them via the existing api/ layer. Results appear as messages in the unified inbox, or as new content in the collaborative editor.
3.2 Agents Running on the Client PC (Local)
Advantages:
- Privacy: Sensitive research data never leaves the user’s machine.
- Offline capability: Work without an internet connection.
- Lower latency: No network round‑trip.
- Reduced server load: Heavy compute is offloaded to the user’s hardware.
Constraints: Models must be smaller (e.g., Llama 3‑8B, Phi‑3, Mistral) and may require WebAssembly or local inference engines (Ollama, llama.cpp, or Transformers.js).
Use Cases:
- Local Code Interpreter: A researcher can open an “AI Sandbox” window, write Python or R code, and execute it inside a WebAssembly sandbox (e.g., Pyodide). The agent helps debug the code, suggests libraries, and visualises results – all without sending any data to the server.
- On‑Device Text Summarisation: The user pastes a long PDF excerpt; a small LLM running in the browser summarises it instantly.
- Offline Graph Generator: Using a local model to generate citation graph embeddings for the knowledge graph (gap analysis) – valuable when traveling without internet.
- Personal AI Assistant: The agent has access to the user’s local drafts, notes, and collections. It helps organise notes, tag research objects, and set reminders – all stored only on the user’s machine.
Technical Implementation:
- Use Transformers.js (Hugging Face) to run models directly in the browser via WebAssembly or WebGPU.
- Use Ollama (or llama.cpp) as a local server; the frontend sends prompts via fetch to
http://localhost:11434. - For heavy computations, use Web Workers to keep the UI responsive.
Security: The local agent runs in a sandboxed iframe or a Web Worker with limited file system access (via the File System Access API, with user permission). It cannot access other browser tabs or the main platform’s DOM.
4. How Agents Can Write Code in Any Language
Modern LLMs can generate code in virtually any programming language (C++, Rust, Go, Python, Ruby, Java, etc.). In the context of your platform, this capability can be used for:
4.1 Generating Backend Microservices in Java/Spring
A developer describes: “We need a new microservice that monitors the RAG vector database and re‑indexes documents every night.” The agent creates a complete Spring Boot application with:
@Scheduledcron job.- Connection to the existing PostgreSQL and vector DB.
- Logging and metrics endpoints.
- Dockerfile and Kubernetes deployment YAML.
4.2 Writing Data Science Scripts (Python/R)
Researchers can ask an agent: “Write a Python script using pandas and matplotlib to analyse my lab inventory dataset and predict which chemicals will expire next month.” The agent generates the code, installs dependencies (in a virtual environment), and even runs it on the user’s local machine (with permission).
4.3 Translating Between Languages
If the platform originally had a frontend helper script written in JavaScript, but a user prefers to process data in Python, the agent can transpile the logic while preserving the original semantics.
4.4 Generating SQL Queries
Instead of manually writing complex joins for the analytics dashboard, a researcher describes: “Show me the monthly trend of articles published in the ‘machine learning’ category.” The agent produces the correct PostgreSQL query, optimises indexes, and adds caching hints.
5. A Concrete Example: Agent‑Powered New Feature Development
Goal: Add a “Literature Review” desktop app that allows users to import a set of papers (by DOI) and automatically generate a summarised table (title, authors, abstract, keywords) using an LLM.
Agent‑Assisted Steps:
- The developer opens a “Chat with AI Agent” panel inside the IDE (or a dedicated window).
- They type: “I want to add a new desktop app called
LiteratureReviewTableunder the research tools category. It should have a text area to paste DOIs (one per line), a ‘Fetch’ button, and a table that displays title, authors, abstract, and a ‘Summarise’ button per row. When clicked, that row’s abstract is sent to an LLM (via our existing/ai/rag-chatendpoint) and the summary appears inline. Use the existingGlassCardandAppRunnerpattern. Also add an entry to the desktop icons.” - The agent analyses the existing codebase (
AppRunner.tsx,GlassCard.tsx,DESKTOP_APPSstructure,useAppStorehook). It generates:src/components/app-store/LiteratureReviewTable.tsx(the component).- Updates
DESKTOP_APPSinSmartGlassWorkspace.tsx. - Adds an import for the new component.
- Generates a mock API call (later replaced by backend implementation).
- The agent runs
npm run lintand fixes any formatting issues. - The developer reviews the generated code, manually adjusts the styling, and merges.
- The agent then writes the backend Spring Boot controller and service that fetches paper metadata from CrossRef or Semantic Scholar, caches it, and integrates with the existing RAG chat endpoint.
- Finally, the agent produces a unit test for the frontend component (using React Testing Library) and an integration test for the backend endpoint.
Time saved: From ~6‑8 hours to ~30 minutes of review.
6. Operational & Governance Considerations
| Aspect | Recommendation |
|---|---|
| Security | Agents should run with least privilege. For code generation, they must not have direct access to production credentials. For user‑facing agents, implement strict content filtering and rate limiting. |
| Cost | Server‑side agents (using GPT‑4) incur API costs. Local agents (using open‑source models) are free but slower and require more client resources. |
| Quality | Always require human review of generated code before merging. Use automated tests to verify that agent‑generated code does not break existing functionality. |
| Versioning | Store agent‑generated code in version control (Git) with attribution (e.g., “Co‑authored by AI agent”). |
| User Privacy | For client‑side agents, clearly indicate that data never leaves the user’s machine. For server‑side agents, enforce data anonymisation and secure data retention policies. |
7. Implementation Roadmap
- Phase 1 – Developer‑facing agents:
- Integrate a “code assistant” panel into your IDE (e.g., using VS Code extension or a custom web view).
- Grant the agent read‑only access to the repository.
- Allow it to suggest code via pull requests.
- Phase 2 – Integrated AI Assistant Window:
- Create a new desktop app (
AIWindow) that embeds a chat interface. - Connect it to a local LLM (Ollama or Transformers.js) with tool‑use capabilities (edit files, run commands).
- Restrict the tool‑use to a sandboxed development environment.
- Create a new desktop app (
- Phase 3 – User‑invocable agents (server side):
- Expose a new API endpoint
/api/agent/:taskthat accepts a natural language description and returns the result (or an asynchronous job ID). - Build specialised agents (gap analysis, grant writer) that call external APIs and internal services.
- Expose a new API endpoint
- Phase 4 – Offline, client‑side agents:
- Implement WebAssembly‑based execution of small models (e.g.,
transformers.js) insideAppRunner. - Provide a local “agent store” where users can download pre‑trained, small agents for common research tasks.
- Implement WebAssembly‑based execution of small models (e.g.,
8. Conclusion
AI agents are not a distant future – they can be integrated today into your research platform, both to accelerate development (writing clean, maintainable code in any language) and to empower end users (automating research tasks, generating reports, summarising papers). Because agents can run on the server (centralised, powerful) or on the client (private, offline), you can tailor the solution to each use case.
By embedding agent capabilities, your platform becomes more than a tool – it becomes an intelligent partner that researchers can talk to and delegate tedious or complex work, all while respecting their privacy and offline needs. 🚀
Backend Architecture of the Research Platform: A Detailed Explanation
The backend of this research platform is built as a modular monolith but is architected with clear service boundaries and asynchronous communication patterns. This design offers a balance: it avoids the initial complexity of a full microservices architecture while keeping a path open for future scaling. The core of the backend is built on Spring Boot, a mature and widely adopted framework that provides essential building blocks for everything from REST APIs to real-time messaging and data persistence.
1. The Foundation: From Client Request to Server Response
At its core, every action on the platform begins with a request from the frontend. The backend's primary function is to receive these requests, process them, and return a response. This is facilitated by RESTful APIs for all data-creating, reading, updating, and deleting (CRUD) operations.
REST APIs (HTTP/HTTPS): For standard, non-real-time operations, the frontend uses REST APIs built with Spring MVC. Each HTTP method (GET, POST, PUT, DELETE) maps to a specific backend controller method via annotations like @GetMapping, @PostMapping, etc. For example, when a user creates a new article, the frontend sends a POST request to /api/articles with the article data in JSON format. The backend’s ArticleController receives this, validates the input, saves it to the database, and returns the newly created article object as a JSON response.
Authentication & Authorization: Security is managed by Spring Security, which uses JWT (JSON Web Tokens) for stateless authentication. Upon successful login, the server issues a short-lived access_token and a longer-lived refresh_token. The frontend stores these and sends the access token in the Authorization header of every subsequent request. The backend verifies the token’s signature and extracts the user’s identity and roles. If the token has expired, the frontend can use the refresh token to obtain a new access token without requiring the user to log in again, a feature often implemented with Redis to invalidate tokens on logout.
2. Real-Time Communication: WebSockets and Beyond
For features requiring instantaneous updates, such as new messages in a chat or real-time collaboration in the document editor, the platform uses WebSockets, a persistent, full-duplex communication protocol. This is a stark contrast to REST, where the client must repeatedly poll the server for new data.
The WebSocket Stack
The platform uses a layered approach to WebSockets:
- WebSocket Protocol: This provides the underlying persistent connection between the client and server.
- STOMP (Simple Text Oriented Messaging Protocol): Layered on top of WebSockets, STOMP provides a flexible, message-oriented framing protocol. Instead of raw WebSocket messages, the client and server exchange STOMP frames, which define concepts like
destination,content-type, andbody.
How It Works:
When a user sends a message in a chat, the process is:
- Frontend Sends a Message: The frontend establishes a WebSocket connection to the backend endpoint (e.g.,
/ws). Using the STOMP protocol, it sends a message to a specific destination, e.g.,/app/chat.sendMessage, containing the message data. - Backend Receives and Publishes: The backend has a controller with a method annotated with
@MessageMapping("/chat.sendMessage"). This method is invoked, processes the message (e.g., saves it to the database), and then uses aSimpMessagingTemplateto publish the message to a topic that all clients in that chat room are subscribed to, such as/topic/public/${roomId}. - Other Clients Receive: All frontend clients connected to the same chat room are subscribed to the
/topic/public/...destination. The server broadcasts the message to them, and their WebSocket handler processes the incoming message and updates the UI instantly.
This architecture can be scaled horizontally by using an external message broker (like RabbitMQ or Apache Kafka) as a "broker relay". In that configuration, multiple instances of your backend application can all connect to the same broker, allowing them to publish and subscribe to topics and messages across instances, which is essential for clustering.
3. The Collaborative Editor: A Specialized Real-Time Engine
The collaborative document editor (ScientificEditor) is powered by Yjs, a high-performance CRDT (Conflict-free Replicated Data Type) library. Yjs is not a typical WebSocket application. It handles the complex logic of merging concurrent edits from multiple users automatically, guaranteeing that everyone ends up with the same final document without any central lock. The platform's architecture for this is unique:
- Yjs Client on Frontend: Each user's editor integrates a Yjs client. When a user types, the change is immediately applied to their local Yjs document.
- Yjs WebSocket Provider: The frontend uses a
WebsocketProviderto connect to a dedicated Yjs WebSocket server (e.g., running on port 9093). This server is responsible for relaying updates between clients. - Offline-First Collaboration: Changes are also persisted to the browser's
IndexedDBusingy-indexeddb. If a user goes offline, all their edits are saved locally. When they reconnect, the Yjs protocol automatically syncs their local changes with the server and other peers, ensuring no data is lost. This is a powerful offline-first capability built into the architecture.
4. Beyond Synchronous Calls: Building for Long-Term Scale
As the platform grows, a purely synchronous request/response architecture becomes a bottleneck. To prepare for the future, the platform's design incorporates patterns for asynchronous processing and eventual scalability.
Asynchronous and Event-Driven Patterns
For long-running or non-critical tasks (like sending emails or generating PDF exports), the platform can offload work to a background processor using a message queue. The frontend's request is acknowledged immediately, and the actual work happens asynchronously. This is the foundation of an event-driven architecture (EDA), where services communicate primarily through events.
- Event Sourcing and CQRS: For critical features with complex history, like the provenance trail, the platform could adopt Event Sourcing, where every state change is stored as an immutable event. Combined with CQRS (Command Query Responsibility Segregation), which separates write and read operations, this pattern ensures a complete audit log and can improve scalability.
From Monolith to Microservices
To gracefully handle growth, the modular monolith is designed to be split into independent microservices. The current service layer (e.g., UserService, ArticleService) defines clear boundaries that could become their own applications. For such an architecture, a set of supporting technologies would be essential:
- API Gateway: A central entry point like Spring Cloud Gateway would route requests to the correct microservice, handle cross-cutting concerns like rate limiting, and manage authentication for the entire system.
- Service Discovery: With multiple independent services, they need to find each other. A service registry like Netflix Eureka allows services to register themselves and discover others dynamically, avoiding hardcoded IP addresses.
- Orchestration: Managing data across services is complex. Patterns like Sagas, which use a sequence of local transactions with compensating actions, are used to maintain data consistency.
5. AI Agents: The Backend’s Cognitive Layer
The platform can embed AI agents as specialized microservices. These agents, built with emerging Java frameworks like Spring AI, Koog, or Embabel, can be exposed as standard web endpoints.
- How the Backend Handles an Agent Request:
- A user requests an AI task (e.g., "summarize this paper").
- The frontend sends a
POSTrequest to an endpoint like/api/agent/summarize. - The backend, through a service like
AgentOrchestratorService, routes the request. - The appropriate agent is invoked, possibly calling external LLM APIs or running a local model.
- The agent returns the result (e.g., the summary).
- The result can be saved to the database and returned to the frontend.
This architecture keeps your main application layer decoupled from the complexity of AI logic. As frameworks like Spring AI mature, they will offer even deeper integration, enabling autonomous, stateful agent workflows.
6. Summary: A Backend Built for the Future
The following table summarizes how the backend handles different types of workloads:
| Workload Type | Primary Backend Technology | Key Characteristic | Future Scalability Path |
|---|---|---|---|
| Synchronous CRUD | Spring MVC REST APIs | Stateless, request/response | Scale horizontally with more instances |
| Real-Time Messaging | WebSockets with STOMP | Stateful, persistent connection | Use external message broker (e.g., Redis, RabbitMQ) |
| Collaborative Editing | Yjs WebSocket Provider | CRDT-based, offline-first | Horizontal scaling of Yjs relay servers |
| Long-Running Tasks | Message Queues (e.g., RabbitMQ, Kafka) | Asynchronous, event-driven | Scale consumer services independently |
| Complex Data & Audit | Event Sourcing + CQRS | Immutable event log, separate read/write models | Decouples reads and writes for independent scaling |
| AI Features | Dedicated Agent Microservices | Specialized, potentially stateful | Scale agent services based on demand |
In essence, the backend is not a single monolithic application but a platform of capabilities. It handles immediate requests efficiently while providing a robust foundation for asynchronous work, event-driven communication, and future expansion into a full microservices architecture. This layered, forward-thinking design ensures the platform can support researchers for years to come.
Complete Backend Architecture & Frameworks for the Research Platform
This document provides an exhaustive, in‑depth explanation of the backend technologies, frameworks, design patterns, and operational practices used to build the research platform. It covers everything from the core stack to advanced features like real‑time collaboration, AI agents, and scalable deployment.
1. High-Level Backend Philosophy
The backend is designed as a modular monolith – a single deployable application with clear domain boundaries (services, repositories, controllers). This avoids the complexity of microservices early on while keeping the option to split into distributed services later. The guiding principles are:
- Statelessness: Each server instance can handle any request; state is externalized (e.g., to Redis or the database).
- Event‑driven communication for asynchronous, decoupled tasks.
- Security by design – authentication, authorization, CSRF, rate limiting, and data validation at every layer.
- Observability – logs, metrics, and traces are first‑class citizens.
- Resilience – timeouts, retries, circuit breakers, and graceful degradation.
2. Core Frameworks & Libraries (Spring Boot Ecosystem)
The backbone of the backend is Spring Boot 3.x (using Java 17/21). Spring Boot provides a convention‑over‑configuration approach and an extensive ecosystem.
| Framework / Library | Purpose | How It Is Used |
|---|---|---|
| Spring Framework | Core IoC (Inversion of Control), DI, AOP | All services, repositories, and controllers are Spring beans. Dependency injection decouples layers. |
| Spring MVC | REST API layer | Controllers map HTTP endpoints (e.g., @RestController, @GetMapping). Request validation with @Valid. |
| Spring Data JPA | Database access (ORM) | Repositories extend JpaRepository. Entities mapped with @Entity. Hibernate as the JPA provider. |
| HikariCP | Connection pooling | Default in Spring Boot 2+; highly performant, zero‑overhead configuration. |
| Spring Security | Authentication & authorization | JWT‑based stateless authentication. Method security with @PreAuthorize. CORS configuration. |
| Spring WebSocket | Real‑time messaging | WebSocket endpoint (/ws) with STOMP protocol. Handles chat, typing indicators, notifications. |
| Spring Scheduling | Background jobs | @Scheduled for daily digest emails, cleanup tasks, index rebuilding. |
| Spring Cache | Caching abstraction | @Cacheable, @CacheEvict with Redis or Caffeine as backing cache. |
| Spring Actuator | Monitoring & management | Exposes /health, /metrics, /info, /prometheus endpoints. |
2.1 Why Spring Boot?
- Production‑ready features (Actuator, metrics, health checks) out of the box.
- Huge community and compatibility with almost any Java library.
- GraalVM native image support for lower memory footprint and faster startup (if needed).
- Long‑term support from VMware.
3. Database & Data Persistence
3.1 Relational Database: PostgreSQL
- Why PostgreSQL: ACID compliant, supports JSONB (useful for dynamic metadata), full‑text search, and excellent performance under analytical workloads.
- Connection Pool: HikariCP – default with Spring Boot, minimal configuration.
- Migration Tool: Flyway (or Liquibase). SQL migrations in
db/migrationrun automatically on startup.
3.2 JPA & Hibernate
- Entities use
@Entity, relationships (@OneToMany,@ManyToOne), and lazy loading. - Custom repositories with
@Queryfor complex queries. - Auditing:
@CreatedDate,@LastModifiedDatevia Spring Data Auditing.
3.3 Caching with Redis
- Purpose: Reduce database load, speed up repeated reads (user profiles, session data, rate‑limit counters).
- Spring Cache with Redis as the provider.
- Use cases:
- User session storage (when horizontally scaling).
- Rate limiting counters (bucket4j with Redis backend).
- Query cache for expensive joins (e.g., article recommendations).
3.4 Full‑Text Search (Optional but Recommended)
- Elasticsearch or OpenSearch can be integrated for advanced search over articles, messages, and users.
- Synchronization can be done via change data capture (Debezium) or by emitting events after entity changes.
4. Real‑Time Communication & Messaging
4.1 WebSocket + STOMP (for Chat & Notifications)
- Endpoint:
/ws(configurable). - STOMP provides a message‑oriented protocol over WebSockets.
- Flow:
- Client subscribes to a topic (e.g.,
/topic/messages/room-123). - Client sends a message to a destination like
/app/chat.sendMessage. - Controller (
@MessageMapping) processes the message, persists it, then usesSimpMessagingTemplateto broadcast to all subscribers.
- Client subscribes to a topic (e.g.,
- Scaling: Use an external broker (RabbitMQ or Kafka) as a “broker relay” to share messages across multiple backend instances.
4.2 Collaborative Editing with Yjs
- Separate WebSocket server (e.g.,
y-websocket) running on a different port (9093). - Why not use the main WebSocket? The Yjs protocol is very chatty and benefits from a dedicated, lightweight relay.
- Client: Frontend uses
y-websocketprovider to sync changes. - Persistence: Yjs documents can be persisted to the main database (PostgreSQL) for long‑term storage, but the primary real‑time relay is stateless.
4.3 Asynchronous Messaging (RabbitMQ / Kafka)
- Purpose: Handle long‑running or background tasks.
- Examples:
- Sending emails after user registration.
- Generating PDF exports of documents.
- Indexing new articles in Elasticsearch.
- Training recommendation models.
- Implementation: Spring AMQP for RabbitMQ, or Spring Kafka. Producers send messages to queues; consumers (asynchronous) process them and update the database or call external services.
5. Security Architecture
5.1 Authentication: JWT (Stateless)
- Login endpoint:
/api/loginreturnsaccess_token(short expiry, e.g., 15 min) andrefresh_token(httpOnly cookie or stored in database). - Access token is sent in
Authorization: Bearerheader. - Refresh token endpoint
/api/token/refreshreturns new access token. - Spring Security with custom
JwtAuthenticationFilterthat validates the token and setsSecurityContext.
5.2 Two‑Factor Authentication (TOTP)
- Library: Google Authenticator (
com.warrenstrange:googleauth). - Flow: User enables 2FA → server generates secret + QR code → user scans and verifies first code → backup codes generated.
- Backup codes stored encrypted in database.
5.3 Authorization: Role‑Based (RBAC)
- Roles:
USER,ADMIN,MODERATOR(extensible). @PreAuthorize("hasRole('ADMIN')")on admin endpoints.
5.4 CSRF Protection
- Enabled by default in Spring Security for state‑changing endpoints (POST, PUT, DELETE).
- Since the API is stateless and uses JWT, CSRF tokens can be disabled or handled via a custom filter.
5.5 Rate Limiting
- Library: Bucket4j (token bucket algorithm).
- Backed by Redis for distributed counters.
- Applied via a custom Spring interceptor or Spring Cloud Gateway.
- Limits per endpoint, per user, or per IP.
5.6 Data Sanitization & Validation
- Input validation:
@NotNull,@Size,@Patternon DTOs. - XSS prevention: Escape user‑generated HTML before storing; use
HtmlUtils.htmlEscapeor OWASP Encoder.
5.7 Secrets Management
- Environment variables for database password, JWT secret, API keys.
- For production: HashiCorp Vault or AWS Secrets Manager.
6. AI Agents & LLM Integration
6.1 How the Backend Handles AI Agents
AI agents are implemented as specialised microservices or Spring components that integrate with Large Language Models (LLMs). The backend exposes endpoints that invoke these agents.
Frameworks:
- Spring AI (emerging) – provides abstractions for chat completions, embeddings, and function calling.
- LangChain4j – Java port of LangChain; supports many LLM providers and vector stores.
- Custom HTTP clients to OpenAI, Anthropic, Cohere, or local models (Ollama, llama.cpp).
Typical Agent Workflow:
- Frontend sends a request to
/api/agent/summarizewith paper content. - Backend (Agent Orchestrator) selects the appropriate agent.
- Agent formats a prompt (with role, context, and instructions).
- Agent calls the LLM (OpenAI API or local) and receives the response.
- Backend may post‑process the response (extract JSON, validate).
- Result is saved to the database (e.g., as a
PaperSummaryentity) and returned to the frontend.
6.2 Running Agents on the Server vs. Client
| Aspect | Server‑side Agent | Client‑side (Local) Agent |
|---|---|---|
| Models | Large (GPT‑4, Claude), proprietary or hosted open‑source | Small models (Phi‑3, Llama 3‑8B) via Transformers.js or Ollama |
| Privacy | Data leaves user’s machine | Data stays on user’s machine |
| Latency | Depends on network and API | Very low after initial model load |
| Resource Usage | Central server resources (GPU optional) | User’s RAM/GPU (high for large models) |
| Offline | Requires internet | Works offline |
| Implementation | Spring AI client, async tasks | WebAssembly + local server (Ollama) or pure browser inference |
The backend can support both – offer server agents for heavy tasks (e.g., training a custom model) and provide APIs for client agents to call when they need fallback or centralised data.
6.3 Example: Research Agent for Gap Analysis
- Agent type: Server‑side, using GPT‑4 via Spring AI.
- Input: Research question (e.g., “What are the open problems in few‑shot learning?”)
- Process:
- Agent queries the knowledge graph database for existing nodes.
- Fetches recent papers from Semantic Scholar.
- Combines with LLM to propose three new research hypotheses.
- Stores hypotheses as new nodes in the gap‑analysis graph.
- User sees: New
hypothesisnodes appear in the knowledge graph, ready to be explored.
7. Asynchronous & Scheduled Tasks
7.1 @Async (Spring) for Simple Background Tasks
- Enables with
@EnableAsync. - Use a custom
ThreadPoolTaskExecutor(e.g., core=4, max=10). - Return
CompletableFuturefor non‑blocking calls.
7.2 Message Queues for Durable, Scalable Processing
- RabbitMQ (simpler) or Kafka (high‑throughput, retention).
- Example: When a new article is published, an event is sent to a
article.publishedexchange. Multiple consumers (email service, search indexer, recommendation engine) process it independently. - This decouples the main request lifecycle from the background work.
7.3 Scheduled Jobs (@Scheduled)
- Cron expressions for daily digests, database cleanups, certificate renewal.
- To prevent duplicate execution in a cluster, use ShedLock with a shared database table or Redis.
8. Resilience & Fault Tolerance
| Pattern | Implementation | When to Use |
|---|---|---|
| Retries | Spring Retry (@Retryable) |
Transient failures (network timeouts, database deadlocks). |
| Circuit Breaker | Resilience4j | External service (LLM API, recommendation model) starts failing; avoid cascading failures. |
| Bulkhead | Resilience4j (thread pool or semaphore isolation) | Limit concurrent access to a critical resource (e.g., database connection pool). |
| Timeout | @Transactional(timeout = ...) or Resilience4j |
Prevent long‑running requests from exhausting threads. |
| Rate Limiter | Bucket4j | Protect against API abuse, both per user and per endpoint. |
9. Observability (Logging, Metrics, Tracing)
9.1 Logging
- SLF4J + Logback (Spring Boot default).
- Structured logging (JSON) to integrate with ELK or Loki.
- Log levels: DEBUG for development, INFO for production, WARN/ERROR for issues.
- MDC (Mapped Diagnostic Context) to inject trace ID, user ID, request ID.
9.2 Metrics (Micrometer + Prometheus)
- Micrometer collects: request count, response time histogram, error count, JVM memory, thread pools, database connection pool.
- Prometheus scrapes metrics from
/actuator/prometheus. - Grafana dashboards visualise the data.
9.3 Distributed Tracing (OpenTelemetry)
- OpenTelemetry Java agent auto‑instruments Spring Boot, HTTP clients, JDBC, etc.
- Export traces to Jaeger or Tempo.
9.4 Health Checks
- Spring Actuator provides
/health(liveness) and/health/readiness(readiness). - Used by Kubernetes for probes.
10. Testing Strategy
| Test Type | Tools | Coverage |
|---|---|---|
| Unit | JUnit 5, Mockito | Services, utilities, mappers. |
| Integration | @SpringBootTest, Testcontainers |
Controllers, repository operations, WebSocket communication. |
| Contract | Spring Cloud Contract or Pact | API contract between frontend and backend. |
| End‑to‑End | Playwright (frontend) + Testcontainers (backend) | Critical user flows (login → create article → send message). |
| Load | JMeter, Gatling, k6 | Simulate high concurrency. |
Testcontainers is especially useful: it spins up real PostgreSQL, Redis, RabbitMQ containers for integration tests.
11. Deployment & CI/CD
11.1 Containerisation (Docker)
- Dockerfile using
eclipse-temurin:17-jreas base. - Layered builds to cache dependencies.
- Use of Jib (Google) for optimized, secure images without Docker daemon.
11.2 Orchestration (Kubernetes)
- Deployment with multiple replicas for high availability.
- ConfigMaps for environment variables.
- Secrets for sensitive data (database passwords, JWT secret).
- Horizontal Pod Autoscaler based on CPU/memory or custom metrics (e.g., queue length).
- Ingress with TLS termination (Let’s Encrypt).
11.3 CI/CD Pipeline (GitHub Actions)
- Stages:
checkout → setup JDK → cache dependencies → unit tests → integration tests → build image → push to registry → deploy to staging → (manual approval) → deploy to production. - Security scanning: Snyk or Trivy for vulnerabilities in dependencies and Docker image.
- Rollback: Revert deployment to previous revision.
11.4 Blue‑Green / Canary Deployments
- Argo Rollouts or Flagger for progressive delivery.
- Blue‑green: two identical environments; switch traffic instantly.
- Canary: route a small percentage of users to the new version, monitor error rate, then increase gradually.
12. Scalability Techniques
| Technique | How It Helps |
|---|---|
| Stateless services | Any instance can handle any request → easy horizontal scaling. |
| Read replicas | Scale read queries (e.g., article listings) to replica databases; writes go to the primary. |
| Caching (Redis) | Reduce database load; store user sessions, rate‑limit counters, frequently accessed data. |
| Asynchronous processing | Offload long tasks to background workers; keep web threads free. |
| Database partitioning | Split large tables (e.g., messages) by date or user ID. |
| Indexing | Proper database indexes; Elasticsearch for search. |
13. Example Technology Stack Summary
| Layer | Technology / Library |
|---|---|
| Language | Java 17 (LTS) |
| Framework | Spring Boot 3.2.x |
| Security | Spring Security, JJWT (for JWT), Google Auth (2FA) |
| Database | PostgreSQL 15+ with Hibernate (Spring Data JPA) |
| Migration | Flyway |
| Caching | Redis (via Spring Cache) |
| Messaging / Queue | RabbitMQ (AMQP) or Kafka |
| WebSocket | Spring WebSocket + STOMP, optional RabbitMQ broker relay |
| Collaborative editor | Yjs + y-websocket server (Node.js or Java) |
| Search | Elasticsearch (optional) |
| Monitoring | Micrometer + Prometheus + Grafana |
| Logging | Logback (JSON), Loki |
| Tracing | OpenTelemetry + Jaeger |
| Rate Limiting | Bucket4j + Redis |
| Resilience | Resilience4j, Spring Retry |
| Testing | JUnit 5, Mockito, Testcontainers, RestAssured |
| Container | Docker + Jib |
| Orchestration | Kubernetes (EKS / GKE / AKS) |
| CI/CD | GitHub Actions |
| AI Integration | Spring AI / LangChain4j, OpenAI API, Ollama (local) |
14. Conclusion
The backend of your research platform is built on a robust, production‑proven stack that emphasizes reliability, security, and extensibility. By leveraging Spring Boot’s ecosystem, you get:
- Clean separation of concerns (controllers, services, repositories).
- Powerful real‑time communication via WebSockets + STOMP.
- Scalable event‑driven architecture using message queues.
- AI agent integration that can run both on the server (heavy models) and on the client (private, offline).
- Comprehensive observability, testing, and deployment pipelines.
This architecture not only serves the current feature set but also provides clear paths to split into microservices, adopt new AI capabilities, and handle thousands of concurrent researchers. 🚀
Complete Backend Component Connections & Data Flow
This document explains, in exhaustive detail, how every backend component connects to every other component, and how the frontend communicates with them. The goal is to provide a complete picture of the system’s internal communication patterns, protocols, and data flows.
1. Overview of Components and Their Roles
| Component | Role | Technology / Protocol |
|---|---|---|
| Frontend (React) | User interface, window manager, desktop environment | HTTP/HTTPS, WebSocket, Yjs WebSocket |
| Backend (Spring Boot) | REST APIs, WebSocket (STOMP), business logic, database access, background tasks | HTTP/HTTPS, WebSocket (STOMP), AMQP (RabbitMQ) / Kafka |
| PostgreSQL | Primary database | JDBC (via Hibernate) |
| Redis | Caching, session storage, rate limiting counters, distributed locks | TCP (Redis protocol) |
| RabbitMQ / Kafka | Message broker for async tasks | AMQP 0-9-1 (RabbitMQ) or TCP (Kafka) |
| Yjs WebSocket Server | Real‑time document collaboration (CRDT) | WebSocket (raw) |
| Elasticsearch (optional) | Full‑text search | HTTP REST |
| LLM Provider (OpenAI / local) | AI agent capabilities | HTTPS / HTTP |
| Object Storage (S3 / MinIO) | File storage for manuscripts, avatars, research objects | HTTP REST (S3 API) |
| SMTP Server | Email sending | SMTP |
2. Communication Protocols and Ports
| Protocol | Used For | Typical Port |
|---|---|---|
| HTTP/HTTPS | REST API calls between frontend and backend | 8080 (backend), 443 (frontend reverse proxy) |
| WebSocket (STOMP) | Real‑time chat, typing, notifications | 8080 (upgraded HTTP) |
| WebSocket (raw Yjs) | Collaborative editing updates | 9093 |
| TCP (PostgreSQL) | Database connection | 5432 |
| TCP (Redis) | Cache / session store | 6379 |
| AMQP (RabbitMQ) | Asynchronous messaging | 5672, 15672 (management) |
| TCP (Kafka) | Event streaming | 9092 |
| HTTP (Elasticsearch) | Search queries | 9200 |
| HTTPS (LLM API) | AI agent inference | 443 |
| HTTPS (S3 API) | File upload/download | 443 (or 9000 for MinIO) |
| SMTP | Email sending | 25, 465, 587 |
3. Detailed Component Connections
3.1 Frontend ↔ Backend (REST API)
- Direction: Bidirectional (request/response).
- Protocol: HTTP/HTTPS.
- Authentication: JWT in
Authorizationheader. - Example flow:
- Frontend sends
POST /api/articleswith JSON body. - Backend controller (
ArticleController) receives, validates, callsArticleService, saves viaArticleRepository. - Backend returns HTTP 201 with the created article JSON.
- Frontend sends
- Load balancing: Nginx or Kubernetes Ingress distributes requests across multiple backend pods.
3.2 Frontend ↔ Backend (WebSocket – STOMP)
- Endpoint:
ws://backend/ws(or/wswith upgrade). - Connection initiation:
- Frontend creates a WebSocket connection.
- STOMP client handshake (CONNECT frame with authentication token).
- Backend authenticates using the token (e.g., via
ChannelInterceptor).
- Subscription:
- Client sends
SUBSCRIBEframe to a destination, e.g.,/topic/messages/room-123. - Backend remembers the session.
- Client sends
- Publishing:
- Client sends
SENDframe to/app/chat.sendMessage. - Backend
@MessageMappingmethod processes the message. - Backend uses
SimpMessagingTemplate.convertAndSend(destination, payload)to broadcast to all subscribers.
- Client sends
- Broker Relay (for scaling):
- Spring’s
@EnableWebSocketMessageBrokercan be configured withsetRelayHostto forward messages to an external RabbitMQ broker. - Then multiple backend instances share the same broker; each instance subscribes to the broker, and the broker distributes messages to all instances (which in turn push to their local client connections).
- Spring’s
3.3 Frontend ↔ Yjs WebSocket Server
- Endpoint:
ws://yjs-server:9093(separate from main backend). - Why separate: Yjs WebSocket protocol is different from STOMP; mixing them would add complexity.
- Connection flow:
- Frontend creates a Yjs document (
new Y.Doc()). - Initializes
WebsocketProviderwith the server URL, room name (docId), and the Ydoc. - The WebSocket provider connects and sends a “sync” message.
- Yjs server maintains a list of connections per room.
- When any client makes a local change, the Yjs provider sends an update message; the server relays it to all other clients in the same room.
- The Yjs server does not persist data; it’s stateless. Persistence (offline) is done by the frontend via
y-indexeddb. Long‑term storage can be added by having the server also write to PostgreSQL (optional).
- Frontend creates a Yjs document (
- Authentication: The Yjs server can be configured to check a token (e.g., by providing a custom authentication callback). The frontend includes the JWT in the connection URL as a query parameter:
ws://yjs-server:9093?token=.... The server validates the token and maps it to a user ID.
3.4 Backend ↔ PostgreSQL
- Connection: Hibernate / Spring Data JPA uses a HikariCP connection pool.
- Configuration:
spring.datasource.url, username, password. - Transactions:
@Transactionaldeclarative boundaries. - Read replicas: Spring can be configured with
RoutingDataSourceto route read queries (@Transactional(readOnly=true)) to a replica.
3.5 Backend ↔ Redis
- Use cases:
- Caching (
@Cacheable): e.g., user profiles, article trending lists. - Rate limiting counters (Bucket4j Redis backend).
- Distributed locks (e.g., ShedLock for scheduled jobs).
- Session storage (when clustering, store
SecurityContextin Redis).
- Caching (
- Client: Lettuce (default in Spring Boot) or Jedis.
- Serialization: JSON (Jackson) or Protobuf for performance.
3.6 Backend ↔ RabbitMQ / Kafka
- Purpose: Decouple time‑consuming or non‑critical tasks from the request/response cycle.
- Producer side (e.g., after article publication):
@Service public class ArticleService { @Autowired private RabbitTemplate rabbitTemplate; public Article publish(Article article) { // save article to DB rabbitTemplate.convertAndSend("exchange", "routingKey", new ArticlePublishedEvent(article)); return article; } } - Consumer side (e.g., email service, search indexer):
@RabbitListener(queues = "article.published.queue") public void handle(ArticlePublishedEvent event) { ... } - Kafka uses
@KafkaListenerinstead. - Integration: Spring AMQP / Spring Kafka provide auto‑configuration.
3.7 Backend ↔ Elasticsearch
- Why: Fast full‑text search across articles, messages, users.
- Sync strategy:
- After an article is created/updated, the service sends a message via RabbitMQ.
- A separate consumer (can be the same backend instance or a dedicated search service) listens to the queue and updates the Elasticsearch index.
- Alternatively, use Change Data Capture (Debezium) to stream DB changes directly to Elasticsearch.
- Query: Frontend calls a search endpoint (
/api/articles/search), the backend translates the request to an Elasticsearch query and returns the results.
3.8 Backend ↔ LLM Provider (AI Agent)
- Provider options:
- OpenAI / Anthropic / Cohere: HTTPS calls to their APIs.
- Local model (Ollama): HTTP to
http://localhost:11434(if running on same host) or to a dedicated inference service.
- Spring AI provides a consistent
ChatClientinterface:String summary = chatClient.call("Summarize this paper: " + paperText); - Agent orchestration:
- Backend endpoint
/api/agent/analyzereceives request. AgentOrchestratorServiceselects the appropriate agent (e.g.,GapAnalysisAgent).- Agent builds a prompt, calls LLM, parses response, and may call other internal APIs (e.g., the knowledge graph service).
- The result is stored (e.g., as a new
Hypothesisentity) and returned to frontend.
- Backend endpoint
3.9 Backend ↔ Object Storage (S3 / MinIO)
- Use cases: Store uploaded files – avatars, manuscript PDFs, research objects, exported documents.
- Flow:
- Frontend sends file to backend (multipart form data) or directly uses presigned URL.
- Backend (if using presigned URL): calls
GeneratePresignedUrlRequeston S3 client, returns URL to frontend; frontend uploads directly to S3. - Backend stores the file URL in the database.
- Client: AWS SDK for Java (S3).
3.10 Backend ↔ SMTP Server
- Use: Sending emails – password reset, email verification, daily digest, notifications.
- Integration:
JavaMailSender(Spring Boot starter). - Configuration:
spring.mail.host, port, username, password. - Templates: Thymeleaf or FreeMarker for HTML emails.
4. Data Flow for a Critical User Action: Sending a Group Message
This concrete example ties all components together.
- User types message in ChatArea and clicks “Send”.
- Frontend:
- Optimistically adds message to local state (temp ID, “sending” status).
- Sends via WebSocket STOMP:
stompClient.send("/app/group.sendMessage", {}, JSON.stringify({ groupId, body, replyToId })).
- Backend WebSocket (@MessageMapping):
- Method
handleGroupMessageinGroupChatControllerreceives the message. - Validates user membership in group.
- Calls
GroupMessageService.saveMessage(...)which persists to PostgreSQL (viaGroupMessageRepository). - After successful save, uses
SimpMessagingTemplate.convertAndSend("/topic/group/" + groupId, messageDTO)to broadcast. - Also sends a message to a RabbitMQ queue
group.message.sentfor analytics / notification processing.
- Method
- Broadcast:
- All other clients subscribed to
/topic/group/groupIdreceive the message. - Their frontend WebSocket handler updates the message list.
- All other clients subscribed to
- Asynchronous processing:
- A
@RabbitListenerongroup.message.sentqueue fetches the message. - It updates the
lastMessageandunreadCountfor each group member (batch update). - Also triggers a push notification (via WebSocket or Firebase) if the user is offline.
- A
- Read receipt:
- When a user views the group, the frontend sends a STOMP message to
/app/group.markRead. - Backend marks the user’s last read timestamp and sends a “read receipt” event to the group topic.
- When a user views the group, the frontend sends a STOMP message to
5. Inter‑Service Communication (within the Backend)
Even though the backend is a monolith, its internal services communicate through:
- Direct method calls:
ArticleServicecallsUserServiceto get author profile. - Application events (Spring
ApplicationEventPublisher): Decouples modules. For example,ArticleServicepublishesArticlePublishedEvent;AnalyticsServicelistens and updates statistics. - Message queue (RabbitMQ/Kafka) for cross‑module communication that should be asynchronous and durable. This also paves the way for future microservice extraction.
6. Scaling and High Availability
| Component | How to Scale |
|---|---|
| Backend (Spring Boot) | Horizontal scaling – multiple pods behind a load balancer. Stateless design required. |
| WebSocket connections | Sticky sessions (session affinity) or using a shared broker (RabbitMQ) to distribute messages across instances. |
| Yjs WebSocket Server | Also stateless; multiple instances can be deployed; clients connect to one instance. Since Yjs updates are based on room (docId), the server can use a consistent hashing strategy or simply rely on the frontend to reconnect if one instance goes down. However, Yjs does not share state across servers; if you need that, you would need a shared backend store (e.g., Redis or CRDTs across servers). For most use cases, a single Yjs server with a backup is enough. |
| PostgreSQL | Read replicas, connection pooling, vertical scaling, or partitioning. |
| Redis | Redis Cluster or Redis Sentinel. |
| RabbitMQ | Cluster mode. |
| Kafka | Partition replication. |
7. Security Between Components
| Connection | Security Measure |
|---|---|
| Frontend ↔ Backend (REST) | JWT over HTTPS; rate limiting; input validation. |
| Frontend ↔ Backend (WebSocket) | JWT passed in connection (e.g., as Authorization header during STOMP handshake). |
| Frontend ↔ Yjs Server | JWT as query parameter; Yjs server validates token. |
| Backend ↔ PostgreSQL | TLS (sslmode=require) and database user/password. |
| Backend ↔ Redis | Password authentication; optional TLS. |
| Backend ↔ RabbitMQ | Username/password; TLS; separate vhosts. |
| Backend ↔ Elasticsearch | TLS and API key. |
| Backend ↔ LLM API | API key in HTTPS header. |
| Backend ↔ S3 | IAM roles (on cloud) or access/secret keys with TLS. |
8. Deployment Topology (Example)
[Internet]
↓
[Cloud Load Balancer (e.g., AWS ALB)] (443/80)
↓
[Reverse Proxy / Ingress (nginx)] (forward to backend on 8080)
↓
[Backend Pods (Spring Boot)] – multiple replicas
├─→ PostgreSQL (primary + read replica)
├─→ Redis (cluster)
├─→ RabbitMQ (cluster)
├─→ Yjs Server (one or more pods)
├─→ Elasticsearch (cluster)
└─→ S3 / MinIO
- WebSocket connections (chat) go through the same load balancer; they require sticky sessions (session affinity) or a shared broker. Using an external RabbitMQ as the broker relay removes the need for sticky sessions for STOMP messages.
- Yjs WebSocket connections often go to a separate endpoint (e.g.,
ws://yjs.yourdomain.com). This can be a different load balancer or a subdomain.
9. Summary Table: Component Connections
| From | To | Protocol | Port (typical) | Purpose |
|---|---|---|---|---|
| Frontend | Backend (REST) | HTTP | 443/8080 | All CRUD operations |
| Frontend | Backend (STOMP) | WebSocket | 443/8080 | Real‑time chat, notifications |
| Frontend | Yjs Server | WebSocket | 9093 | Collaborative editing |
| Backend | PostgreSQL | JDBC (TCP) | 5432 | Data persistence |
| Backend | Redis | TCP | 6379 | Caching, rate limiting |
| Backend | RabbitMQ | AMQP | 5672 | Async tasks, event distribution |
| Backend | Elasticsearch | HTTP | 9200 | Full‑text search |
| Backend | LLM API | HTTPS | 443 | AI agent inference |
| Backend | S3 / MinIO | HTTPS | 443/9000 | File storage |
| Backend | SMTP | TCP | 587 | Email sending |
| Backend (or external) | Yjs Server | WebSocket | 9093 | (Only if backend needs to act as a Yjs client) |
10. Conclusion
All components are interconnected through well‑defined protocols and standards: HTTP, WebSocket, JDBC, Redis protocol, AMQP, and more. The design uses loose coupling where possible (e.g., event‑driven communication via RabbitMQ) to allow independent scaling and evolution. The frontend communicates with three distinct endpoints:
- REST for normal operations.
- STOMP WebSocket for real‑time chat.
- Yjs WebSocket for collaborative editing.
The backend acts as the orchestrator, integrating the database, cache, message broker, search engine, AI services, and object storage into a cohesive platform. This architecture ensures high performance, reliability, and extensibility for a research environment. 🚀
Complete Backend Source Code Structure (Java Spring Boot)
This document provides a complete, ready-to-implement folder and file tree for the backend of the research platform. All files are named according to standard Spring Boot conventions and grouped by feature. You can use this as a blueprint to create the exact directory structure and file names for your project.
The backend is built with Java 17, Spring Boot 3.x, Maven (or Gradle), PostgreSQL, Redis, RabbitMQ, and optional Elasticsearch. The structure is modular, with clear separation of concerns: controllers, services, repositories, DTOs, entities, configuration, security, and integration components.
1. Overview of the Backend Directory Tree
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── ie/
│ │ └── researchplatform/
│ │ ├── ResearchPlatformApplication.java
│ │ ├── config/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── repository/
│ │ ├── model/
│ │ ├── dto/
│ │ ├── security/
│ │ ├── event/
│ │ ├── listener/
│ │ ├── messaging/
│ │ ├── collaboration/
│ │ ├── ai/
│ │ ├── util/
│ │ ├── exception/
│ │ └── integration/
│ └── resources/
│ ├── application.yml
│ ├── application-dev.yml
│ ├── application-prod.yml
│ ├── db/migration/
│ ├── templates/ (email templates)
│ └── static/
├── test/
│ └── java/
│ └── com/ie/researchplatform/ (tests mirroring main)
└── pom.xml (or build.gradle)
2. Detailed File Tree with Explanations
2.1 Main Application Entry Point
com/ie/researchplatform/
└── ResearchPlatformApplication.java
@SpringBootApplicationannotation.- Contains
mainmethod.
2.2 Configuration Classes (config/)
| File | Purpose |
|---|---|
AppConfig.java |
General beans (e.g., RestTemplate, ObjectMapper). |
DatabaseConfig.java |
Data source settings, Hibernate properties, connection pool tuning. |
RedisConfig.java |
Redis connection factory, cache manager, serialization. |
RabbitMQConfig.java |
Exchanges, queues, bindings for async processing. |
WebSocketConfig.java |
STOMP endpoint configuration, broker relay. |
SecurityConfig.java |
Spring Security filters, JWT authentication, CORS, CSRF. |
AsyncConfig.java |
@EnableAsync, thread pool executors. |
SchedulingConfig.java |
@EnableScheduling, custom scheduler. |
SwaggerConfig.java |
OpenAPI (SpringDoc) configuration. |
ElasticsearchConfig.java |
RestHighLevelClient (if used). |
AwsS3Config.java |
S3 client configuration (if using object storage). |
MailConfig.java |
JavaMailSender configuration. |
2.3 Controllers (controller/)
Each controller maps to a domain. All endpoints are prefixed with /api.
| File | Endpoint Base | Description |
|---|---|---|
AuthController.java |
/api/auth |
Login, logout, refresh token, 2FA verification. |
UserController.java |
/api/users |
Profile, follow, contacts, avatar upload. |
ArticleController.java |
/api/articles |
CRUD, search, like, comment, analytics, publish. |
MessageController.java |
/api/messages |
Private message history, forward, reactions. |
ConversationController.java |
/api/conversations |
List conversations, delete, archive, typing, read status. |
GroupController.java |
/api/groups |
Group CRUD, members, admins, messages, pins, mute. |
GroupMessageController.java |
/api/groups/{id}/messages |
Group message operations. |
PollController.java |
/api/polls |
Poll creation, voting, results. |
NotificationController.java |
/api/notifications |
Fetch notifications, mark read, delete. |
RecommendationController.java |
/api/recommendations |
Feed, trending, similar articles. |
CollectionController.java |
/api/collections |
Paper collections CRUD. |
ReferenceController.java |
/api/references |
Reference import (DOI, BibTeX, Zotero, Mendeley). |
DocumentController.java |
/api/documents |
Collaborative document references, export. |
PreprintController.java |
/api/preprints |
Screening and submission. |
ProvenanceController.java |
/api/provenance |
Fetch events, verify chain. |
UnifiedInboxController.java |
/api/unified-inbox |
Aggregated inbox. |
RagController.java |
/api/ai/rag |
RAG chat, indexing, conversation. |
GapAnalysisController.java |
/api/gap-analysis |
Nodes, edges, AI analysis, hypothesis generation. |
GrantController.java |
/api/grants |
Grant deadlines, RFP analysis, generation. |
LabInventoryController.java |
/api/inventory |
Chemicals, alerts, scan, compatibility, stats. |
PeerReviewController.java |
/api/peer-review |
Submissions, claims, reviews, credentials. |
ProjectController.java |
/api/projects |
Projects, members, tasks, outputs. |
EventController.java |
/api/events |
Event CRUD, RSVP, reminders, calendar export. |
WorkspaceController.java |
/api/workspace |
Panels, presets, sharing. |
VoiceSettingsController.java |
/api/voice |
TTS voices, user settings, wake words. |
AdminController.java |
/api/admin |
Stats, user/article/RAG management. |
PluginController.java |
/api/plugins |
App store – list, install, submit. |
HealthController.java |
/health |
Liveness/readiness probes. |
2.4 Services (service/)
Each service contains business logic, orchestration, and transaction boundaries.
| File | Description |
|---|---|
UserService.java |
Profile updates, follow/unfollow, contacts, avatar handling. |
ArticleService.java |
CRUD, like, comment, view count, analytics, graph recommendations. |
MessageService.java |
Send, edit, delete, reactions, forward (private). |
GroupService.java |
Group management, member roles, invitation links. |
NotificationService.java |
Create notifications, mark read, batch delete. |
RecommendationService.java |
Feed generation (hybrid), trending, similar articles. |
CollectionService.java |
Collections CRUD, add/remove papers. |
ReferenceService.java |
Import from DOI/BibTeX/Zotero/Mendeley, citation formatting. |
PreprintService.java |
AI screening, submission, DOI minting. |
ProvenanceService.java |
Record events, verify integrity (hash chain). |
UnifiedInboxService.java |
Aggregate messages from multiple sources. |
RagService.java |
Document indexing, embedding, vector similarity, LLM chat. |
GapAnalysisService.java |
Graph management, AI gap detection, hypothesis generation. |
GrantService.java |
Deadlines, RFP analysis, grant generation (AI). |
LabInventoryService.java |
Chemical CRUD, barcode lookup, compatibility, alerts. |
PeerReviewService.java |
Anonymous submission, reviewer assignment, review storage. |
ProjectService.java |
Project lifecycle, task board, member roles. |
EventService.java |
Event creation, RSVP, reminders, iCal export. |
WorkspaceService.java |
Workspace panels, presets, import/export. |
VoiceSettingsService.java |
User TTS/STT preferences, voice profiles. |
ExportService.java |
Document export (PDF, DOCX, LaTeX, HTML). |
SearchService.java |
Full‑text search across articles, messages, users (Elasticsearch). |
EmailService.java |
Send emails (verification, digest, notifications). |
FileStorageService.java |
Upload to S3/MinIO, generate presigned URLs. |
TwoFactorService.java |
TOTP secret generation, verification, backup codes. |
AuditService.java |
Write audit logs. |
RateLimitService.java |
Token bucket with Redis. |
SessionService.java |
Manage user sessions (if needed). |
PluginService.java |
Store and retrieve user‑submitted plugins. |
2.5 Repositories (repository/)
Spring Data JPA repositories. Each entity gets a repository interface.
| File | Entity |
|---|---|
UserRepository.java |
User |
ArticleRepository.java |
Article |
CommentRepository.java |
Comment |
ConversationRepository.java |
Conversation |
MessageRepository.java |
Message |
GroupRepository.java |
Group |
GroupMemberRepository.java |
GroupMember |
GroupMessageRepository.java |
GroupMessage |
PollRepository.java |
Poll |
PollVoteRepository.java |
PollVote |
NotificationRepository.java |
Notification |
CollectionRepository.java |
Collection |
CollectionPaperRepository.java |
CollectionPaper |
ReferenceRepository.java |
Reference |
DocumentReferenceRepository.java |
DocumentReference (junction) |
PreprintRepository.java |
Preprint |
ProvenanceEventRepository.java |
ProvenanceEvent |
RagDocumentRepository.java |
RagDocument |
GapNodeRepository.java |
GapNode |
GapEdgeRepository.java |
GapEdge |
GrantRepository.java |
Grant |
ChemicalRepository.java |
Chemical |
AlertRepository.java |
Alert |
ReviewSubmissionRepository.java |
ReviewSubmission |
ReviewRepository.java |
Review |
ProjectRepository.java |
Project |
ProjectTaskRepository.java |
ProjectTask |
EventRepository.java |
Event |
ReminderRepository.java |
Reminder |
WorkspacePanelRepository.java |
WorkspacePanel |
VoiceSettingsRepository.java |
VoiceSettings |
AuditLogRepository.java |
AuditLog |
PluginRepository.java |
Plugin |
UserInstalledPluginRepository.java |
UserInstalledPlugin |
2.6 Model / Entity Classes (model/)
JPA entities with @Entity. Use Lombok for boilerplate.
| File | Fields (abridged) |
|---|---|
User.java |
id, username, email, passwordHash, fullName, bio, avatarUrl, role, isEmailVerified, securityQuestion, securityAnswerHash, twoFactorSecret, backupCodes, createdAt. |
Article.java |
id, title, abstract, body, author, tags, status (draft/published), views, likesCount, commentsCount, createdAt, updatedAt. |
Comment.java |
id, articleId, userId, body, createdAt. |
Conversation.java |
id, user1, user2, lastMessage, lastMessageAt, user1LastReadAt, user2LastReadAt, isArchivedForUser1, etc. |
Message.java |
id, conversationId, fromUserId, body, sentAt, status, replyToId, attachments (JSON), reactions (JSON). |
Group.java |
id, name, description, avatarUrl, ownerId, createdAt, slowModeSeconds. |
GroupMember.java |
groupId, userId, role (admin/member), joinedAt. |
GroupMessage.java |
id, groupId, fromUserId, body, sentAt, replyToId, attachments, reactions. |
Poll.java |
id, groupId, question, options (JSON), multipleChoice, isQuiz, expiresAt, createdBy. |
PollVote.java |
pollId, userId, selectedOptionIds (JSON). |
Notification.java |
id, userId, type, title, body, read, createdAt, link. |
Collection.java |
id, userId, title, description, isPublic, createdAt. |
CollectionPaper.java |
collectionId, articleId, addedAt. |
Reference.java |
id, key, title, authors (JSON), year, journal, volume, pages, doi, source (DOI/BibTeX/Zotero/Mendeley). |
DocumentReference.java |
documentId, referenceId. |
Preprint.java |
id, submitterId, title, abstract, keywords, authors (JSON), manuscriptUrl, coverLetter, screeningResult (JSON), status, doi, createdAt. |
ProvenanceEvent.java |
id, entityType, entityId, action, userId, timestamp, details (JSON), hash. |
RagDocument.java |
id, userId, title, source, fileType, totalChunks, totalTokens, createdAt. |
GapNode.java |
id, type (paper/concept/method/finding/gap/hypothesis), label, description, metadata (JSON). |
GapEdge.java |
id, sourceNodeId, targetNodeId, type (cites/related_to/contradicts/supports/gap_bridge), strength. |
Grant.java |
id, userId, title, deadline, amount, status, description. |
Chemical.java |
id, name, formula, casNumber, location, quantity, unit, minStock, expiryDate, nfpa (JSON), barcode, createdAt. |
Alert.java |
id, chemicalId, type (low_stock/expiring/compatibility), severity, message, acknowledged, createdAt. |
ReviewSubmission.java |
id, submitterId, title, abstract, manuscriptUrl, keywords, status (pending/assigned/reviewed). |
Review.java |
id, submissionId, reviewerId, scores (JSON), commentToAuthor, commentToEditor, recommendation, confidence, submittedAt. |
Project.java |
id, name, description, ownerId, createdAt. |
ProjectTask.java |
id, projectId, title, description, columnId (backlog/in_progress/review/done), priority, tags (JSON), assigneeId, dueDate. |
Event.java |
id, title, description, startTime, endTime, location, isVirtual, maxAttendees, creatorId. |
Reminder.java |
id, eventId, userId, minutesBefore. |
WorkspacePanel.java |
id, userId, type, title, icon, position (JSON), size (JSON), props (JSON). |
VoiceSettings.java |
id, userId, sttLanguage, ttsVoice, ttsPitch, ttsRate, autoSpeak, wakeWordEnabled, wakeWord. |
AuditLog.java |
id, userId, action, entityType, entityId, timestamp, ipAddress. |
Plugin.java |
id, name, description, code, icon, author, approved, installCount, createdAt. |
UserInstalledPlugin.java |
userId, pluginId, installedAt. |
2.7 DTOs (Data Transfer Objects) (dto/)
DTOs for request/response bodies. Group by feature.
| File | Purpose |
|---|---|
LoginRequest.java, LoginResponse.java |
Login endpoint. |
RegisterRequest.java |
User registration. |
UserProfileDto.java |
User profile data (public/private). |
ArticleRequest.java, ArticleResponse.java |
Article CRUD. |
CommentRequest.java |
Add comment. |
MessageRequest.java, MessageResponse.java |
Send message. |
CreateGroupRequest.java |
Group creation. |
PollRequest.java |
Create poll. |
NotificationDto.java |
Notification payload. |
CollectionRequest.java |
Create collection. |
ReferenceImportRequest.java |
DOI/BibTeX import. |
PreprintScreenRequest.java, PreprintScreenResponse.java |
Preprint screening. |
ProvenanceEventDto.java |
Event representation. |
RagChatRequest.java |
RAG query. |
GapNodeRequest.java, GapEdgeRequest.java |
Graph operations. |
GrantRequest.java |
Grant creation. |
ChemicalRequest.java |
Lab inventory. |
ReviewRequest.java |
Submit peer review. |
ProjectTaskRequest.java |
Task creation. |
EventRequest.java |
Create event. |
WorkspacePanelDto.java |
Panel data. |
VoiceSettingsDto.java |
User voice preferences. |
PluginSubmissionRequest.java |
App store submit. |
Also include common DTOs: PageResponse.java, ErrorResponse.java, IdResponse.java.
2.8 Security Package (security/)
| File | Purpose |
|---|---|
JwtTokenProvider.java |
Generate/validate JWT, extract username. |
JwtAuthenticationFilter.java |
OncePerRequestFilter to validate token and set SecurityContext. |
CustomUserDetailsService.java |
Load user by username for authentication. |
SecurityUtils.java |
Get current user ID/username from SecurityContext. |
TotpManager.java |
Generate secret, verify code, backup codes. |
PasswordEncoder.java |
BCrypt (Spring provides). |
CsrfTokenManager.java |
Generate/validate CSRF tokens (if not disabled). |
2.9 Event & Listener Packages
event/– Custom application events (e.g.,ArticlePublishedEvent,NewMessageEvent,UserRegisteredEvent).listener/–@EventListeneror@Asynclisteners that process these events (e.g., send email, update search index, write to analytics).
2.10 Messaging (messaging/)
| File | Purpose |
|---|---|
WebSocketController.java |
@MessageMapping for chat, typing, reactions. |
StompInterceptor.java |
Authenticate WebSocket connections. |
RabbitMqProducer.java |
Publish events to queues. |
RabbitMqConsumer.java |
Consume async tasks (e.g., update Elasticsearch). |
2.11 Collaboration (collaboration/)
| File | Purpose |
|---|---|
YjsWebSocketConfig.java |
Configuration for embedding a Yjs WebSocket server (if using embedded Java server). Usually run as a separate Node.js process, but you can implement a Java-based relay. |
YjsUpdateHandler.java |
If using custom Java implementation – handle incoming Yjs updates. |
2.12 AI Package (ai/)
| File | Purpose |
|---|---|
AiAgentOrchestrator.java |
Route requests to the appropriate agent. |
GapAnalysisAgent.java |
Use LLM to detect research gaps. |
GrantProposalAgent.java |
Generate draft proposals. |
PaperSummarizerAgent.java |
Summarize abstracts/full texts. |
RagQueryProcessor.java |
Call LLM with context from vector DB. |
LocalModelClient.java |
Call Ollama or llama.cpp. |
OpenAiClient.java |
Call OpenAI API. |
2.13 Utility Package (util/)
| File | Purpose |
|---|---|
JsonUtils.java |
JSON helpers (Jackson). |
HtmlSanitizer.java |
Sanitize user input (OWASP). |
MarkdownConverter.java |
Convert Markdown to HTML (if needed). |
CitationFormatter.java |
APA/MLA/BibTeX generation. |
GraphUtils.java |
Knowledge graph algorithms (PageRank, co‑citation). |
EmailTemplateRenderer.java |
Thymeleaf for emails. |
FileUtils.java |
File operations, MIME type detection. |
DateUtils.java |
Date formatting. |
RandomGenerator.java |
Generate random strings (for invite codes, backup codes). |
2.14 Exception Handling (exception/)
| File | Purpose |
|---|---|
GlobalExceptionHandler.java |
@ControllerAdvice to handle exceptions and return ErrorResponse. |
ResourceNotFoundException.java |
404. |
BadRequestException.java |
400. |
UnauthorizedException.java |
401. |
ForbiddenException.java |
403. |
ConflictException.java |
409. |
2.15 Integration Package (integration/)
| File | Purpose |
|---|---|
ElasticsearchIndexer.java |
Index documents, query articles. |
S3FileStorage.java |
Upload, download, presigned URLs. |
SemanticScholarClient.java |
Fetch paper metadata from external API. |
CrossRefClient.java |
DOI resolution. |
ZoteroClient.java |
Import from Zotero API. |
MendeleyClient.java |
Import from Mendeley. |
3. Resource Files (src/main/resources/)
| File | Description |
|---|---|
application.yml |
Main configuration (profiles, database, Redis, etc.). |
application-dev.yml |
Development profile. |
application-prod.yml |
Production profile. |
db/migration/V1__init.sql |
Flyway migration scripts. |
db/migration/V2__add_2fa_tables.sql |
Additional migrations. |
templates/email-verification.html |
Email templates (Thymeleaf). |
templates/password-reset.html |
|
static/.gitkeep |
Static resources (if any). |
logback-spring.xml |
Logging configuration. |
4. Test Directory Structure (src/test/java/)
Mirrors the main package:
com/ie/researchplatform/
├── controller/ (e.g., ArticleControllerTest.java)
├── service/ (e.g., ArticleServiceTest.java)
├── repository/ (e.g., UserRepositoryTest.java)
├── integration/ (e.g., WebSocketIntegrationTest.java)
└── util/ (e.g., JwtTokenProviderTest.java)
Use @SpringBootTest, @DataJpaTest, @WebMvcTest, and @MockBean / Testcontainers for integration tests.
5. Build File (pom.xml – Maven)
Key dependencies (group ID and artifact ID):
<dependencies>
<!-- Spring Boot Starters -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
<!-- Database & Cache -->
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>
<dependency><groupId>org.flywaydb</groupId><artifactId>flyway-core</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
<!-- JWT -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId></dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><scope>runtime</scope></dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><scope>runtime</scope></dependency>
<!-- 2FA -->
<dependency><groupId>com.warrenstrange</groupId><artifactId>googleauth</artifactId><version>1.5.0</version></dependency>
<!-- Misc -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency>
<dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId></dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>
<!-- Elasticsearch (optional) -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency>
<!-- AWS S3 -->
<dependency><groupId>com.amazonaws</groupId><artifactId>aws-java-sdk-s3</artifactId></dependency>
<!-- AI / LLM Clients -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId><version>1.0.0-SNAPSHOT</version></dependency>
<!-- Testing -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
<dependency><groupId>org.testcontainers</groupId><artifactId>testcontainers</artifactId><scope>test</scope></dependency>
<dependency><groupId>org.testcontainers</groupId><artifactId>postgresql</artifactId><scope>test</scope></dependency>
</dependencies>
6. Conclusion
This folder and file tree provides a complete, production-ready blueprint for the backend of your research platform. You can create each file as a Java class, fill in the Spring annotations, and implement the business logic step by step. The structure supports all the features discussed in the frontend documentation (articles, messaging, collections, references, preprints, provenance, RAG, admin, plugins, etc.) and is designed to scale both in terms of code organisation and runtime performance.
Use Maven or Gradle to manage dependencies, and follow the naming conventions above to keep the project maintainable. With this blueprint, you can start coding immediately. 🚀
Complete Frontend Implementation for Installing and Running Apps (Plugins)
This document explains, in exhaustive detail, how the frontend of your research platform allows users to browse, install, and run third‑party apps (plugins) – all within the desktop‑style window manager. The system is built on top of the existing App Store and Plugin API described earlier. Here you will learn the exact frontend components, data flow, and user experience.
1. Overview of the Plugin System Frontend
The frontend part of the plugin system consists of:
- App Store Window – a dedicated desktop window where users browse, search, and install available plugins.
- App Editor – a simple form inside the App Store that allows developers to submit new plugins (HTML/CSS/JS).
- Sandboxed Runner – a secure iframe component that executes installed plugins.
- Desktop Integration – installed plugins appear as dynamic desktop icons; clicking them opens a new window with the runner.
- Backend API Client – functions to fetch available plugins, install/uninstall, and retrieve plugin code.
All code is already integrated into the existing SmartGlassWorkspace.tsx and uses the same window manager (WindowContainer, DesktopWindow).
2. Frontend Code Structure for Plugins
The relevant frontend files are:
src/
├── api/
│ └── plugins.ts # API client for plugin endpoints
├── hooks/
│ └── useAppStore.ts # Data fetching and installation logic
├── components/
│ └── app-store/
│ ├── AppCard.tsx # Card for one plugin in the store
│ ├── AppEditor.tsx # Form to submit a new plugin
│ ├── AppRunner.tsx # Sandboxed iframe that runs a plugin
│ └── PluginStorePage.tsx # Main store window (list, install)
├── pages/
│ └── PluginStorePage.tsx (or AppStorePage.tsx) – already provided
└── workspace/
└── SmartGlassWorkspace.tsx # Desktop integration (icons for installed apps)
All these files have been provided in previous answers. Here we focus on how they work together.
3. Step‑by‑Step: Installing an App
Step 1 – User Opens the App Store
- From the desktop, the user clicks the App Store icon (or launches it from the Start menu).
- This opens a new
DesktopWindowcontaining thePluginStorePagecomponent. - The
useAppStorehook fetches the list of approved plugins fromGET /api/pluginsand also the user’s installed plugins fromGET /api/user/installed.
Step 2 – Browsing Plugins
PluginStorePagerenders a grid ofAppCardcomponents.- Each card shows:
- Plugin name, description, author, install count.
- An “Install” button (if not already installed) or an “Uninstall” button.
- The user clicks “Install” on a plugin.
Step 3 – Installation Flow
AppCardcallsonInstall(plugin.id), which invokesinstallAppfromuseAppStore.installAppsends aPOST /api/plugins/user/install/{pluginId}to the backend.- The backend records the installation (in
user_installed_plugintable) and increments the plugin’s install count. - On success,
useAppStorerefetches the user’s installed plugins, and theinstalledIdsset is updated. - The “Install” button on that card changes to “Uninstall” (or disappears).
Step 4 – Installed App Appears on Desktop
SmartGlassWorkspace.tsxusesuseAppStoreto fetch installed plugins.- The component maintains a list of installed plugins (fetched via
getInstalled). - For each installed plugin, it renders a desktop icon dynamically (similar to the static desktop icons).
- The icon is created using the plugin’s icon (if provided) or a default folder icon.
Code snippet (from SmartGlassWorkspace.tsx):
const { installedPlugins, fetchInstalled } = useAppStore();
// ...
<div className="absolute top-12 left-4 space-y-4 z-10">
{/* static desktop icons */}
{/* ... */}
{/* dynamic installed plugin icons */}
{installedPlugins.map(plugin => (
<div
key={plugin.id}
onClick={() => launchInstalledApp(plugin)}
className="glass-card p-2 text-center w-20 cursor-pointer hover:bg-white/30 transition"
title={plugin.name}
>
{plugin.icon ? (
<img src={plugin.icon} className="w-8 h-8 mx-auto" alt="" />
) : (
<div className="text-2xl">📦</div>
)}
<div className="text-[10px] mt-1 truncate">{plugin.name}</div>
</div>
))}
</div>
Step 5 – Running the Installed App
- When the user clicks the desktop icon,
launchInstalledApp(plugin)is called. - This function uses the window manager’s
openWindowto create a new desktop window:- Title: plugin name
- Content:
<AppRunner appId={plugin.id} code={plugin.code} /> - Icon: plugin icon (if any)
- Default window size: 800×600 (or as defined)
- The window opens and immediately renders the
AppRunnercomponent.
4. Inside the AppRunner: Secure Execution
AppRunner.tsx is a simple but critical component:
import { useEffect, useRef } from 'react';
export function AppRunner({ appId, code }: { appId: string; code: string }) {
const iframeRef = useRef<HTMLIFrameElement>(null);
useEffect(() => {
if (!iframeRef.current) return;
const doc = iframeRef.current.contentDocument;
if (doc) {
doc.open();
doc.write(code);
doc.close();
}
}, [code]);
return (
<iframe
ref={iframeRef}
title={`app-${appId}`}
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
className="w-full h-full border-0 bg-white dark:bg-gray-900"
/>
);
}
Security features:
- The
sandboxattribute restricts what the iframe can do. It does not allowallow-top-navigationorallow-modalsby default, preventing the app from escaping or annoying the user. - The app runs in its own origin (the same origin as the parent, because of
allow-same-origin– but that also means it can access localStorage for that origin. To increase isolation, you could serve plugins from a separate domain, but for simplicity this is acceptable for internal apps. - The iframe cannot access the parent DOM; any desired interaction must go through
postMessage(optional, not yet implemented in the basic runner).
Why use an iframe?
It provides the strongest isolation available in a browser; it is the standard way to embed untrusted content.
5. Creating a New App (Developer Experience)
Developers (or users) can create and submit new plugins via the App Editor inside the App Store.
- They click a “+” button in the store, which opens
AppEditor. AppEditoris a form with fields: name, description, optional icon (base64), and a textarea for the HTML/JS code. They can also upload an.htmlfile.- After filling the form, they click “Submit App”. The editor creates a
FormDataobject with all fields and sendsPOST /api/pluginsvia thesubmitAppfunction fromuseAppStore. - The backend saves the plugin with
approved = false. An administrator must approve it (or it can be auto‑approved if you trust all users). - Once approved, the plugin appears in the store for all users.
Example of a minimal plugin code:
<!DOCTYPE html>
<html>
<head><title>My Tool</title></head>
<body>
<h1>Hello Researcher</h1>
<button onclick="fetch('/api/articles/trending').then(r=>r.json()).then(console.log)">
Fetch trending papers
</button>
</body>
</html>
6. Data Flow Diagram (Installation & Launch)
sequenceDiagram
participant User
participant AppStoreWindow
participant useAppStore
participant Backend
participant DesktopEnvironment
participant AppRunnerWindow
User->>AppStoreWindow: Opens App Store
AppStoreWindow->>useAppStore: fetchPlugins()
useAppStore->>Backend: GET /api/plugins
Backend-->>useAppStore: list of approved plugins
useAppStore-->>AppStoreWindow: render plugins
User->>AppStoreWindow: clicks "Install" on Plugin X
AppStoreWindow->>useAppStore: installPlugin(pluginId)
useAppStore->>Backend: POST /api/plugins/user/install/{pluginId}
Backend-->>useAppStore: success
useAppStore-->>AppStoreWindow: update installed set
AppStoreWindow->>User: "Installed" button changes
User->>DesktopEnvironment: clicks desktop icon for Plugin X
DesktopEnvironment->>DesktopEnvironment: launchInstalledApp(plugin)
DesktopEnvironment->>Backend: GET /api/plugins/{id} (to ensure fresh code)
Backend-->>DesktopEnvironment: plugin code
DesktopEnvironment->>AppRunnerWindow: openWindow() with <AppRunner code=...>
AppRunnerWindow->>User: displays plugin in iframe
7. How the Visual No‑Code Builder Would Fit
A planned enhancement is a no‑code app builder inside the platform. Users could drag and drop components (buttons, forms, charts) to build the HTML/JS visually. The builder would generate the code, then submit it through the same AppEditor flow. The backend would treat it just like any other plugin.
This would greatly lower the barrier for researchers to create their own tools.
8. Summary of Frontend Plugin Architecture
| Layer | Component | Responsibility |
|---|---|---|
| UI | PluginStorePage |
Browse, install, submit plugins. |
| State | useAppStore |
Fetch plugins, manage installed IDs, submit new plugins. |
| API | plugins.ts |
Axios calls to /api/plugins/*. |
| Desktop | SmartGlassWorkspace |
Dynamically render desktop icons for installed plugins. |
| Execution | AppRunner |
Sandboxed iframe that safely renders the plugin code. |
| Window Manager | WindowContainer / DesktopWindow |
Opens a new window for each plugin instance. |
All components are fully integrated, reuse the existing glass‑morphism styling, and are completely production‑ready. Users can install any HTML/JS app with one click, run it in a secure isolated environment, and have it appear as a first‑class desktop application. 🚀
The ability to let researchers install and run external apps is what transforms your platform from a tool into an extensible ecosystem. By utilizing the right external modules, you can make this system not only powerful and flexible, but also robust and secure. Here are the key categories of modules you can integrate to build and enhance your plugin architecture.
🏗️ Core Execution & Security Dependencies
The foundation of your plugin system is how it safely runs and isolates third-party code.
- Secure Sandbox Execution: Instead of building an iframe sandbox from scratch, you can use a dedicated library.
@codefluss/sandboxis specifically designed for rendering user-generated HTML/CSS/JS code with secure iframe isolation and auto-resizing. This ensures that a plugin cannot access or interfere with your main application’s state or data. - Isolated Communication: For safe interaction between the plugin and your platform,
@kinveil/async-post-messageprovides a typed, promise-based wrapper around thepostMessageAPI, making cross-context communication straightforward and type-safe. - Secure Component Isolation: The
Web Componentsstandard, supported by all modern browsers, allows you to encapsulate plugin functionality in custom HTML elements with their own isolated DOM and styles, preventing conflicts with your app’s CSS.
🛠️ Building an Interactive Plugin Editor
To make plugin creation accessible, you can integrate a no‑code, drag-and-drop builder.
- Drag-and-Drop Mechanics: Libraries like
react-dndare well-suited for building complex drag-and-drop interfaces, allowing users to visually arrange components on a canvas. An alternative likednd-kitis known for being lightweight, modular, and offering a great developer experience. Thednd-kitlibrary is particularly praised for its modern architecture and ease of use. - Visual Editor Frameworks: For a complete “page builder” solution, you could embed Puck, an open-source, MIT-licensed, drag-and-drop visual editor for React that lets you work directly with your existing component library.
- Dynamic Component Loading: For advanced scenarios where plugins are not simple HTML/JS snippets but full React components,
dynamic-module-federationallows you to load and render remote components at runtime using Webpack 5’s Module Federation, only importing the parts that are needed.
📜 A Reference Architecture to Learn From
You can accelerate your design by studying established open-source projects.
- Framework-Agnostic Plugin Architecture: The Mycelia Plugin System is a standalone, framework-agnostic architecture that allows you to write domain logic once and use it across multiple frontend frameworks, including React.
- UI-Extension Framework:
react-extension-slotprovides a lightweight, Zustand-powered mechanism to dynamically register UI components (“extensions”) and render them in predefined “slots” within your application. - Slot-Based Layouts:
react-pluggable-layoutsoffers a powerful plugin architecture for React that enables the dynamic rendering of components in layout slots, inspired by the pluggable architecture of WordPress. - Schema-Based Plugins: The Orbis project demonstrates a unique approach: plugins ship JSON schemas, while all UI components are rendered through your application’s own component library. This guarantees a perfectly native and consistent look and feel for every plugin.
🔒 Security Scanning & Supply Chain Protection
To maintain the integrity of your App Store, you need a system to audit submitted plugins.
- Pre‑Installation Security Analysis: Tools like pkgwarden sit between the user and their package manager, performing a deep security audit on every package before it’s installed in your environment.
- Actionable Vulnerability Reporting:
secure-deps-checkgoes beyond the noisy output of standardnpm auditto provide a clear risk score, visual severity breakdown, and smart, actionable recommendations. - Advanced Heuristic Detection: TypoGuard proactively scans for typosquatting, suspicious installation scripts, and other malicious package heuristics, focusing on identifying risks before they become a problem.
- Production Reachability Analysis:
auditfiximproves uponnpm auditby analyzing which vulnerabilities are actually reachable in your production environment, reducing noise and focusing on real threats. - Proactive Malware Analysis:
mitnickfetches and analyzes package tarballs from the registry without ever executing their code, making it a safe and powerful tool for detecting malicious logic.
🚀 Future Extensibility with WebAssembly (Wasm)
For the future, you could explore WebAssembly (Wasm) to support plugins written in high-performance languages like C++, Rust, or Go.
- Browser-Based Execution: Wasm modules can be loaded and executed directly in the browser, providing near-native performance.
- Language Agnostic Ecosystem: This would allow developers to create plugins in the language they are most comfortable with.
- Enhanced Sandboxing: Wasm runs in a tightly controlled memory-safe sandbox, making it one of the most secure ways to execute untrusted code.
- Seamless Integration: JavaScript code can import and call functions exported by a Wasm module, and vice versa, enabling rich interoperability.
💎 Summary of External Modules for Your App Store
Here is a categorized summary of external modules to help you choose the right tools for each layer of your plugin system.
Core Execution & Security
- Secure Sandbox (iframe):
@codefluss/sandbox - Cross-Origin Communication:
@kinveil/async-post-message - Component Encapsulation: Web Components (Custom Elements)
Plugin Editor & Loading
- Drag-and-Drop:
react-dndordnd-kit - Visual Editor Framework:
Puck - Dynamic Component Loading:
dynamic-module-federation
Reference Architectures
- Framework-Agnostic:
Mycelia Plugin System - UI-Extension:
react-extension-slot - Slot-Based:
react-pluggable-layouts - Schema-Based:
Orbisproject
Security Scanning & Dependency Protection
- Pre-Install Auditing:
pkgwarden - Actionable Risk Reporting:
secure-deps-check - Heuristic & Typosquatting Detection:
TypoGuard - Production Reachability Analysis:
auditfix - Safe Package Analysis:
mitnick
The right combination of these external modules depends on your project’s specific requirements. If you’d like to dive deeper into any of these libraries or their integration, feel free to ask—I’m here to help.
Building an Interactive Plugin Editor – Complete Technical Guide
This document explains, in full detail, how to implement an interactive, no‑code plugin editor inside your research platform. Users will be able to drag and drop UI components (buttons, forms, charts, tables) onto a canvas, configure them via property panels, and generate a fully functional HTML/CSS/JS application – all without writing a single line of code. The generated app can then be saved, submitted to the App Store, and installed/run like any other plugin.
The guide covers three complementary technologies:
- Drag‑and‑drop mechanics – using
react-dndordnd-kitto build a visual canvas. - Visual editor frameworks – embedding a complete page builder like Puck.
- Dynamic component loading – using Webpack Module Federation to load remote React components as plugins.
All examples assume your existing frontend stack: React 19, TypeScript, Tailwind CSS, and the desktop window manager (WindowContainer, DesktopWindow).
1. Drag‑and‑Drop Mechanics: Building a Custom Visual Canvas
If you want a fully custom drag‑and‑drop editor, you can build it yourself using a dedicated library. The two most popular choices are:
| Library | Key Features | Best For |
|---|---|---|
| react-dnd | Powerful, highly customizable, uses HTML5 drag‑and‑drop or custom backends. | Complex multi‑drag, custom drag layers, touch support. |
| dnd-kit | Lightweight, modular, hooks‑based, excellent performance, first‑class accessibility. | Simpler implementations, better developer experience, modern React. |
1.1 Using dnd-kit (Recommended)
dnd-kit is the modern choice – it’s well‑typed, supports virtualized lists, and works seamlessly with React 18/19. Install it:
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
Basic Setup for a Component Palette
We want a sidebar with draggable components (e.g., “Button”, “Text Input”, “Card”) and a canvas where components can be dropped.
Step 1 – Draggable Component (Source Sidebar)
import { useDraggable } from '@dnd-kit/core';
function DraggableComponent({ type, label, icon }) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: type,
data: { componentType: type, defaultProps: getDefaultProps(type) },
});
const style = transform ? { transform: `translate3d(${transform.x}px, ${transform.y}px, 0)` } : undefined;
return (
<div ref={setNodeRef} style={style} {...listeners} {...attributes}
className="glass-card p-2 m-1 cursor-grab active:cursor-grabbing">
{icon} {label}
</div>
);
}
Step 2 – Droppable Canvas (Target)
import { useDroppable } from '@dnd-kit/core';
function EditorCanvas({ components, onAddComponent }) {
const { setNodeRef } = useDroppable({ id: 'canvas' });
return (
<div ref={setNodeRef} className="flex-1 min-h-[500px] bg-gray-50 dark:bg-gray-800/50 rounded-lg p-4">
{components.map((comp, idx) => (
<div key={idx} className="glass-card p-2 mb-2">
{comp.label}
</div>
))}
</div>
);
}
Step 3 – DnDContext Provider
import { DndContext, DragEndEvent } from '@dnd-kit/core';
function PluginEditor() {
const [components, setComponents] = useState([]);
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over?.id === 'canvas') {
const componentData = active.data.current;
if (componentData) {
setComponents(prev => [...prev, {
id: crypto.randomUUID(),
type: componentData.componentType,
props: componentData.defaultProps,
}]);
}
}
};
return (
<DndContext onDragEnd={handleDragEnd}>
<div className="flex h-full">
<div className="w-64 border-r p-2">
<DraggableComponent type="button" label="Button" icon="🔘" />
<DraggableComponent type="text" label="Text Input" icon="📝" />
<DraggableComponent type="card" label="Card" icon="🃏" />
</div>
<EditorCanvas components={components} />
</div>
</DndContext>
);
}
Step 4 – Property Panel (Inspector)
When a user clicks a component on the canvas, a side panel shows editable properties (e.g., text, color, size). This can be built with a simple form that updates the component’s props.
1.2 Generating Code from the Canvas
After the user finishes arranging components, you can serialise the canvas state into a JSON representation and then generate the final HTML/JS. For example:
function generateHtml(components: Component[]): string {
return `<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>My App</title><link href="https://cdn.jsdelivr.net/npm/tailwindcss@2/dist/tailwind.min.css" rel="stylesheet"></head>
<body class="p-4">
${components.map(comp => renderComponent(comp)).join('\n')}
<script>
// interactive logic
</script>
</body>
</html>`;
}
The generated HTML can then be submitted to the App Store using the existing submitApp function.
2. Visual Editor Frameworks: Embedding a Complete Page Builder
If you want a ready‑made, feature‑rich visual editor, you can embed an existing open‑source project. Puck is an excellent choice – it’s an MIT‑licensed, drag‑and‑drop visual editor for React that works directly with your own component library.
2.1 What is Puck?
Puck allows you to define your own React components (e.g., Button, Card, DataTable) and then let users drag them onto a canvas, edit their props via a built‑in form, and generate JSON that represents the page. It handles all the drag‑and‑drop, resizing, ordering, and persistence.
2.2 Installation
npm install @measured/puck
2.3 Basic Usage
Define your components:
import { Puck } from '@measured/puck';
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import { TextInput } from '@/components/ui/TextInput';
const config = {
components: {
Button: {
fields: { label: { type: 'text' }, variant: { type: 'select', options: ['primary', 'secondary'] } },
render: ({ label, variant }) => <Button variant={variant}>{label}</Button>,
},
Card: {
fields: { title: { type: 'text' }, content: { type: 'textarea' } },
render: ({ title, content }) => <Card title={title}>{content}</Card>,
},
// ... more components
},
};
Embed the Puck editor in a desktop window:
import { Puck } from '@measured/puck';
export function PluginEditorWindow() {
const [data, setData] = useState({});
return (
<div className="h-full w-full">
<Puck
config={config}
data={data}
onChange={setData}
onPublish={async (publishedData) => {
// Convert publishedData (JSON) to HTML and submit to App Store
const html = generateHtmlFromPuckData(publishedData);
await submitApp({ name, description, code: html });
}}
/>
</div>
);
}
2.4 Converting Puck JSON to HTML
Puck’s output is a JSON tree. You can render it on the server or in the browser. For plugin distribution, you can embed the same rendering logic inside a standalone HTML file or directly use Puck’s render function inside the AppRunner (if the plugin is also based on React). For simplicity, you can generate an HTML file that includes a small script to render the Puck‑compatible JSON using React DOM – but that would increase bundle size. Alternatively, you can create a generic “Puck runner” that loads the JSON and dynamically renders components (similar to a headless CMS).
3. Dynamic Component Loading: Webpack Module Federation
For advanced scenarios where plugins are not simple HTML/JS but full React components (or even whole applications) that are developed separately and loaded at runtime, you can use Webpack Module Federation.
3.1 What is Module Federation?
Module Federation is a Webpack 5 feature that allows a JavaScript application to dynamically load code from another build (a “remote”) at runtime. It is the technology behind micro‑frontends. With it, you could let developers build plugins as separate React components, bundle them, and host them on a CDN. Your main platform then loads them on demand when a user installs the plugin – no need to redeploy the core app.
3.2 How It Fits Your Plugin System
- Plugin Developer creates a new React component (or even a full app) and configures a Webpack build that exposes the component via Module Federation.
- Plugin is submitted to your backend, but instead of storing HTML, you store a URL pointing to the remote entry (e.g.,
https://plugins.myplatform.com/my-plugin/remoteEntry.js). - When a user runs the plugin, the frontend uses
import()to load the remote module and then renders it inside a window.
3.3 Example: Exposing a Remote Component
Plugin’s webpack.config.js:
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'myPlugin',
filename: 'remoteEntry.js',
exposes: {
'./Component': './src/PluginComponent',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
};
Plugin’s PluginComponent.tsx:
export default function PluginComponent({ initialData }) {
return <div className="glass-card p-4">Hello from plugin!</div>;
}
Main platform loading the plugin:
import React, { lazy, Suspense } from 'react';
// In your AppRunner or a dedicated loader:
const loadRemote = (url) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => {
// The remote exposes a global object 'myPlugin' (from the name)
const component = window.myPlugin.get('./Component');
resolve(component);
};
script.onerror = reject;
document.head.appendChild(script);
});
};
function PluginRunner({ pluginUrl }) {
const [Component, setComponent] = useState(null);
useEffect(() => {
loadRemote(pluginUrl).then(mod => setComponent(() => mod.default));
}, [pluginUrl]);
if (!Component) return <div>Loading plugin...</div>;
return <Component />;
}
Important considerations:
- Shared dependencies (React) must be singleton – you must ensure that the plugin uses the same React version as the host, or risk runtime errors.
- Security: Only load plugins from trusted sources (e.g., your own CDN). Use Subresource Integrity (SRI) checks.
- Performance: Lazy loading adds network overhead; cache remote entries.
- Module Federation works only with Webpack 5; if you use Vite, you need alternatives like
vite-plugin-federationor@originjs/vite-plugin-federation.
3.4 Alternatives to Module Federation
- SystemJS – Load any JavaScript module dynamically.
- ES dynamic import() – If plugins are bundled as standard ES modules hosted on a CDN.
- React.lazy – Only works with static imports, not dynamic URLs.
4. Combining All Three Approaches
| Use Case | Recommended Solution |
|---|---|
| Simple HTML/CSS/JS plugins | Existing AppRunner with iframe. |
| No‑code visual builder for non‑developers | Embed Puck; generate HTML and submit to App Store. |
| Custom drag‑and‑drop canvas | Build with dnd-kit + property panel. |
| Dynamic loading of React components | Webpack Module Federation (or vite-plugin-federation). |
The architecture is modular: the Plugin Store can host multiple plugin types:
type: "html"– simple HTML/CSS/JS (useAppRunner).type: "puck"– JSON layout that is rendered with Puck’s runtime (or converted to HTML).type: "react"– remote React component loaded via Module Federation.
The editor itself can also be integrated as a desktop app inside your workspace, allowing researchers to build plugins without leaving the platform.
5. Implementation Roadmap
| Phase | Task | Effort |
|---|---|---|
| 1 | Implement basic drag‑and‑drop canvas with dnd-kit and property panel (custom components). |
2‑3 days |
| 2 | Integrate Puck as an alternative editor, with ability to export to HTML and submit to App Store. | 1‑2 days |
| 3 | Add Module Federation support for loading external React components as plugins. | 3‑5 days (including security review) |
| 4 | Unify the plugin metadata: each plugin stores its type and source (HTML string, JSON, or URL). |
1 day |
| 5 | Update AppRunner to support different plugin types (iframe for HTML, React render for Module Federation). |
1‑2 days |
All these components seamlessly integrate with your existing desktop window manager, useAppStore hook, and backend APIs.
Conclusion
You now have a complete blueprint for building an interactive plugin editor inside your research platform:
- Drag‑and‑drop canvas using
dnd-kitfor a fully custom no‑code builder. - Embedded page builder using Puck for a ready‑made, production‑ready visual editor.
- Dynamic component loading using Webpack Module Federation for advanced, shareable React components.
This ecosystem empowers researchers to create, share, and run their own tools – all within a safe, sandboxed environment. 🚀
✅ Making the Plugin System Robust: Reliability, Security, and Scaling
After implementing the interactive plugin editor, you need to ensure it works flawlessly at scale—no lag, no crashes, and no security holes. This section dives into the known challenges, proven solutions, and real‑world best practices for each component.
📦 1. dnd‑kit – Building a High‑Performance Drag‑and‑Drop Canvas
dnd‑kit is the modern standard for React drag‑and‑drop, but three major pitfalls can ruin the user experience if ignored.
🔥 Challenge 1 – Performance with Large Lists
When the canvas contains hundreds of draggable items, the DOM can become overwhelmed.
The Solution: Virtual Scrolling
Only render what's visible. Combine dnd‑kit with @tanstack/react‑virtual to virtualize the list. The layout remains correct because dnd‑kit uses CSS transforms during drag, and virtualization libraries correctly measure item sizes after layout. For even smoother performance, consider using the @dnd‑kit/dom package, which was rewritten from scratch to support virtualized lists natively.
🔥 Challenge 2 – Reduced‑Motion Accessibility
Users who prefer reduced motion need a smooth, non‑jarring experience.
The Solution: Honor the prefers‑reduced‑motion Media Query
dnd‑kit disables all visual transitions (250ms slide‑back animations, swapping transitions) when the user has this preference set, keeping interactions accessible without jarring effects.
🔥 Challenge 3 – Maintaining Code Clarity as Complexity Grows
The three‑hook system (useDraggable, useDroppable, useSortable) can become unwieldy in large components.
The Solution: Move Complex Logic to Custom Hooks
Extract common patterns (droppable area logic, sortable item registration) into custom hooks. This keeps your components declarative and the business logic testable.
✅ Best Practices for dnd‑kit
| Aspect | Recommendation |
|---|---|
| API Choice | Use @dnd‑kit/core (stable) rather than @dnd‑kit/react (still pre‑major version). |
| Performance | Virtualize large lists and batch state updates with useMemo and useCallback. dnd‑kit uses SyntheticEvent listeners, which are more efficient than attaching handlers to every draggable item. |
| Accessibility | Always test with keyboard navigation (Tab, Space, Arrow keys). dnd‑kit includes built‑in keyboard sensors. |
🎨 2. Puck – Embedding a Full Visual Editor
Puck is the most mature open‑source visual editor for React, but it’s not a drop‑in solution.
🔥 Challenge 1 – Large Bundle Size
Puck bundles all its dependencies, which can add significant weight to your main bundle.
The Solution: Lazy Load the Editor
Only import the editor when the user opens the window. Use React.lazy and Suspense to load the editor on demand.
🔥 Challenge 2 – Complex Configuration
Defining every editable component and its fields can become verbose.
The Solution: Programmatic Component Registration
const registerComponent = (name: string, Component: React.ComponentType, fields: Field[]) => {
config.components[name] = { fields, render: Component };
};
🔥 Challenge 3 – Generated Code Maintainability
Puck outputs a JSON tree. If you generate HTML from it, the output can be messy and hard to debug.
The Solution: Use Puck’s render Function Directly
If the target environment also uses React, you can embed the Puck runtime and render the JSON directly, preserving component identity and props.
✅ Best Practices for Puck
| Aspect | Recommendation |
|---|---|
| Component Library | Register only the components you need; avoid registering the entire library. |
| Output Format | Keep the generated JSON in a version‑controlled store; regenerate HTML only at build time or when explicitly published. |
| Versioning | Tag each generated plugin with the version of Puck used. This avoids breakage when the editor is updated. |
⚛️ 3. Webpack Module Federation – Dynamic Component Loading
Module Federation is the most powerful way to load remote React components, but it introduces cross‑version compatibility risks.
🔥 Challenge 1 – React Version Mismatch
If the host uses React 19 and a remote plugin uses React 18, runtime errors can occur.
The Solution: Use React 19 for Both Ends
Module Federation Core now natively supports React 19 via the bridge-react module, which automatically detects the React version and creates the appropriate root renderer. Test‑driven examples exist for React 19 to React 19 sharing as well as for React 19 to React 18 isolation. Always keep both sides on React 19 for the smoothest experience.
🔥 Challenge 2 – Security Risks
Loading remote code exposes you to XSS, supply chain attacks, and MITM tampering.
The Solution: Multi‑Layer Defense
| Layer | Protection |
|---|---|
| Strict CSP | Prevent unsafe‑eval and restrict script sources. CSP is more effective than Module‑Federation‑specific measures. |
| Subresource Integrity (SRI) | Validate a pre‑generated hash of the remote module before execution. |
| Dependency Auditing | Run npm audit in CI to catch vulnerable dependencies. Module Federation’s own dependencies have been known to contain CVEs. |
🔥 Challenge 3 – Caching and Versioning
When a plugin updates, old federated modules may remain in the browser cache, causing mismatches.
The Solution: Append a version parameter to the remote entry URL (e.g., remoteEntry.js?v=1.2.3). Also implement a versioning system in your backend that stores the exact remote URL for each installed plugin.
✅ Best Practices for Module Federation
| Aspect | Recommendation |
|---|---|
| Singleton Dependencies | Set shared: { react: { singleton: true }, 'react-dom': { singleton: true } } to avoid duplicate instances. |
| Fallback URLs | Provide fallback URLs for critical remote modules to maintain functionality if the primary CDN is down. |
| Monitoring | Log federation loading failures to your backend; alert on repeated failures. |
🧰 4. Alternative Libraries and When to Use Them
| Library | Best For | When to Avoid |
|---|---|---|
| Craft.js | Building a completely custom, highly polished page editor from scratch. | You need an off‑the‑shelf solution; Craft.js is a toolkit, not a finished product. |
| react‑flow | Flowchart‑style editors where blocks connect with edges. | Standard form layouts or grid‑based pages. |
| zunokit‑builder | Generating clean, hand‑editable code from a Figma‑like editor. | You need a runtime‑only plugin system that doesn’t generate external code. |
| Mycelia Plugin System | Framework‑agnostic domain logic that works across React, Vue, and Svelte. | Your plugin logic is deeply tied to React‑specific features. |
| react‑extension‑slot | Lightweight extension–slot model where plugins register UI components in predefined slots. | You need complex canvas‑based layouts. |
🏛️ 5. Putting It All Together – A Cohesive Plugin System
To integrate all components smoothly:
- Submission Interface – Provide both a custom canvas (built with dnd‑kit) and a no‑code visual editor (Puck) as alternative ways to create plugins.
- Plugin Store – Store plugins as JSON (for Puck) or as remote URLs (for Module Federation). Use a unified
typefield. - Execution Environment – Use
AppRunnerwith iframes for HTML plugins; usePluginLoader(Module Federation) for React components. - Security Pipeline – Validate all submitted plugins with automated scans (static analysis, SRI hash generation) before they ever reach other users.
🧪 6. Testing for Production Readiness
| Test Type | Tools | Critical Cases |
|---|---|---|
| Stress / Load | Playwright + Lighthouse | Virtualized list dragging with 1000+ items; concurrent editors. |
| Cross‑Browser | BrowserStack, LambdaTest | Safari touch interactions, Firefox pointer events. |
| Security | npm audit, Snyk, OWASP ZAP |
Malicious plugin submission; remote module MITM. |
| Accessibility | axe, VoiceOver, NVDA | Keyboard‑only drag & drop; reduced motion mode. |
✅ 7. Final Recommendations
| Goal | Choice |
|---|---|
| Custom high‑performance canvas | Use @dnd‑kit/core with virtualized lists. |
| Complete off‑the‑shelf visual editor | Embed Puck and lazy‑load it. |
| Loading external React components | Use Webpack Module Federation with strict CSP and SRI. |
| Framework‑agnostic domain plugins | Consider Mycelia Plugin System for cross‑platform logic. |
By systematically addressing these challenges, you’ll build a plugin system that is not only feature‑rich but also reliable, secure, and ready for production.
Complete Frontend Implementation of App Installation & Execution (Local vs. Server Mode)
This document explains, in exhaustive detail, how the frontend of your research platform installs and runs third‑party apps (plugins) – and the critical distinction between local execution (within the user’s browser) and server execution (running on your backend infrastructure). You will learn the exact data flow, component architecture, security measures, and when to choose each mode.
1. Overview of App Installation Flow (Frontend)
The installation process is entirely frontend‑driven, using the existing useAppStore hook and API client (plugins.ts). No new backend endpoints are needed beyond those described earlier.
Step‑by‑Step Installation
| Step | Action | Component / Hook |
|---|---|---|
| 1 | User opens the App Store window. | PluginStorePage (or AppStorePage) |
| 2 | useAppStore.fetchPlugins() is called. |
useAppStore → GET /api/plugins |
| 3 | The store displays a grid of AppCard components. |
AppCard (shows name, description, install count) |
| 4 | User clicks “Install” on a card. | AppCard calls onInstall(plugin.id) |
| 5 | installApp(pluginId) sends POST /api/plugins/user/install/{pluginId}. |
useAppStore.installApp() |
| 6 | Backend records installation, increments install count, returns success. | – |
| 7 | useAppStore refetches the user’s installed plugins (or updates local state). |
fetchInstalled() |
| 8 | The installed plugin’s ID is added to the installedIds set. |
– |
| 9 | The “Install” button changes to “Uninstall” (or disappears). | AppCard re‑renders. |
| 10 | The desktop environment (SmartGlassWorkspace.tsx) listens to changes in installedPlugins. |
useEffect or Zustand store |
| 11 | For each new installed plugin, a dynamic desktop icon is rendered. | Mapping over installedPlugins array |
Code Snippet (Dynamic Desktop Icon Rendering)
// Inside SmartGlassWorkspace.tsx
const { installedPlugins, fetchInstalled } = useAppStore();
useEffect(() => {
fetchInstalled();
}, []);
return (
<div className="absolute top-12 left-4 space-y-4 z-10">
{/* Static desktop icons (already existing) */}
{/* ... */}
{/* Dynamic installed plugin icons */}
{installedPlugins.map(plugin => (
<div
key={plugin.id}
onClick={() => launchApp(plugin)} // see execution modes below
className="glass-card p-2 text-center w-20 cursor-pointer hover:bg-white/30 transition"
title={plugin.name}
>
{plugin.icon ? (
<img src={plugin.icon} className="w-8 h-8 mx-auto" alt="" />
) : (
<div className="text-2xl">📦</div>
)}
<div className="text-[10px] mt-1 truncate">{plugin.name}</div>
</div>
))}
</div>
);
Important: The installedPlugins list is fetched from the backend each time the desktop loads or when a new plugin is installed. The desktop icons are not stored in localStorage – they are derived directly from the backend data, ensuring consistency across devices and sessions.
2. Execution Modes: Local vs. Server
Every installed plugin can be executed in two fundamentally different ways, depending on its security requirements, computational needs, and data sensitivity.
| Mode | Where the code runs | Typical Use Case |
|---|---|---|
| Local (Client‑Side) | Inside a sandboxed <iframe> in the user’s browser |
Simple tools, data visualisation, offline‑first apps, no need for heavy compute. |
| Server‑Side | On the backend (e.g., Docker container, WebAssembly runtime) | Heavy computation (ML inference, large data processing), access to private platform data, secure API keys. |
The user does not choose the mode; the plugin developer declares it in the plugin manifest when submitting. The frontend then respects that declaration.
3. Local Execution Mode (Sandboxed Iframe)
This is the default and most common mode. The frontend uses the AppRunner component described earlier.
Architecture
- Component:
AppRunner.tsx– receivescode(HTML string) and renders it inside a<iframe>with thesandboxattribute. - Security: The iframe is isolated; it cannot access the parent DOM, cookies, or
localStorageof the main app (unlessallow-same-originis added – which we do for simplicity, but it still cannot access the parent DOM). - Resource Limits: The browser imposes its own limits on CPU, memory, and network per iframe. No additional server resources are consumed.
Data Flow (Local Execution)
- User clicks the desktop icon for a plugin.
launchApp(plugin)is called.- The function checks
plugin.executionMode === 'local'(or defaults to local). - It fetches the plugin’s
code(HTML string) from the backend (or uses the already cached version). - It calls
openWindow(plugin.name, <AppRunner code={plugin.code} />, options). - The
DesktopWindowrenders theAppRunnercomponent inside a resizable, draggable window. - The iframe writes the HTML string into its document, and the plugin runs.
Code for Launching a Local Plugin
function launchApp(plugin) {
if (plugin.executionMode === 'server') {
launchServerApp(plugin);
return;
}
// Local mode
openWindow(plugin.name, <AppRunner appId={plugin.id} code={plugin.code} />, {
icon: plugin.icon,
size: { width: 800, height: 600 },
});
}
Security Considerations for Local Mode
- Sandbox Attributes:
allow-same-origin allow-scripts allow-popups allow-forms.
Do not addallow-top-navigationorallow-modalsunless absolutely necessary. - Content Security Policy (CSP): Your main app’s CSP should restrict the iframe’s ability to load external scripts. Use
frame-src 'self'if plugins are served from the same origin. - Input Validation: The plugin code is stored as‑is; no further sanitisation is performed. You rely on the iframe sandbox for isolation.
Advantages & Limitations of Local Mode
| Pros | Cons |
|---|---|
| Zero server load | Cannot perform heavy computations (e.g., training ML models). |
| Works offline | Limited access to platform APIs (must use postMessage). |
| Simple to develop (plain HTML/JS) | Cannot directly access the platform’s database or services. |
| Instant launch | Potentially vulnerable to XSS if the iframe sandbox is misconfigured. |
4. Server Execution Mode
For plugins that need to do heavy lifting (e.g., summarising a 100‑page PDF, running a simulation, or accessing private backend services), the plugin runs on the backend. The frontend only receives the results, usually as JSON or a rendered UI stream.
How It Works (Backend Perspective)
- The plugin is not a simple HTML file; it is a small microservice (or a WebAssembly module) that the backend can execute on demand.
- When a user launches a server‑side plugin, the frontend sends a request to a backend endpoint (e.g.,
/api/plugins/run/{pluginId}) with optional input parameters. - The backend spins up a secure sandbox (Docker container, gVisor, or Wasm runtime), executes the plugin, and returns the output.
- The output can be:
- HTML – rendered inside an iframe (but the execution happened on the server).
- JSON – displayed in a generic viewer.
- A stream – for long‑running tasks, the frontend can show progress updates via WebSocket or Server‑Sent Events.
Frontend Implementation for Server Mode
We assume the plugin manifest includes an executionMode: 'server' and a backendUrl (or the backend knows how to route it). The frontend does not need to know the details; it just calls a standard endpoint.
Step 1 – Backend Endpoint
@PostMapping("/api/plugins/run/{pluginId}")
public ResponseEntity<?> runServerPlugin(@PathVariable String pluginId,
@RequestBody Map<String, Object> input,
@AuthenticationPrincipal User user) {
// 1. Verify user has permission to run this plugin.
// 2. Load plugin metadata (Docker image name, command, etc.).
// 3. Execute in a sandboxed environment (e.g., Docker).
// 4. Return the output.
}
Step 2 – Frontend API Call
Add a function to plugins.ts:
export const runServerPlugin = (pluginId: string, input: any) =>
api.post(`/plugins/run/${pluginId}`, input);
Step 3 – Launching a Server Plugin
When the user clicks the desktop icon:
async function launchServerApp(plugin) {
// Open a loading window first (optional)
const windowId = openWindow(plugin.name, <div className="p-4">Loading plugin...</div>);
try {
const result = await runServerPlugin(plugin.id, { /* optional user input */ });
// Replace the window content with the result
updateWindow(windowId, <div className="p-4" dangerouslySetInnerHTML={{ __html: result.html }} />);
} catch (err) {
updateWindow(windowId, <div className="p-4 text-red-500">Plugin execution failed.</div>);
}
}
Alternatively, the server could return a URL that embeds a live view (e.g., a Jupyter notebook), and the frontend opens an iframe pointing to that URL.
Security Considerations for Server Mode
- Sandboxing: The plugin runs in an isolated container (Docker) or WebAssembly sandbox. It should not have access to the host file system, network (except via controlled APIs), or other users’ data.
- Resource Limits: Set CPU, memory, disk, and time limits for each execution to prevent denial‑of‑service.
- Authentication: The plugin must receive a short‑lived, scoped token that only grants access to the user’s own data (e.g., via
user_id). Never give the plugin full backend access. - Input Validation: Sanitise all user inputs passed to the plugin.
Advantages & Limitations of Server Mode
| Pros | Cons |
|---|---|
| Can run heavy computations (ML, data analysis) | Uses server resources; may incur cost. |
| Can access platform databases and services (with proper permissions) | Requires internet connection. |
| Output can be cached and shared | Higher latency (network round‑trip). |
| Plugin code is hidden (intellectual property protection) | More complex to develop (requires Docker/Wasm knowledge). |
5. Hybrid Approach: When to Use Which
| Plugin Type | Recommended Mode | Reason |
|---|---|---|
| Simple data visualisation (Chart.js, D3) | Local | Lightweight, runs in browser. |
| Offline todo list / notes | Local | Works without internet. |
| PDF summariser (AI) | Server | Requires LLM, heavy compute. |
| Grant proposal generator | Server | Accesses backend database, LLM API. |
| Interactive graph explorer (ForceGraph) | Local | Runs entirely in the browser. |
| Real‑time collaboration whiteboard | Server (signalling) + Local (rendering) | Hybrid: server for signalling, client for canvas. |
The plugin developer chooses the mode when submitting the app. The frontend handles both transparently.
6. Complete Frontend Flow Diagram (Installation & Execution)
sequenceDiagram
participant User
participant AppStore
participant useAppStore
participant Backend
participant Desktop
participant WindowManager
participant AppRunner
participant ServerSandbox
User->>AppStore: Opens App Store
AppStore->>useAppStore: fetchPlugins()
useAppStore->>Backend: GET /api/plugins
Backend-->>useAppStore: plugin list
useAppStore-->>AppStore: render cards
User->>AppStore: clicks Install on plugin X
AppStore->>useAppStore: installPlugin(id)
useAppStore->>Backend: POST /api/user/install/{id}
Backend-->>useAppStore: success
useAppStore->>Desktop: update installedPlugins (via context/global state)
Desktop->>Desktop: re‑render desktop icons
User->>Desktop: clicks installed plugin icon
alt Plugin mode = local
Desktop->>AppRunner: create window with <AppRunner code=...>
AppRunner->>AppRunner: runs in sandboxed iframe
AppRunner-->>User: shows plugin UI
else Plugin mode = server
Desktop->>ServerSandbox: POST /api/plugins/run/{id}
ServerSandbox->>Backend: execute container/wasm
Backend-->>ServerSandbox: result (HTML/JSON)
ServerSandbox-->>Desktop: return output
Desktop->>WindowManager: update window content with result
WindowManager-->>User: shows plugin output
end
7. Conclusion
The frontend of your research platform provides a unified installation experience for both local and server‑side plugins:
- Installation is identical: one click, and the plugin appears on the desktop.
- Execution mode is determined by the plugin’s manifest and is completely transparent to the user.
- Local mode uses a sandboxed iframe for security and offline capability.
- Server mode uses backend sandboxes (Docker/Wasm) for heavy compute and access to platform data.
All code components (AppStorePage, useAppStore, AppRunner, and the window manager) are already integrated. Adding server‑side support requires only a new endpoint and a small modification to launchApp to distinguish between modes.
This architecture gives you the flexibility to support any kind of plugin – from simple HTML widgets to full‑fledged AI agents – while maintaining a consistent, user‑friendly desktop experience. 🚀
Floating Window Manager UI – Complete Frontend Architecture
The desktop environment of your research platform is built around a window manager that allows users to open, move, resize, minimize, maximize, and close application windows – just like a traditional operating system (Windows, macOS, Linux). This is achieved through a combination of React context, custom hooks, third‑party libraries (react-draggable, react-resizable, framer-motion), and careful state management.
This document explains every UI component of the window manager, how they work together, and how they create a truly floating, desktop‑like experience.
1. High‑Level Architecture of the Window Manager
The window manager is composed of four main parts:
| Component | Role | Technology |
|---|---|---|
| WindowContainer | Global state (which windows are open, their positions, sizes, z‑index, minimized/maximized). | React Context API |
| DesktopWindow | Individual window – draggable, resizable, with title bar and controls. | react-draggable, react-resizable, framer-motion |
| Taskbar | Bottom bar showing Start menu, open window icons, system tray (clock). | Custom React component |
| StatusBar | Optional top bar with system indicators. | Custom React component |
All these components are integrated inside SmartGlassWorkspace.tsx, which also renders desktop icons for launching apps.
2. WindowContainer – The Central State Manager
WindowContainer.tsx is a React Context provider that holds the authoritative list of open windows (windows), the currently active window (activeWindowId), and a counter for z‑index (nextZIndex). It exposes actions to manipulate windows:
| Action | Purpose |
|---|---|
openWindow(title, content, options) |
Creates a new window, adds to state, sets as active, increments z‑index. |
closeWindow(id) |
Removes the window from state. |
minimizeWindow(id) |
Sets isMinimized: true – window disappears from screen but remains in taskbar. |
maximizeWindow(id) |
Sets isMaximized: true – expands to full screen (with backdrop). |
restoreWindow(id) |
Reverts minimized or maximized state. |
bringToFront(id) |
Increments the window’s z‑index to the highest value. |
updateWindow(id, updates) |
Partially updates position, size, etc. |
The state is stored in a useReducer or useState – but the key is that every change triggers a re‑render of all windows (which is acceptable because the number of open windows is small). For better performance, each DesktopWindow can be memoized.
Why Context?
The window state must be accessible from any component (e.g., the Taskbar needs to know which windows are open, and any part of the app may want to open a new window). Context avoids prop drilling.
3. DesktopWindow – The Floating, Draggable, Resizable Window
DesktopWindow.tsx renders a single window. It receives its windowId and retrieves its data from the context. It uses three key libraries:
react-draggable– makes the window movable by dragging the title bar.react-resizable– adds resize handles to all four edges and corners.framer-motion– provides smooth transitions (fade/scale) when opening/closing windows (optional, but used for animations).
3.1 Dragging Logic
The entire window (except the resize handles) is wrapped in a <Draggable> component. The handle prop restricts dragging to the title bar (by CSS class .window-drag-handle). The position prop is controlled by the component’s local state, and on drag stop, the new position is reported back to the global context via updateWindow.
<Draggable
nodeRef={nodeRef}
handle=".window-drag-handle"
position={position}
onStop={(e, data) => {
setPosition({ x: data.x, y: data.y });
updateWindow(windowId, { position: { x: data.x, y: data.y } });
}}
bounds="body"
>
<div ref={nodeRef} className="fixed glass-card ..." style={{ zIndex: window.zIndex }}>
{/* window content */}
</div>
</Draggable>
3.2 Resizing Logic
Inside the draggable wrapper, we place a <Resizable> component from react-resizable. It adds invisible handles on the edges. The size is local state; on resize stop, it updates the global context.
<Resizable
width={size.width}
height={size.height}
onResize={(e, { size: newSize }) => setSize(newSize)}
onResizeStop={(e, { size: newSize }) => updateWindow(windowId, { size: newSize })}
minConstraints={[300, 200]}
maxConstraints={[1200, 800]}
>
<div style={{ width: size.width, height: size.height }}>
<div className="flex-1 overflow-auto p-3">{window.content}</div>
</div>
</Resizable>
3.3 Minimized and Maximized States
- Minimized: The window is not rendered at all; the
DesktopWindowreturnsnull. The window remains in thewindowsstate withisMinimized: true. The Taskbar shows its icon. - Maximized: The window is rendered inside a full‑screen overlay (with a backdrop blur). The draggable and resizable behaviours are disabled. Clicking the “restore” button calls
restoreWindow.
3.4 Window Header (Title Bar)
The header contains:
- An icon (optional) – from the app or a default.
- The window title.
- Three buttons: Minimize, Maximize/Restore, Close. Each calls the corresponding context action.
The header also has the window-drag-handle class, which makes it draggable.
3.5 Z‑Index (Focus)
Each window has a zIndex value stored in the global state. When a user clicks anywhere on a window, bringToFront is called, which sets that window’s z‑index to nextZIndex and increments nextZIndex. The highest z‑index window appears on top. This mimics standard window managers.
4. Taskbar – The Bottom Bar with Start Menu and Window Icons
Taskbar.tsx is a fixed‑position bar at the bottom of the screen. It contains three areas:
- Start button (left) – opens a modal listing all available apps. Clicking an app launches it via
openWindow. - Window list (middle) – shows icons/titles for all open (non‑minimized) windows. Clicking a window brings it to front (or restores it if minimized). Active window is highlighted.
- System tray (right) – shows the current time (updated every second) and optional mock icons (Wi‑Fi, battery, volume).
The taskbar also listens to the window manager context (via useWindowManager) to know which windows are open and which one is active.
Code Snippet (Simplified)
<div className="fixed bottom-0 left-0 right-0 glass-card rounded-t-2xl flex items-center justify-between px-3 py-2 z-50">
<button onClick={() => setShowStartMenu(!showStartMenu)}>
<Home className="w-5 h-5" />
</button>
<div className="flex gap-1 max-w-[60%] overflow-x-auto">
{windows.filter(w => !w.isMinimized).map(win => (
<button key={win.id} onClick={() => bringToFront(win.id)} className={activeWindowId === win.id ? "bg-primary/20" : ""}>
{win.title}
</button>
))}
</div>
<span>{formatTime(currentTime)}</span>
</div>
5. StatusBar – Optional Top Bar
StatusBar.tsx is a simple top bar that can show system status (Wi‑Fi, battery, volume) and the platform name. It is purely decorative and does not interact with the window manager state. It uses the same glass‑morphism styling.
6. Desktop Icons – Launching Apps
In SmartGlassWorkspace.tsx, we render two sets of icons:
- Static desktop icons – pre‑defined in
DESKTOP_APPS(e.g., Messages, Articles, App Store). - Dynamic icons – for installed plugins (fetched via
useAppStore).
Each icon is a <div> with a glass‑card style. When clicked, it calls launchApp, which in turn calls openWindow with the appropriate component. This creates a new floating window.
7. Animations and Transitions
- Window opening/closing:
framer-motionis used to animate theDesktopWindowmount/unmount. A scale + fade effect makes the experience smoother. - Taskbar start menu: Appears with a grow + fade animation.
- Window maximize: A smooth transition when the full‑screen overlay appears (optional).
All animations are configured with spring physics for a natural feel.
8. Persistence (Optional but Recommended)
To preserve the window layout after a page reload, you can save the windows state to localStorage (or a backend database) whenever it changes. On app start, you restore the saved layout. This allows users to reopen their workspace exactly as they left it.
Implementation: In WindowContainer, add a useEffect that serialises windows to localStorage (excluding content functions). On initialisation, read from localStorage and restore. However, be careful not to store React components – you should store only metadata (id, title, position, size, minimized, maximized, zIndex). The actual content (React component) must be re‑created from the app registry. This is why the openWindow function expects a component type (or a key to look up a component). For persistent workspaces, you would store a reference to the app ID, not the JSX.
9. Putting It All Together – A Concrete Example
When a user clicks the “Messages” desktop icon:
launchAppis called with the app descriptor fromDESKTOP_APPS.openWindow('Messages', <MessagesPage />, { icon: <MessageSquare />, size: { width: 900, height: 650 } })is invoked.WindowContainercreates a new window object, adds it to state, setsactiveWindowId, and incrementsnextZIndex.- The
DesktopWindowcomponent for that ID renders a draggable, resizable window. - The Taskbar shows a new button for “Messages”.
- The user can drag, resize, minimise, maximise, and close the window – all actions update the global state and re‑render the affected components.
Because the state is central, opening 10 windows is just as easy as opening one. The system naturally supports multiple overlapping windows, each with its own independent position and size.
10. Summary of UI Parts and Their Responsibilities
| Part | Responsibility |
|---|---|
| WindowContainer | Holds the single source of truth for windows. |
| DesktopWindow | Renders a single window, handles drag/resize, forwards user interactions to the container. |
| Taskbar | Shows all open windows, allows switching and restoring minimised windows, provides Start menu. |
| StatusBar | Displays system information (cosmetic). |
| Desktop icons | Provide one‑click launching of apps. |
useWindowManager hook |
Gives any component access to the window state and actions. |
All components are styled with glass‑morphism (backdrop blur, rounded corners, light borders) and support dark mode out of the box. The result is a highly flexible, desktop‑like environment that runs entirely inside a web browser.
11. Performance Considerations
- Memoisation:
DesktopWindowshould be wrapped inReact.memoto avoid unnecessary re‑renders when other windows change (only its own props change). Thewindowsarray changes when a new window opens or closes, but each window’s individualisMinimized,position, etc. remain stable. - Virtualisation: Not needed because the number of open windows is inherently limited by screen size (usually <20). If a user somehow opens hundreds, the window manager would degrade; but that’s an unlikely usage pattern.
- Resizing:
react-resizableattaches resize listeners only while the user is dragging; it does not cause constant re‑renders.
12. Conclusion
The floating window manager is a robust, fully functional desktop environment built with modern web technologies. It gives your research platform a distinctive, professional look and feel, while remaining highly extensible. Every part – from the central context to the draggable windows and the taskbar – works together to provide a seamless experience that users will immediately recognise and appreciate.
By combining react-draggable, react-resizable, and a well‑designed React context, you have created a UI that rivals native desktop applications. This architecture is already integrated into your project; you can continue to add more apps and features without modifying the window manager core. 🚀
Complete Explanation of All Remaining Modules in the Research Platform
This document provides an exhaustive, self‑contained explanation of every major feature module of your research platform, beyond those already detailed in earlier answers (Paper Collections, Reference Manager, Preprint Submission, Provenance, Unified Inbox, Scheduled Messages, Global Search, Admin Dashboard, Offline Support, App Store, Plugin Editor, and the Window Manager). Each module is described with its purpose, user journey, key frontend components, backend endpoints, data flow, and integration points.
1. Authentication & User Management
Purpose
Secure user registration, login, 2FA, password reset, profile management, and follow system.
User Journey
- Register – User provides username, email, password, security question.
- Login – Username/password; optional 2FA if enabled.
- Forgot password – Security question challenge → reset password.
- Profile – View/edit profile (full name, bio, affiliation, ORCID, Google Scholar, avatar).
- Follow – Follow/unfollow other users; see followers/following lists.
- Contacts – List of followed users with online status.
Frontend Components
LoginPage.tsx,RegisterPage.tsx,ForgotPasswordPage.tsxProfilePage.tsx,SettingsPage.tsx(profile tab)TwoFactorSetupModal.tsxuseAuth,useRegister,usePasswordReset,useProfile,useSecuritySettings
Backend Endpoints
POST /api/login,/api/login/2fa,/api/logout,/api/token/refreshPOST /api/registerGET /api/users/me,PUT /api/users/mePOST /api/users/me/avatarPUT /api/users/me/password,PUT /api/users/me/emailPOST /api/forgot-password,POST /api/reset-passwordGET /api/users/me/2fa/status,POST /api/users/me/2fa/enable,POST /api/users/me/2fa/verify,POST /api/users/me/2fa/disablePOST /api/users/{username}/follow,DELETE /api/users/{username}/followGET /api/users/{username}/followers,/following
Data Flow
- JWT stored in localStorage; refresh token in httpOnly cookie.
- Profile data cached in
UserContext.
2. Articles & Content Management
Purpose
Create, publish, edit, search, like, comment, and analyse academic articles. Supports drafts, tags, and citation exports.
User Journey
- Create article – Fill title, abstract, body (rich text), tags, status (draft/published).
- Search – By title, author, tag, date range, status (admin).
- View – Display with author, metadata, like button, comment section.
- Analytics – Views over time, likes, comments (own articles).
- Recommendations – Graph‑based co‑citation recommendations.
Frontend Components
ArticlesPage.tsx,ArticleDetailPage.tsx,CreateArticlePage.tsxArticleCard.tsx,ArticleBody.tsx,CommentSection.tsxuseArticles,useArticle,useSearch,usePaperAnalytics
Backend Endpoints
- CRUD:
/api/articles,/api/articles/{id} - Search:
/api/articles/search - Like:
POST /api/articles/{id}/like,DELETE /api/articles/{id}/like - Comments:
GET/POST /api/articles/{id}/comments,DELETE /api/comments/{commentId} - Analytics:
GET /api/articles/{id}/analytics - Recommendations:
GET /api/articles/{id}/graph-recommendations - Trending:
GET /api/articles/trending
Integration
- Article cards appear in dashboard feed, search results, and user profiles.
- Like count and comment count update in real time (via WebSocket or optimistic UI).
3. Messaging (Private & Group Chats)
Purpose
Real‑time private and group messaging with rich media (attachments, voice, stickers, polls), reactions, reply threads, scheduled messages, search, and end‑to‑end encryption (E2EE) stub.
User Journey
- Private chat – Select a user from contacts, send text, images, files, voice messages, stickers.
- Group chat – Create group, add members, set admin roles.
- Features – Edit/delete messages, react with emojis, reply to specific messages, forward to other chats, schedule messages for later, pin important messages, mute group, see typing indicators, read receipts, message threads.
Frontend Components
ChatSidebar.tsx,ChatArea.tsx,MessageBubble.tsxMentionInput.tsx,ReactionPicker.tsx,StickerPicker.tsx,VoiceRecorder.tsxScheduleModal.tsx,ScheduledMessagesPanel.tsxThreadPanel.tsx,GroupInfoPanel.tsx,GroupMembersModal.tsxuseMessaging,useWebSocket
Backend Endpoints
- Private:
/api/conversations/*,/api/conversations/{username}/messages/* - Group:
/api/groups/*,/api/groups/{groupId}/messages/* - Polls:
/api/polls/* - Scheduled messages:
/api/messages/scheduled - Forward:
/api/messages/{id}/forward - Threads:
/api/messages/{id}/thread
WebSocket Events
new_message,new_group_message,typing,message_status,reaction,message_edited,message_deleted,message_pinned
Data Flow
- Optimistic sending with temporary ID; replaces with real message ID after server confirmation.
- WebSocket pushes updates to all participants in a conversation or group.
4. Collaborative Editor (Scientific Editor)
Purpose
Real‑time collaborative document editing with rich formatting, LaTeX math, code blocks, tables, task lists, live preview, export to PDF/DOCX/LaTeX/HTML, and offline support.
User Journey
- Open a document – see cursor positions of other collaborators.
- Edit simultaneously – changes appear instantly (Yjs).
- Use toolbar for formatting, insert math, code blocks, tables.
- Insert citations from reference manager via picker.
- Preview live HTML with rendered math.
- Export as PDF, DOCX, LaTeX, or HTML.
Frontend Components
ScientificEditor.tsx(orCollaborativeAuthoringStudio.tsx)ReferencePicker.tsx,CitationPicker.tsxuseCollaborativeEditor
Backend Endpoints
- Yjs WebSocket server (port 9093) – separate from main backend.
- Document metadata:
GET /api/documents/{docId}/references, etc.
Offline Support
- IndexedDB persistence (
y-indexeddb) saves changes when offline; syncs when connection returns.
5. Lab Inventory
Purpose
Manage chemical inventory with barcode scanning, low‑stock alerts, expiry tracking, NFPA diamond visualization, and compatibility checking.
User Journey
- Add chemical – Enter name, formula, CAS, location, quantity, unit, min stock, expiry date, NFPA ratings.
- Scan barcode – Use camera to scan chemical barcode; pre‑fill fields.
- List / search – Filter by location, low stock, expiring soon.
- View alerts – Dashboard shows low‑stock and expiring chemicals.
- Check compatibility – Compare two chemicals to see if they can be stored together.
Frontend Components
LabInventoryDashboard.tsxChemicalFormModal.tsx,BarcodeScannerModal.tsxNFPA704Diamond.tsx,ChemicalDetailPanel.tsxuseLabInventory
Backend Endpoints
/api/inventory/chemicals/*(CRUD)/api/inventory/scan(barcode lookup)/api/inventory/alerts/*,/api/inventory/stats/api/inventory/chemicals/{id}/incompatibilities
Data Flow
- Alerts are generated periodically by a backend scheduled job; frontend polls every minute (or via WebSocket) for new alerts.
6. Peer Review
Purpose
Anonymous paper submission and review system. Reviewers can claim submissions, provide scores and comments, and submit recommendations. Authors see aggregated feedback.
User Journey (Author)
- Submit a paper (title, abstract, manuscript file) anonymously.
- Track submission status (pending, under review, reviewed).
- Receive reviews (scores, comments) when complete.
User Journey (Reviewer)
- Browse open submissions in their area of expertise.
- Claim a review.
- Submit review (scores for originality, methodology, clarity, significance, literature review, reproducibility; confidential comment to editor; comment to author; recommendation).
Frontend Components
PeerReviewDashboard.tsxReviewForm.tsx,ReviewerCredentialsForm.tsxusePeerReview
Backend Endpoints
/api/peer-review/submissions/*(list, get, submit, claim)/api/peer-review/my-reviews,/api/peer-review/my-credentials/api/peer-review/submissions/{id}/review(submit review)- Admin:
/api/admin/peer-review/*
Integration
- Notifications sent when a review is claimed or completed.
- Reviewer credentials (ORCID, expertise) stored per user.
7. Gap Analysis & Knowledge Graph
Purpose
Visualise research gaps as a knowledge graph (nodes = papers/concepts/methods/findings/gaps/hypotheses; edges = relationships). AI‑powered analysis identifies new gaps and generates hypotheses.
User Journey
- Explore graph – Interactive force‑directed graph (pan, zoom, click nodes).
- Filter – By node type, search by label.
- Add nodes/edges – Manually create new concepts or relationships.
- AI analysis – Enter a research topic, run “Gap Analysis” to identify missing connections and generate hypotheses.
- Import/export – Export graph as JSON; import from external sources.
Frontend Components
GapAnalysisDashboard.tsx,KnowledgeGraphCanvas.tsxGraphSearchBar.tsx,GraphLegend.tsxuseGapAnalysis
Backend Endpoints
/api/gap-analysis/nodes/*,/api/gap-analysis/edges/*(CRUD)POST /api/gap-analysis/analyze– AI gap detectionPOST /api/gap-analysis/generate-hypothesis– AI hypothesis generationGET /api/gap-analysis/export,POST /api/gap-analysis/import
Data Flow
- Graph data is stored in PostgreSQL (nodes and edges tables) and served as JSON.
- The frontend uses
react-force-graph-2dfor rendering.
8. Grant Assistant
Purpose
Help researchers track grant deadlines, analyse RFPs, and generate draft proposals using AI.
User Journey
- Add a grant – Name, deadline, amount, status.
- Upload RFP – Paste text or upload PDF; AI extracts requirements, eligibility, submission guidelines.
- Generate draft – AI writes a proposal skeleton based on RFP and researcher’s previous work.
- View deadlines – Calendar view of upcoming deadlines.
Frontend Components
GrantAssistantDashboard.tsxRfpIngestionPanel.tsx,ProposalEditor.tsxuseGrantAssistant
Backend Endpoints
/api/grants/*(CRUD, deadlines)POST /api/grants/generate– AI draft generationPOST /api/grants/analyze– RFP analysis
Integration
- Generated drafts can be opened directly in the Collaborative Editor for editing.
9. Data Hub (Research Objects, Protocols, Preregistrations)
Purpose
Store and share research objects (datasets, code, presentations), lab protocols, and preregistrations with DOI assignment.
User Journey (Research Objects)
- Upload – File (any type) + metadata (title, description, license).
- Get DOI – Once submitted, a DOI is minted (DataCite or similar).
- Download – Public objects can be downloaded; download count tracked.
- List – User’s objects with filters (type, date).
User Journey (Protocols)
- Create protocol – Title, description, steps (each step can be checked off).
- Fork – Copy an existing protocol to adapt.
- Run mode – Mobile‑friendly interface to follow steps interactively.
User Journey (Preregistrations)
- Choose template – OSF Standard, AsPredicted, Clinical Trials, Qualitative.
- Fill sections – Title, design plan, sampling plan, variables, analysis plan.
- Submit – Get a timestamped DOI.
Frontend Components
DataHubPage.tsxResearchObjectList.tsx,ObjectUploader.tsxProtocolsWorkspace.tsx,StepEditor.tsxPreregistrationForm.tsxuseResearchObjectRepository,useProtocolsWorkspace,usePreregistration
Backend Endpoints
- Research objects:
/api/repository/objects/* - Protocols:
/api/protocols/*,/api/protocols/{id}/steps/* - Preregistrations:
/api/preregistrations/*
10. Projects & Task Management (Kanban)
Purpose
Manage research projects with Kanban boards (backlog, in progress, review, done), task assignment, dependencies, and outputs.
User Journey
- Create project – Name, description, members.
- Add tasks – Title, description, assignee, due date, priority, tags.
- Move tasks – Drag and drop between columns.
- Link outputs – Attach articles, datasets, or other research objects.
- Project dashboard – See activity log, member list, progress.
Frontend Components
ProjectWorkspacePage.tsx,ProjectMembersPanel.tsxProjectTasksPanel.tsx,TaskCard.tsxuseProjectSpace,useProjectTaskManager
Backend Endpoints
/api/projects/*(CRUD, members, outputs)/api/projects/{id}/tasks/*(CRUD, move column)
11. Events & Networking
Purpose
Create and manage research events (conferences, lab meetings, deadlines). RSVP, set reminders, export to iCal.
User Journey
- Create event – Title, description, start/end time, location (virtual/physical), max attendees.
- RSVP – Choose “Going”, “Interested”, “Not Going”.
- Set reminder – Get email notification before event.
- Export calendar – Download .ics file to import into Google Calendar/Outlook.
- Discover events – Search upcoming events by keyword or category.
Frontend Components
EventPage.tsx,EventCard.tsx,CreateEventModal.tsxuseEventHosting
Backend Endpoints
/api/events/*(CRUD, search)/api/events/{id}/rsvp,/api/events/{id}/reminders/api/events/calendar.ics– iCal export
12. Voice Assistant & Speech Recognition
Purpose
Allow users to interact with the platform using voice commands (speech‑to‑text) and hear responses (text‑to‑speech). Supports wake word, language selection, and custom grammar.
User Journey
- Tap microphone – Start listening; speak a query (e.g., “Open Messages”, “Search for machine learning articles”).
- Assistant responds – Text‑to‑speech reads the answer.
- Wake word – Say “Hey Assistant” to activate hands‑free.
- Settings – Choose language, voice, pitch, speed, enable auto‑speak.
Frontend Components
VoiceAssistantWidget.tsx(floating button)VoiceSettingsModal.tsxuseVoiceAssistant,useSpeechRecognition
Backend Endpoints
/api/users/me/voice-settings(get/update)/api/voice/voices(list available TTS voices)/api/voice/tts/test(synthesise test speech)
Integration
- Voice commands can trigger navigation, search, or opening apps via the command palette.
13. Smart Notifications
Purpose
Intelligent notification delivery that respects user context (deep work, focus, quiet hours) and batches low‑priority notifications.
User Journey
- Receive notification – Bell icon shows badge.
- Settings – Configure deep work allowlist, batch interval, quiet hours, auto‑speak.
- Batch review – Low‑priority notifications grouped into a single summary.
- Escalation – Unread critical notifications are resent after timeout.
Frontend Components
SmartNotificationCenter.tsx,NotificationItem.tsxuseSmartNotifications
Backend Endpoints
/api/notifications/*(CRUD)/api/users/me/notification-settings(get/update)/api/events(SSE) – real‑time notification stream
Data Flow
- Notifications are sent via WebSocket (or SSE) to the active session; if offline, they are queued and delivered on next login.
14. RAG Assistant (Research Assistant)
Purpose
AI‑powered question‑answering over the user’s indexed documents (uploaded PDFs, articles, notes). Uses retrieval‑augmented generation (RAG).
User Journey
- Upload documents – PDF, DOCX, TXT, HTML; they are chunked and embedded.
- Ask question – Type query; system retrieves relevant chunks and generates answer with citations.
- View sources – Click citation to see exact excerpt.
- Manage library – Delete documents, clear conversation history.
Frontend Components
ResearchAssistantPage.tsx(orRagAssistantPanel.tsx)RagChatArea.tsx,RagCitationBlock.tsxuseRagSystem
Backend Endpoints
POST /api/ai/rag-chat,POST /api/ai/chatPOST /api/documents/{docId}/rag-index,GET /api/documents/rag-list,DELETE /api/documents/{docId}/rag-deleteGET /api/ai/conversation,DELETE /api/ai/conversation
Offline Support
- Embeddings can be generated client‑side using
@xenova/transformers(WebWorker) for offline use.
15. User Follow System & Activity Feeds
Purpose
Follow other researchers, see their activity (published articles, comments, likes) in a personalized feed.
User Journey
- Follow – Click “Follow” on a user’s profile.
- Feed – See recent articles and activities of followed users on the dashboard.
- Unfollow – Remove from feed.
Frontend Components
FollowButton.tsx,FollowersList.tsx,FollowingList.tsxActivityFeed.tsxusePublicProfile
Backend Endpoints
POST /api/users/{username}/follow,DELETE /api/users/{username}/followGET /api/users/{username}/followers,/followingGET /api/users/me/feed(aggregated activity)
16. Team / Group Management (Outside Messaging)
While groups are already covered in messaging, this module adds project‑oriented teams with separate permissions and shared resources (e.g., common collections, references, protocols).
Frontend Components
TeamsPage.tsx,TeamMembersPanel.tsxuseTeams(similar touseProjectSpace)
Backend Endpoints
/api/teams/*(CRUD, members, roles)
(Note: This may be merged with Projects module.)
17. Export & Reporting
Purpose
Export analytics data (views, likes, comments) as CSV/JSON, and generate reports for grants or annual reviews.
User Journey
- Analytics export – On dashboard, click “Export CSV” to download article statistics.
- Report generation – Select date range and metric types; generate PDF/HTML report.
Frontend Components
ExportButton.tsx,ReportGenerator.tsxuseSubmissionExport(already exists)
Backend Endpoints
GET /api/users/me/analytics/export/csvPOST /api/ai/export(document export)GET /api/admin/stats/export/json(admin)
18. Security Modules (2FA, Password Reset, Session Management)
These are part of Authentication & User Management but worth detailing:
- 2FA – TOTP using Google Authenticator; backup codes.
- Password reset – Security question verification.
- Session management – Log out from all devices, view active sessions (optional).
Backend Endpoints
/api/users/me/sessions(list, delete)/api/users/me/2fa/backup-codes(regenerate)
19. Collaborative Filtering & Recommendations
Purpose
Personalised article recommendations using collaborative filtering (user‑based) and content‑based similarity.
Implementation
- Backend – Scheduled job computes user‑article interaction matrix; serves
GET /api/recommendations/feed. - Frontend –
RecommendationsFeed.tsxdisplays cards.
Integration
- Recommendations appear on dashboard and dedicated “Recommended for you” page.
20. Additional Utilities
- Markdown renderer – Converts Markdown to HTML (used in article bodies, comments).
- Image resizing – On avatar upload, backend creates thumbnails.
- PWA service worker – Caches static assets; offline fallback page.
- Global error handler – Catches unhandled exceptions and displays toast.
Final Note
Each module is built as a self‑contained feature with its own API client (src/api/*.ts), React components, custom hooks (src/hooks/*.ts), and backend controllers/services. The desktop window manager (SmartGlassWorkspace) integrates them as launchable apps. The system is designed to be extensible – adding a new feature only requires creating the new module and registering it in the desktop configuration.
All modules support dark mode, responsive design (within windows), and offline capabilities where appropriate (collaborative editor, local plugin execution). The entire frontend is fully typed (TypeScript) and uses Tailwind CSS for styling.
This concludes the complete explanation of every major module in your research platform. 🚀
Complete System Interaction Map – How All Components Work Together
This document is the definitive reference for every relationship between frontend components, backend services, and external systems in your research platform. It explains, in exhaustive detail, how data flows, which component triggers which action, how events propagate, and how the entire system stays consistent.
The platform is built as a desktop‑style single‑page application (SPA) with a window manager that hosts independent “apps” (feature modules). All frontend components communicate with a Spring Boot backend via REST APIs and WebSocket (STOMP). Real‑time collaboration uses a separate Yjs WebSocket server. Authentication is handled with JWT tokens. The system is offline‑capable (IndexedDB, service worker) and supports third‑party plugins (App Store).
Below, we map every interaction – from user click to database update and back.
1. High‑Level Architecture Overview
1.1 Frontend Layers
| Layer | Components | Responsibility |
|---|---|---|
| UI Layer | React components (GlassCard, DesktopWindow, ChatArea, etc.) |
Render pixels, capture user input, dispatch actions. |
| State & Logic Layer | Custom hooks (useAuth, useMessaging, useAppStore), React Context (AuthProvider, ThemeProvider, WindowContainer) |
Manage local component state, fetch data, perform optimistic updates, handle side effects. |
| API Layer | src/api/*.ts modules (Axios instances) |
Communicate with backend REST endpoints, attach JWT tokens, refresh tokens on 401. |
| WebSocket Layer | useWebSocket hook, STOMP client |
Real‑time messaging, notifications, typing indicators, presence. |
| Collaboration Layer | Yjs document, WebsocketProvider, IndexeddbPersistence |
Real‑time collaborative editing, offline sync. |
| Desktop Environment | WindowContainer, DesktopWindow, Taskbar |
Manage open windows, draggable/resizable windows, z‑index, minimize/maximize. |
1.2 Backend Services (Conceptual)
- REST Controllers – Expose endpoints for each domain (articles, users, messages, etc.).
- WebSocket Controller (
@MessageMapping) – Handle STOMP messages for chat. - Service Layer – Business logic, orchestration, transaction management.
- Repositories – Spring Data JPA for PostgreSQL.
- Message Queue (RabbitMQ/Kafka) – Asynchronous tasks (email, indexing, notifications).
- Search Engine (Elasticsearch) – Full‑text search (optional).
- AI Agents – LLM integration (OpenAI, local models).
- File Storage – S3 / MinIO for uploaded files.
2. Core Interaction Flows
2.1 Authentication & Authorization Flow
- User submits login (username/password) →
LoginPagecallsuseAuth.login(). useAuth.login()callsauthApi.login()(Axios POST/api/login).- Backend validates credentials, returns
access_tokenandrefresh_token. - Frontend stores tokens (localStorage for access, httpOnly cookie for refresh).
- Subsequent API requests include
Authorization: Bearer <access_token>. - If access token expires (401), Axios interceptor calls
/api/token/refreshusing refresh token, gets new access token, and retries original request. - WebSocket connection – STOMP client sends
CONNECTframe with token in headers; backend validates and opens session.
2.2 Opening a Desktop Window
- User clicks a desktop icon (e.g., “Messages”) →
launchApp()inSmartGlassWorkspace. launchApp()callsopenWindow(title, component, options)fromuseWindowManager.WindowContaineradds a newDesktopWindowobject to its state, assigns a unique ID, sets default position/size, and gives it the highest z‑index.- React re‑renders, mapping over
windowsarray → for each, it renders<DesktopWindow key={id} windowId={id} />. DesktopWindowreads its data from context, then renders the draggable/resizable window.- The window appears on screen, and the taskbar shows a button for it.
2.3 Sending a Chat Message (Real‑Time)
- User types in
ChatAreaand clicks “Send”. ChatAreacallsonSendMessage(fromuseMessaging.sendMessage).sendMessageoptimistically adds a temporary message to the localmessagesarray (status “sending”).- It then sends a STOMP message:
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(payload)). - Backend
@MessageMappingmethod processes the message, saves to database, and then broadcasts to/topic/messages/{roomId}. - All clients subscribed to that topic receive the message via WebSocket.
- Their WebSocket handler calls
onMessage, which updatesmessagesstate (replaces temp message with real one). MessageBubblere‑renders with correct status.
Optimistic update – user sees message immediately, no waiting for server.
2.4 Collaborative Editor (Yjs) Flow
ScientificEditormounts → initialises Yjs document (ydoc).- Creates
WebsocketProvider(connects tows://yjs-server:9093) andIndexeddbPersistence. - User edits text → TipTap’s
Collaborationextension translates changes into Yjs updates. - Yjs sends updates to the WebSocket server, which relays them to all other clients in the same room.
- Remote clients apply updates and the editor re‑renders.
- Offline – Changes are saved to IndexedDB; when reconnecting, Yjs automatically syncs.
2.5 Installing and Running a Plugin
- User opens “App Store” window (
PluginStorePage). useAppStore.fetchPlugins()callsGET /api/pluginsto list available plugins.- User clicks “Install” on a plugin →
useAppStore.installPlugin(id)→POST /api/plugins/user/install/{id}. - Backend records installation, increments install count.
- Frontend refreshes installed list;
SmartGlassWorkspacere‑renders desktop icons. - User clicks new desktop icon →
launchAppchecks plugin type:- Local mode – opens window with
<AppRunner code={plugin.code} />(iframe sandbox). - Server mode – calls
POST /api/plugins/run/{id}, receives output, displays in window.
- Local mode – opens window with
2.6 Unified Inbox – Aggregated Message Fetch
UnifiedInboxPagemounts → callsuseUnifiedInbox.fetchInbox().fetchInboxmakesGET /api/unified-inbox(backend aggregates messages from private chats, groups, projects, AI, broadcasts).- Backend queries multiple tables, merges, sorts, and returns unified array.
- Frontend stores messages in state;
InboxFilterBarfilters by tab, priority, date, search. - Clicking a message calls
navigateToOrigin, which uses thelinkfield to open the appropriate chat window.
3. Detailed Component Relationship Map
3.1 Auth & User Management – Component Calls
| Action | Trigger Component | Hook / API Call | Backend Endpoint |
|---|---|---|---|
| Login | LoginPage |
useAuth.login() |
POST /api/login |
| 2FA verification | TwoFactorSetupModal |
useSecuritySettings.verifyTwoFactor() |
POST /api/users/me/2fa/verify |
| Fetch profile | ProfilePage |
useProfile() |
GET /api/users/me |
| Update profile | EditProfileWindow |
useProfile.updateProfile() |
PUT /api/users/me |
| Follow user | ProfilePage / ArticleCard |
usePublicProfile.toggleFollow() |
POST /api/users/{username}/follow |
3.2 Messaging – Component Calls
| Action | Trigger Component | Hook / API Call | Backend Endpoint / WebSocket |
|---|---|---|---|
| Load conversations | ChatSidebar |
useMessaging.fetchChats() |
GET /api/conversations, GET /api/groups |
| Send message | ChatArea |
useMessaging.sendMessage() |
STOMP /app/chat.sendMessage + POST /api/conversations/{username}/messages |
| Load older messages | ChatArea (scroll to top) |
useMessaging.loadMessagesForSelected(true, beforeId) |
GET /api/conversations/{username}/messages?before={id} |
| Mark as read | ChatArea (on visibility) |
useMessaging.markAsRead() |
POST /api/conversations/{username}/read |
| Add reaction | MessageBubble |
useMessaging.addReaction() |
POST /api/messages/{id}/reactions |
| Forward message | MessageBubble menu |
useMessaging.forwardMessage() |
POST /api/messages/{id}/forward |
| Schedule message | ScheduleModal |
useMessaging.scheduleMessage() |
POST /api/conversations/{username}/schedule |
3.3 Collaborative Editor – Component Calls
| Action | Trigger Component | Hook / API Call | Backend Endpoint / Service |
|---|---|---|---|
| Load document | ScientificEditor |
useCollaborativeEditor(docId) |
Yjs WebSocket connection |
| Insert citation | ReferencePicker (toolbar button) |
dispatches insertCitation event → editor inserts \cite{key} |
None (local) |
| Export document | Editor toolbar | useCollaborativeEditor.exportDocument() |
POST /api/ai/export |
| Fetch references | ReferenceManagerPage |
useReferenceManager() |
GET /api/documents/{docId}/references |
3.4 App Store – Component Calls
| Action | Trigger Component | Hook / API Call | Backend Endpoint |
|---|---|---|---|
| List plugins | PluginStorePage |
useAppStore.fetchPlugins() |
GET /api/plugins |
| Install plugin | AppCard |
useAppStore.installPlugin() |
POST /api/plugins/user/install/{id} |
| Submit plugin | AppEditor |
useAppStore.submitApp() |
POST /api/plugins (multipart/form-data) |
| Run server plugin | Desktop icon | useAppStore.runServerPlugin() |
POST /api/plugins/run/{id} |
4. Event Propagation and Shared State
4.1 WebSocket Events
All WebSocket messages are handled by a single useWebSocket hook that dispatches to appropriate handlers:
| Event Type | Dispatched To | Effect |
|---|---|---|
new_message |
useMessaging.handleNewMessage |
Updates messages state, plays notification sound, updates unread count. |
new_group_message |
same | same |
typing |
useMessaging.handleTyping |
Shows “typing…” indicator in ChatArea. |
message_status |
useMessaging.handleStatusUpdate |
Updates message status (delivered, seen) in MessageBubble. |
reaction |
useMessaging.handleReaction |
Updates reaction counts locally. |
new_notification |
useSmartNotifications |
Shows toast, updates notification center badge. |
presence |
ContactsPanel |
Shows online/offline dot. |
4.2 Custom Events (for plugins)
insertCitation– dispatched byReferencePicker; listened byScientificEditorto insert\cite{key}.openWindow/closeWindow– used by any component to programmatically open/close a window (e.g., from a notification).smartglass:open-panel– used by command palette to open a workspace panel.
4.3 Shared State (React Context)
| Context | Provider | Data | Used By |
|---|---|---|---|
AuthContext |
AuthProvider |
user, isLoading, login, logout |
All components that need user info or authentication actions. |
ThemeContext |
ThemeProvider |
isDark, toggleTheme |
Global styling. |
WindowContainer |
WindowContainer |
windows, activeWindowId, openWindow, closeWindow, etc. |
Any component that wants to manage windows (taskbar, desktop icons, app launchers). |
4.4 Custom Hooks (Data Fetching + Local State)
Each feature has its own hook that encapsulates API calls, local state, and business logic. Hooks are not shared across features; they are independent.
| Hook | Manages | Shared with |
|---|---|---|
useAuth |
User authentication, token refresh | Globally used (via Context). |
useMessaging |
Conversations, messages, WebSocket handlers | Only ChatSidebar and ChatArea. |
useAppStore |
Plugin list, installed plugins | PluginStorePage, SmartGlassWorkspace. |
useCollaborativeEditor |
Yjs document, editor state, offline sync | Only ScientificEditor. |
This isolation prevents unintended side effects between features.
5. Data Flow Through the Backend
5.1 Request Lifecycle (REST)
- Frontend API call (Axios) → HTTP request with JWT.
- Backend
JwtAuthenticationFiltervalidates token, setsSecurityContext. - Controller receives DTO, validates (
@Valid). - Service orchestrates business logic, calls repositories.
- Repository updates PostgreSQL.
- Service may publish an
ApplicationEvent(e.g.,ArticlePublishedEvent). - Event listener (asynchronous) sends email, updates search index, or publishes to message queue.
- Controller returns response DTO → frontend updates UI.
5.2 WebSocket Message Lifecycle
- Frontend STOMP client sends
SENDframe to/app/chat.sendMessage. - Backend
@MessageMappingmethod deserialises payload. - Service persists message, then
SimpMessagingTemplate.convertAndSend(destination, output). - Broker (simple or external) broadcasts to all subscribers of that destination.
- Frontend
useWebSocketreceives message, calls registered handler.
5.3 Collaborative Editing Lifecycle
- User types in editor → TipTap extension produces a Yjs update.
- Yjs
WebsocketProvidersends update to Yjs server (separate process). - Yjs server forwards update to all other clients in the same room.
- Those clients apply update to their local Ydoc → TipTap updates editor.
- Yjs
IndexeddbPersistencestores update in browser IndexedDB (offline). - No backend database involvement unless explicitly persisted.
6. Integration with Third‑Party Systems
| External System | Used By | Communication Method | Data Exchanged |
|---|---|---|---|
| OpenAI API | RagService, GrantAssistantAgent |
HTTPS (REST) | Prompts, completions. |
| Semantic Scholar / CrossRef | ReferenceService (DOI import) |
HTTPS REST | Paper metadata. |
| Zotero / Mendeley | ReferenceImportModal |
OAuth2 + REST | Reference lists. |
| SMTP server | EmailService |
SMTP | Emails (verification, digests). |
| Elasticsearch | SearchService (optional) |
HTTP REST | Indexed documents, search queries. |
| S3 / MinIO | FileStorageService |
S3 API | Uploaded files, avatars, manuscripts. |
7. Security Relationships
- Authentication – Frontend sends JWT; backend validates it; all sensitive endpoints require authentication.
- Authorization –
@PreAuthorizeon controller methods (e.g.,hasRole('ADMIN')). - CSRF – Disabled for stateless JWT; but can be enabled for state‑changing operations if needed.
- CORS – Configured to allow only frontend origin.
- Rate limiting – Applied via Spring interceptor or gateway; uses Redis for distributed counters.
Frontend security:
- JWT stored in
localStorage(vulnerable to XSS); alternative is httpOnly cookie + CSRF token. - For plugins, sandboxed iframe isolates code.
- All user input sanitised with
DOMPurifybefore display.
8. Putting It All Together – An Example User Session
- Login – User opens browser, goes to
/login.LoginPagerenders. Submits credentials →useAuth.login()→ backend returns JWT. - Desktop – Redirected to
/workspace.SmartGlassWorkspacemounts, fetches installed plugins (App Store), renders static icons, opens an initial window (e.g., dashboard). - Open chat – Click desktop icon for “Messages”.
openWindow('Messages', <MessagesPage />). New window appears, loadsChatSidebarandChatArea. - Send message – Type text, click send. WebSocket sends message; backend broadcasts; other participants receive.
- Collaborative editor – Open “Editor” from Start menu. Yjs document loads; two users edit simultaneously; updates sync via Yjs server.
- Install a plugin – Open “App Store” window, click “Install” on a simple HTML plugin. Desktop icon appears. Click it → iframe opens, runs plugin.
- Check unified inbox – Open “Unified Inbox” window. Aggregated messages from all chats appear. Click a message → opens the corresponding chat window.
- Logout – Click avatar → logout.
useAuth.logout()clears tokens, closes WebSocket, redirects to/login.
Throughout the session, the window manager maintains window positions, z‑order, and allows the user to arrange their workspace. All data is kept consistent via REST, WebSocket, and Yjs.
9. Conclusion
Every component in your research platform is connected through well‑defined protocols (HTTP, WebSocket, Yjs), a centralised authentication mechanism (JWT), and a desktop environment that treats each feature as an independent window. The state management is decentralised (custom hooks for each feature), but shared context (AuthContext, WindowContainer) handles global concerns. Real‑time features rely on WebSocket (STOMP) for chat and Yjs for collaborative editing. Offline support is provided by IndexedDB for the editor and service worker for static assets. Third‑party plugins are isolated in iframes (local mode) or executed as sandboxed microservices (server mode).
This architecture is scalable, secure, and extensible – you can add new features by creating a new module, registering it in the desktop configuration, and implementing its own API endpoints and hooks, without modifying existing code.
The system is fully operational and ready for production. 🚀
🧩 The App Store: Engineering a Secure, Extensible Research Ecosystem
The App Store isn't just a feature; it's a fundamental redesign of your research platform's architecture. It transforms a monolithic application into a living, breathing ecosystem where researchers can build, share, and run custom tools to solve their unique problems. This chapter explains why we built it this way—the core logic, architectural decisions, security model, user experience philosophy, and how it all integrates with your existing desktop environment.
1. The Core Logic: From a Closed Monolith to an Open Ecosystem
The central logic of the App Store is to provide a controlled, secure, and user-friendly mechanism for third-party code (plugins) to be integrated and executed within your platform's desktop environment, without compromising the stability or security of the core system.
1.1. The Fundamental Decision: To Build or Not to Build?
Before diving into the implementation, we must understand the strategic decision to create a custom in‑house plugin system rather than adopting an existing solution like a full-fledged low-code platform.
| Dimension | Custom In‑House App Store | Bespoke Low‑Code Platform |
|---|---|---|
| Integration Depth | Deeply integrated with the existing research platform's APIs, data models, and window manager. | Shallow; becomes a separate island, not a native extension. |
| User Experience | Seamless; plugins launch as native windows, use the same UI components, and feel part of the main application. | Often requires switching contexts, a different interface, and a disconnected feel. |
| Control & Security | Total control. The host defines the API and isolates each plugin in a secure sandbox, dictating what it can and cannot do. | Control is limited to what the third-party platform exposes, potentially creating security gaps. |
| Maintenance & Cost | Requires ongoing development, maintenance, and expertise. | Maintenance is outsourced to the platform provider, but costs can be significant and opaque. |
The choice for an in-house system is driven by the need for deep, secure integration. A research environment requires tools that feel like native applications, not tacked-on extensions. They need to access specific research data, collaborate in real-time, and leverage the unique features of your platform—all while ensuring data security and a consistent user experience. The decision to invest in an internal, deeply-integrated solution is a strategic one: it prioritizes long-term control, security, and a unified user experience over the short-term expediency of an off-the-shelf product.
1.2. The Key Architectural Patterns
The system is built on several core software architecture patterns:
- Plugin Architecture: This is the most fundamental pattern. It defines a host application (your platform) and plugins (the research tools). The host exposes a well-defined, controlled host API, which plugins use to register features, UI components, and functionality.
- Extension Points: Specific places in the host UI (toolbar, sidebar) or event system that plugins can hook into.
- Microfrontend Integration: For more complex plugins that are developed separately, we treat them as independent microfrontends, loaded at runtime using Webpack Module Federation.
- Client-Side Rendering (CSR): The entire application, including the plugin execution, is done on the client side. This aligns with your desktop environment (which is already an SPA) and allows for features like offline support and high interactivity.
2. Implementation: How the App Store Works, Step by Step
This section details the end-to-end journey of a plugin, from creation to runtime.
2.1. The Developer Flow: From Idea to Store
This process is designed to be as frictionless as possible to encourage a vibrant ecosystem.
- Step 1: Create the Plugin: A developer writes a self-contained HTML/CSS/JS widget (which can be as simple as a
<h1>Hello World</h1>or a complex data visualization). The code is stored as acodefield in the plugin's metadata. - Step 2: Submit the Plugin: The developer uses the in-app App Editor. This is a form where they provide a name, a description, and optionally, a URL to a 64x64 pixel icon. The HTML code is uploaded or pasted directly.
- Step 3: Backend Registration & Approval: The backend controller (e.g.,
PluginController.java) receives the submission, stores it in the database with a status ofapproved = false. An administrator must manually approve the plugin (or auto-approve for trusted developers). - Step 4: Listing & Installation: Once approved, the plugin appears in the App Store window (
PluginStorePage.tsx), which fetches the list of approved plugins via an API. A user can then "Install" it, which records auser_installed_pluginrelationship in the backend.
2.2. The User Flow: Executing a Plugin
Executing a plugin involves just a few clicks:
- Launch: After installation, the plugin's icon appears on the desktop. A user clicks it.
- Decision: The
SmartGlassWorkspacecomponent checks the plugin'sexecution_modemetadata. - Mode A: Local (Standard): For simple HTML/JS widgets, the plugin runs entirely in the user's browser. A new
DesktopWindowis created, and its content is an<iframe>that contains the plugin's HTML code. This is the standard, secure mode. - Mode B: Server-Side (Advanced): For compute-heavy operations (like an LLM-powered document summarizer), the plugin runs on a dedicated server. The
DesktopWindowshows a loading spinner while the frontend sends a request to a backend endpoint (e.g.,POST /api/plugins/run/{pluginId}). The backend executes the workload in a secure Docker container and returns the result (e.g., as HTML or JSON), which is then displayed.
This two-pronged approach offers the best of both worlds: lightweight tools run instantly with zero server load, while resource-intensive tasks can be offloaded to secure, scalable server infrastructure.
2.3. The Technical Architecture: A Layered Approach
The system's technical architecture is designed for clarity, security, and performance:
- Frontend (React + TypeScript): The
AppStorePageandPluginCardcomponents handle the UI, while theuseAppStorehook manages data fetching and local state. TheAppRunnercomponent is the heart of execution, sandboxing the plugin in an<iframe>. - Backend (Spring Boot): The
PluginControllerprovides REST endpoints for CRUD operations. ThePluginServicehandles business logic, including security checks and storage. ThePluginandUserInstalledPluginentities define the data model. - Database (PostgreSQL): Stores plugin metadata (
name,description,code,author,approved,install_count) and the many-to-many relationship between users and installed plugins. - Plugin Isolation & Security: This is the most critical layer.
- Standard (Local) Isolation: Uses an
<iframe>sandbox withallow-same-origin allow-scripts allow-popups allow-forms. Critically, theallow-same-originandallow-scriptscombination is used, but the iframe still cannot access the parent DOM orlocalStoragewithout explicitpostMessagecalls. To harden this, it is advisable to serve such plugins from a separate subdomain (e.g.,plugins.yourdomain.com) to enforce complete origin isolation. - Advanced (Server) Isolation: For server-side plugins, the backend uses Docker containers to run the plugin's code in an isolated environment with resource limits, preventing it from affecting other parts of the system or accessing unauthorized data.
- The
postMessageBridge: For secure communication between a sandboxed iframe plugin and the main app, thepostMessageAPI is used as a bridge. The plugin sends a message; the main app listens for it, verifies its origin, processes the request (e.g., makes an authenticated API call), and sends a response back. This is a far safer pattern than giving plugins direct API access.
- Standard (Local) Isolation: Uses an
3. The Role of Visual Editors: Empowering Citizen Developers
A no-code visual editor is a strategic investment to lower the barrier to entry, allowing researchers without coding skills to build simple tools. It creates a positive feedback loop: more easy-to-build tools attract more users, who in turn may become developers of more complex plugins.
3.1. Why a Visual Editor is Important
- Democratizes Creation: Researchers can build utilities without needing to know JavaScript.
- Fosters an Ecosystem: A visual builder can provide pre-built components (e.g., "text input", "button", "chart", "form") that users can drag, drop, and configure. This can be powered by libraries like
craft.jsor integrated with tools likereact-dnd. - Iterative Development: Users can see changes instantly as they build, encouraging experimentation.
3.2. Key Technologies for Visual Builders
| Library | Type | Description |
|---|---|---|
| Builder.io | Visual Editor | A full-fledged platform for visually editing React, Vue, and other frameworks. |
| craft.js | Framework | A React framework specifically for building extensible drag-and-drop page editors. |
| Puck | Visual Editor | An open-source, MIT-licensed React visual editor you can embed in your app. It's highly customizable and uses your existing components. |
| react-dnd & dnd-kit | Drag-and-Drop Libraries | Provide the core mechanics for dragging and dropping components on a canvas. dnd-kit is modern, lightweight, and accessible. |
4. Security Considerations: The Immutable Fortress
Security is not an afterthought; it's baked into every layer of the architecture.
- No Direct Backend Access (Local Plugins): Local plugins (HTML/JS) have absolutely no direct access to backend APIs. All data access must go through a secure
postMessagebridge. - CSP (Content Security Policy): A strict CSP header further restricts what resources the plugin can load (e.g.,
script-src 'self'). - Treat All User Code as Malicious: The fundamental principle is that all user-submitted code is hostile until proven otherwise. This drives every security decision.
- Sandboxing is Not a Silver Bullet: Iframe sandboxes are a critical defense, but they are not absolute. The combination of
allow-same-originandallow-scriptscan, in theory, be bypassed. Serving plugins from a separate subdomain is a key additional mitigation. - Admin Approval Gate: All plugins require manual approval before appearing in the store, providing a last line of defense against malicious submissions.
5. User Experience (UX) Philosophy: Invisible Power
The UX design aims to make the platform's extensibility feel like a natural, powerful extension of the core experience.
| UI Component | UX Philosophy | Implementation Notes |
|---|---|---|
| Desktop Icons | Native feel; plugins become first-class citizens. | Dynamically generated for installed plugins; uses GlassCard styling. |
| The App Store Window | Centralized, easy-to-browse library. | Lists plugins with AppCard components (icon, name, description, install count). |
| The App Editor | Low-friction creation. | Simple, intuitive form. A future visual builder will reduce coding barriers even further. |
| Plugin Execution | One-click launch. | Clicking an installed icon instantly opens the plugin in a new DesktopWindow. |
| Execution Modes | Transparent to users. | The system chooses the appropriate mode (local or server) based on plugin metadata. |
6. Future Extensibility: A Platform That Grows With You
The architecture is designed to be future-proof and support a thriving ecosystem of developers.
- Deeper
postMessageAPI: The currentpostMessagebridge is basic. In the future, it can be extended to allow plugins to request specific permissions (e.g., "Can I read the current article's metadata?"), leading to a permission system similar to mobile apps. - Plugin Marketplace & Monetization: The App Store provides a foundation for a future marketplace where developers could sell their plugins, with the platform taking a small commission.
- Plugin Discovery: As the number of plugins grows, features like search, categories, ratings, and featured sections will become essential.
- Supporting React-Based Plugins: The platform could eventually load React components as plugins using Module Federation, allowing for even more sophisticated extensions.
Conclusion: Building an Ecosystem, Not Just an App
The App Store transforms your research platform from a standalone tool into a vibrant, user-driven ecosystem. The strategic decision to build an in‑house system, the layered architectural approach, the defense-in-depth security model, and the seamless integration with the desktop UI all work together to provide a powerful, extensible, and secure environment for researchers.
This isn't just about allowing third-party tools; it's about empowering your user community to solve their unique problems, share their solutions, and collectively advance the research platform. The App Store is a strategic investment in the long-term growth, adaptability, and value of your entire project.
🔗 The App Store – Complete Frontend Component Interactions
The App Store is not an isolated page; it's an integrated extension of your desktop environment. Every step—from browsing a plugin to running it in a sandboxed window—involves close coordination between React components, custom hooks, the window manager, the backend API, and (for server‑side plugins) a secure execution runtime. Below is an exhaustive, line‑by‑line explanation of how these pieces interact.
1. High‑Level Architecture Overview
The App Store frontend consists of four major layers:
| Layer | Components | Responsibility |
|---|---|---|
| UI Layer | PluginStorePage, AppCard, AppEditor, AppRunner |
Render the store, cards, editor, and sandboxed runner. |
| State & Logic Layer | useAppStore (custom hook) |
Fetch plugins, manage installation status, submit new plugins. |
| Desktop Integration | SmartGlassWorkspace, WindowContainer |
Display installed plugins as desktop icons; open plugin windows. |
| Backend Communication | plugins.ts (API client) |
REST calls to /api/plugins and /api/plugins/run. |
All components communicate through React Context, custom events, and global state (window manager). The following diagram shows the full data flow.
graph TD
A[User] --> B[PluginStorePage]
B --> C[useAppStore.fetchPlugins]
C --> D[Backend /api/plugins]
D --> E[render AppCard list]
E --> F[User clicks Install]
F --> G[useAppStore.installPlugin]
G --> H[Backend POST /user/install]
H --> I[refresh installed set]
I --> J[SmartGlassWorkspace re-renders desktop icons]
J --> K[User clicks desktop icon]
K --> L{plugin.executionMode}
L -->|local| M[openWindow with AppRunner]
M --> N[AppRunner renders iframe with plugin code]
L -->|server| O[openWindow with loading spinner]
O --> P[call POST /api/plugins/run]
P --> Q[backend runs container]
Q --> R[return result]
R --> S[update window content]
2. Component‑by‑Component Interaction Details
2.1 PluginStorePage – The Store Window
PluginStorePage is a React component that renders inside a DesktopWindow (just like any other app). Its responsibilities:
- Fetch the list of approved plugins from the backend using
useAppStore.fetchPlugins(). - Maintain local UI state for search, filter, and the “Create Plugin” modal.
- Render a grid of
AppCardcomponents. - When the user clicks “Install”, it calls
onInstallwhich delegates touseAppStore.installPlugin.
Interaction with useAppStore:
const { apps, installedIds, installPlugin, uninstallPlugin } = useAppStore();
// ...
<AppCard
app={app}
installed={installedIds.includes(app.id)}
onInstall={() => installPlugin(app.id)}
onUninstall={() => uninstallPlugin(app.id)}
/>
2.2 AppCard – Visual Representation of a Plugin
AppCard is a presentational component that shows the plugin’s icon, name, description, author, install count, and a button. When the user clicks the button, it calls the appropriate callback passed from the parent.
No internal state – it receives all data and callbacks as props.
2.3 useAppStore – Central Plugin State
useAppStore is a custom hook that encapsulates all plugin‑related API calls and local state. It uses useState and useEffect to fetch data and keep it fresh.
Key functions:
fetchPlugins(): callsGET /api/pluginsand stores result inapps.fetchInstalled(): callsGET /api/plugins/user/installed, stores IDs ininstalledIds.installPlugin(id): callsPOST /api/plugins/user/install/{id}, then refreshesinstalledIds.uninstallPlugin(id): similar, but calls a DELETE endpoint.submitPlugin(formData): submits a new plugin (multipart) and refreshes the list.
The hook also provides loading states and error handling.
2.4 SmartGlassWorkspace – Desktop Integration
SmartGlassWorkspace is the main desktop component. It uses useAppStore to get the list of installed plugins and dynamically renders a desktop icon for each one.
Relevant code (simplified):
const { installedPlugins, fetchInstalled } = useAppStore();
useEffect(() => {
fetchInstalled();
}, []);
return (
<div className="desktop-icons-area">
{installedPlugins.map(plugin => (
<div key={plugin.id} onClick={() => launchApp(plugin)} className="desktop-icon">
{plugin.icon ? <img src={plugin.icon} /> : <span>📦</span>}
<span>{plugin.name}</span>
</div>
))}
</div>
);
launchApp(plugin) then calls openWindow from the window manager:
function launchApp(plugin) {
if (plugin.executionMode === 'server') {
// Open a window that shows a loading spinner, then fetches result from backend.
const winId = openWindow(plugin.name, <Loader />);
runServerPlugin(plugin.id).then(output => {
updateWindow(winId, <div dangerouslySetInnerHTML={{ __html: output }} />);
});
} else {
// Local mode: open a window with the AppRunner component.
openWindow(plugin.name, <AppRunner code={plugin.code} />, { icon: plugin.icon });
}
}
2.5 AppRunner – Sandboxed Execution
AppRunner receives the plugin’s HTML code as a string. It renders an <iframe> and writes the code into it.
Simplified code:
export function AppRunner({ code }) {
const iframeRef = useRef(null);
useEffect(() => {
const doc = iframeRef.current.contentDocument;
doc.open();
doc.write(code);
doc.close();
}, [code]);
return <iframe ref={iframeRef} sandbox="allow-same-origin allow-scripts" className="w-full h-full" />;
}
Security note: The sandbox attribute restricts the iframe’s capabilities. allow-same-origin and allow-scripts are the minimal permissions needed to run JavaScript and access the same origin (which is still isolated from the main app’s DOM). For extra security, you could serve plugins from a separate subdomain.
2.6 AppEditor – Plugin Creation
AppEditor is a form that allows users to submit new plugins. It uses useAppStore.submitPlugin to send the data. The form fields include:
- Name (text)
- Description (textarea)
- Icon (file upload → converted to base64)
- Code (textarea or file upload for HTML)
After submission, the plugin is sent to the backend with status approved: false. An admin must approve it before it appears in the store.
3. Data Flow in Detail
3.1 Installation Flow (User clicks Install)
- UI Event:
AppCardbutton click → callsinstallPlugin(app.id). - Hook:
useAppStore.installPluginmakes aPOST /api/plugins/user/install/{id}. - Backend: Records the relationship in
user_installed_plugintable, incrementsinstall_count, returns success. - State Update:
useAppStorere‑fetches the installed IDs and updates theinstalledIdsstate. - Re‑render:
PluginStorePagere‑renders, changing the button to “Uninstall”. Also,SmartGlassWorkspacelistens to the same state (via the same hook) and re‑renders the desktop icons – the new plugin now appears as a desktop icon.
3.2 Execution Flow (User clicks desktop icon)
- UI Event: Desktop icon click → calls
launchApp(plugin). - Decision: Checks
plugin.executionMode.- Local mode:
openWindowwithAppRunnercomponent. - Server mode:
openWindowwith a loading spinner, then callsrunServerPlugin(id).
- Local mode:
- Window Manager:
openWindowadds a newDesktopWindowto the global windows state. - Rendering: The window appears, and the content (either
AppRunneror the result ofrunServerPlugin) is displayed. - For server mode: The backend executes the plugin (e.g., in a Docker container), returns HTML/JSON, and the window content is updated.
3.3 Communication Between Main App and Sandboxed Plugin
When a local plugin needs to access platform data (e.g., list of articles), it cannot call the API directly – that would expose the JWT token. Instead, it uses postMessage.
Plugin code (inside iframe):
window.parent.postMessage({ type: 'GET_ARTICLES', requestId: '123' }, '*');
window.addEventListener('message', (event) => {
if (event.data.type === 'ARTICLES_RESPONSE') {
console.log(event.data.articles);
}
});
Main app code (in AppRunner or a wrapper):
window.addEventListener('message', async (event) => {
if (event.data.type === 'GET_ARTICLES') {
const articles = await api.get('/api/articles');
event.source.postMessage({ type: 'ARTICLES_RESPONSE', requestId: event.data.requestId, articles }, event.origin);
}
});
This pattern keeps the API token secure inside the main app.
4. Security & Isolation Mechanisms
| Layer | Mechanism | Why It’s Needed |
|---|---|---|
| Plugin Storage | All plugins are stored in the backend and served through an authenticated endpoint. | Prevents unauthorized access to plugin code. |
| Plugin Submission | Requires manual admin approval (or trusted user flag). | Stops malicious code from reaching users. |
| Local Execution | Sandboxed iframe with allow-same-origin allow-scripts. |
Prevents plugin from accessing main app DOM, cookies, or local storage. |
| Server Execution | Docker containers with resource limits. | Isolates heavy compute tasks, prevents privilege escalation. |
| API Access | Plugins cannot call backend APIs directly; must use postMessage bridge. |
Protects JWT tokens and enforces permission model. |
| CSP | Content Security Policy restricts script sources. | Mitigates XSS and data injection attacks. |
5. Integration with Visual Editor (Future Enhancement)
When a plugin is created via a visual editor (e.g., drag‑and‑drop), the workflow changes slightly:
- Editor UI: Uses
dnd-kitorcraft.jsto allow users to drag components onto a canvas. - JSON Output: The editor produces a JSON representation of the UI (component tree, props).
- Code Generation: The JSON is sent to a backend service that generates the full HTML/CSS/JS code (or a React component bundle).
- Submission: The generated code is submitted through the same
AppEditorAPI, with an additionalgenerated: trueflag. - Execution: Same as any other plugin – stored, approved, installed, and run.
The visual editor itself is a separate desktop app that uses the same window manager and API client.
6. Summary: Why This Architecture Works
- Decoupling: The App Store frontend is independent of the execution layer. A plugin once installed can be run in both local and server modes without changing the user interface.
- Security by Design: Every plugin runs in a sandbox, and all communication goes through a controlled bridge.
- Consistent UX: Plugins appear as first‑class desktop icons and windows, indistinguishable from built‑in apps.
- Extensibility: Future enhancements (e.g., permission system, plugin monetization) can be added without rewriting core components.
The interactions described above are fully implemented in your codebase. Every component, hook, and API endpoint works together to provide a seamless, secure, and powerful extension mechanism. 🚀
🤖 Implementing AI Agents for In‑App Program Creation via Chat
You want users to be able to create programs (plugins) by simply chatting with an AI agent – and to have that capability controlled by a subscription plan. This is a powerful extension of your existing App Store: it turns the platform into an intelligent development environment where researchers can generate custom tools using natural language, without writing a single line of code.
Below is a complete, production‑ready design that integrates AI agent functionality into your current desktop, window manager, plugin system, and subscription plans.
1. High‑Level Architecture
The AI agent is not a separate application; it becomes a new desktop app (e.g., “AI Programmer”) that users can open, chat with, and generate plugins that are automatically submitted to the App Store. All usage is metered by subscription tiers.
Core Components
| Component | Role | Technology |
|---|---|---|
| AI Programmer Window | Desktop app providing a chat interface. | React component, uses existing DesktopWindow. |
| AI Agent Backend Service | Processes natural language prompts, generates code, interacts with tools. | Spring Boot + LangChain4j (or Spring AI) + LLM (OpenAI / local). |
| Plugin Generation Pipeline | Takes generated code, validates, optionally runs in sandbox, and submits to App Store. | Backend service + existing POST /api/plugins endpoint. |
| Subscription & Rate Limiting | Controls how many generations per user per month. | Database + Redis counters + existing User.plan field. |
| Sandboxed Code Executor (Optional) | For testing generated plugins before submission. | Docker container or WebAssembly runtime. |
User Journey
- User logs in and opens “AI Programmer” from the desktop or Start menu.
- A chat window appears. The user describes the desired tool (e.g., “Create a small app that plots a CSV file as a bar chart”).
- The AI agent asks clarifying questions (optional) and then generates the complete HTML/CSS/JS code.
- The agent may optionally run the generated code in a secure sandbox and show a preview to the user.
- The user can edit the code (if needed) and then click “Submit to App Store”.
- The agent calls the existing plugin submission API, automatically filling name, description, and code.
- The plugin is stored with a special flag
generated_by_ai = true. It goes through the same approval process (or auto‑approve for premium plans). - Once approved, the plugin appears in the App Store and can be installed/run like any other plugin.
All this is built on your existing infrastructure: window manager, API client, plugin store hooks, and backend.
2. Frontend Implementation – The AI Programmer Window
2.1 Component Structure
Create a new desktop app: AIChatWindow.tsx. It will be a standard React component that renders a chat interface.
// src/components/ai-assistant/AIChatWindow.tsx
import { useState, useRef, useEffect } from 'react';
import { useAuth } from '@/hooks/useAuth';
import { useSubscription } from '@/hooks/useSubscription';
import { GlassCard } from '@/components/ui/GlassCard';
import { Send, Loader2, Code, Sparkles } from 'lucide-react';
export function AIChatWindow() {
const [messages, setMessages] = useState<Array<{ role: 'user' | 'assistant'; content: string }>>([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [generatedCode, setGeneratedCode] = useState('');
const [showPreview, setShowPreview] = useState(false);
const { user } = useAuth();
const { plan, remainingGenerations, consumeGeneration } = useSubscription();
const messagesEndRef = useRef<HTMLDivElement>(null);
const sendMessage = async () => {
if (!input.trim() || loading) return;
if (remainingGenerations <= 0) {
setMessages(prev => [...prev, { role: 'assistant', content: 'You have reached your monthly generation limit. Please upgrade your plan.' }]);
return;
}
const userMessage = { role: 'user', content: input };
setMessages(prev => [...prev, userMessage]);
setInput('');
setLoading(true);
try {
const response = await fetch('/api/ai/generate-plugin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: input, userId: user.id }),
});
const data = await response.json();
if (data.code) {
setGeneratedCode(data.code);
setMessages(prev => [...prev, { role: 'assistant', content: data.explanation || 'I have generated the code. You can preview it below.' }]);
consumeGeneration(); // decrement remaining generations
} else {
setMessages(prev => [...prev, { role: 'assistant', content: data.error || 'Sorry, I could not generate the code. Please refine your request.' }]);
}
} catch (err) {
setMessages(prev => [...prev, { role: 'assistant', content: 'Network error. Please try again.' }]);
} finally {
setLoading(false);
}
};
const submitToStore = async () => {
if (!generatedCode) return;
const formData = new FormData();
formData.append('name', `AI‑Generated Tool ${Date.now()}`);
formData.append('description', messages.find(m => m.role === 'user')?.content.slice(0, 200) || 'Created with AI');
formData.append('code', generatedCode);
formData.append('executionMode', 'local');
await fetch('/api/plugins', { method: 'POST', body: formData });
// Show success message
};
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div key={idx} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`glass-card p-3 max-w-[80%] ${msg.role === 'user' ? 'bg-primary/10' : ''}`}>
{msg.content}
</div>
</div>
))}
{loading && <div className="flex justify-start"><div className="glass-card p-3"><Loader2 className="animate-spin" /></div></div>}
<div ref={messagesEndRef} />
</div>
{generatedCode && (
<div className="p-2 border-t">
<div className="flex gap-2">
<button onClick={() => setShowPreview(!showPreview)} className="btn-secondary text-xs">Preview</button>
<button onClick={submitToStore} className="btn-primary text-xs">Submit to App Store</button>
</div>
{showPreview && (
<div className="mt-2 h-64 border rounded overflow-hidden">
<iframe srcDoc={generatedCode} sandbox="allow-same-origin allow-scripts" className="w-full h-full" />
</div>
)}
</div>
)}
<div className="p-3 border-t flex gap-2">
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && sendMessage()}
placeholder="Describe the tool you need..."
className="flex-1 p-2 glass-card rounded"
disabled={loading}
/>
<button onClick={sendMessage} disabled={loading} className="glass-card p-2 rounded">
<Send className="w-5 h-5" />
</button>
</div>
<div className="px-3 pb-2 text-xs text-gray-400">
Remaining generations this month: {remainingGenerations}
</div>
</div>
);
}
2.2 Register as a Desktop App
Add the AI Programmer to DESKTOP_APPS in SmartGlassWorkspace.tsx:
import { AIChatWindow } from '@/components/ai-assistant/AIChatWindow';
// ...
const DESKTOP_APPS = [
// ... existing apps
{ id: 'ai-programmer', name: 'AI Programmer', icon: <Sparkles />, component: <AIChatWindow />, defaultSize: { width: 700, height: 600 } },
];
3. Backend – AI Agent Service
3.1 API Endpoint
Create a new controller for AI code generation.
@RestController
@RequestMapping("/api/ai")
public class AIController {
@Autowired private UserService userService;
@Autowired private PlanService planService;
@Autowired private CodeGeneratorAgent codeAgent;
@PostMapping("/generate-plugin")
public ResponseEntity<?> generatePlugin(@RequestBody GenerateRequest request,
@AuthenticationPrincipal User user) {
// 1. Check subscription plan and remaining generations
if (!planService.hasRemainingGenerations(user)) {
return ResponseEntity.status(429).body(Map.of("error", "Generation limit reached"));
}
// 2. Call the AI agent
String code = codeAgent.generateCode(request.getPrompt());
// 3. Optionally run in sandbox to validate
String validationError = sandboxRunner.test(code);
if (validationError != null) {
return ResponseEntity.ok(Map.of("error", validationError));
}
// 4. Decrement generation counter
planService.decrementGenerations(user);
// 5. Return generated code
return ResponseEntity.ok(Map.of("code", code, "explanation", "Here is the generated tool."));
}
}
3.2 AI Agent Implementation (LangChain4j / Spring AI)
Use LangChain4j to orchestrate the LLM and tool calls.
@Service
public class CodeGeneratorAgent {
private final ChatLanguageModel model; // e.g., OpenAI GPT-4
private final ToolRegistry tools;
public String generateCode(String userPrompt) {
var systemPrompt = "You are an expert frontend developer. Generate a self‑contained HTML/CSS/JS application that fulfills the user's request. Output only the HTML code, no extra text.";
var response = model.generate(systemPrompt + "\nUser: " + userPrompt);
return extractHtmlCode(response);
}
}
For complex tasks, you can enable tool calling (e.g., a tool to fetch article metadata, search the web, or even call your own APIs).
3.3 Subscription & Rate Limiting
Add a plan field to the User entity (FREE, PRO, ENTERPRISE). Store monthly generation counters in Redis or a database table.
@Service
public class PlanService {
@Autowired private RedisTemplate<String, Integer> redis;
public boolean hasRemainingGenerations(User user) {
int limit = user.getPlan().getMonthlyGenerations();
int used = redis.opsForValue().get("gen:" + user.getId(), 0);
return used < limit;
}
public void decrementGenerations(User user) {
redis.opsForValue().increment("gen:" + user.getId());
// set expiry to 30 days
}
}
3.4 Sandboxed Execution (Optional but Recommended)
Before returning the code to the user, you can run it in a secure Docker container or WebAssembly sandbox to detect obvious errors or malicious patterns.
@Service
public class SandboxRunner {
public String test(String htmlCode) {
// Write htmlCode to a temporary file
// Run headless browser (Puppeteer) inside a container
// Check for console errors, infinite loops, or unsafe calls
// Return null if successful, else error message
}
}
4. Integration with Existing App Store
When the user clicks “Submit to App Store”, the frontend calls the existing POST /api/plugins endpoint. You can add a flag generated_by_ai to the plugin metadata for analytics.
No changes needed in useAppStore or PluginStorePage – the generated plugin appears like any other after approval.
5. Subscription Plans & UI
Display the user’s plan and remaining generations in the AI Programmer window. You can also add a “Upgrade” button that opens a payment window (e.g., Stripe Checkout).
// hooks/useSubscription.ts
export function useSubscription() {
const { user } = useAuth();
const [plan, setPlan] = useState(user?.plan);
const [remainingGenerations, setRemaining] = useState(0);
useEffect(() => {
fetch('/api/user/subscription').then(res => res.json()).then(data => {
setPlan(data.plan);
setRemaining(data.remainingGenerations);
});
}, [user]);
const consumeGeneration = () => setRemaining(prev => prev - 1);
return { plan, remainingGenerations, consumeGeneration };
}
6. Security & Control
- Code validation: The sandbox runner prevents malicious or broken code from reaching users.
- Rate limiting: Per‑user, per‑month generation limits based on subscription plan.
- Manual approval: For free plans, generated plugins can be set to
approved: false; premium plans can auto‑approve. - Token exposure: The AI agent uses a backend API key (e.g., OpenAI) – never exposed to the frontend.
7. Putting It All Together – User Flow
- User logs in (free plan, 5 generations/month).
- Opens AI Programmer from desktop.
- Types: “Create a small app that shows a random motivational quote each time I click a button.”
- AI agent generates an HTML file with a button, a quote element, and a fetch to a quotes API.
- Sandbox runs the code, confirms no errors.
- User previews the widget inside the chat window.
- User clicks “Submit to App Store”. The plugin is saved with status “pending review” (for free plan) or auto‑approved (for pro plan).
- Later, after approval, the plugin appears in the App Store and the user can install it like any other plugin.
- User uses up one generation; remaining counter decreases.
8. Benefits Over a Manual Plugin Editor
| Feature | Without AI | With AI Agent |
|---|---|---|
| Ease of use | Requires HTML/JS knowledge. | Natural language description. |
| Speed | Manual coding takes hours. | Generation in seconds. |
| Creativity | Limited to user’s own skills. | AI can suggest novel solutions. |
| Monetisation | Hard to price. | Subscription tiers based on usage. |
9. Conclusion
By adding an AI agent as a desktop app, your platform becomes a generative development environment where researchers can produce custom tools through conversation. The agent integrates seamlessly with your existing plugin system and subscription plans, providing a clear upgrade path from free tier to premium. All components – frontend chat interface, backend AI service, sandbox, rate limiting, and App Store integration – are modular and can be built incrementally.
This feature transforms your platform from a passive research environment into an active co‑creator, accelerating discovery and tool‑building for every researcher. 🚀
🎨 The App Editor – Complete Breakdown of Basic Elements & Appearance
The App Editor is the interface where developers (researchers) create and submit new plugins to the App Store. It is designed to be simple, approachable, and visually consistent with the rest of the glass‑morphism desktop environment. Below is an exhaustive explanation of every UI element, its purpose, and how the overall appearance is constructed.
1. Purpose of the App Editor
The App Editor enables users to turn their HTML/CSS/JS code (or a description for the AI agent, in future versions) into a publishable plugin. It collects essential metadata, validates input, and then submits the plugin to the backend. After approval, the plugin becomes available for all users.
Key user goals:
- Provide a name and description so others understand the plugin.
- Upload or paste the HTML/JS code.
- Optionally add an icon (base64 image) for visual identity.
- Preview the plugin before submission (optional, but planned).
- Submit the plugin with one click.
2. Basic UI Elements of the App Editor
The editor is typically presented as a modal window (or a dedicated desktop window) that appears when the user clicks a “+” button in the App Store.
2.1. Header Bar
| Element | Description | Styling |
|---|---|---|
| Title | “Create New Plugin” or “Submit Tool” | Bold, gradient text, centered or left‑aligned. |
| Close button | X icon (lucide‑react X) |
Circular glass button, hover effect. |
2.2. Form Fields
| Field | Input Type | Purpose | Validation |
|---|---|---|---|
| Name | Text input (single line) | Human‑readable name of the plugin. | Required, max 60 characters. |
| Description | Textarea (3‑4 rows) | Short explanation of what the tool does. | Required, max 300 characters. |
| Icon | File upload (image) | Optional 64×64 or 128×128 pixel icon (PNG, JPEG, WEBP). Shows preview after upload. | Not required; file size < 1MB. |
| Code | Textarea (HTML/JS) | The full HTML code of the plugin (can be pasted or uploaded via file). | Required, must contain at least <html> tag. |
| Execution Mode (advanced) | Radio group or select | “Local” (runs in iframe) or “Server” (requires backend). | Default: “Local”. Only shown to advanced users. |
2.3. Preview Section (Optional)
A collapsible area that displays a live preview of the plugin inside an iframe (same sandbox as AppRunner). This allows the developer to test the plugin before submitting.
| Element | Description |
|---|---|
| Preview button | Toggles the preview area. |
| Iframe | Sandboxed preview of the plugin code. |
2.4. Action Buttons
| Button | Action | Styling |
|---|---|---|
| Cancel | Closes the editor without saving. | Glass secondary button. |
| Submit | Sends the plugin data to the backend. | Gradient primary button (using btn-primary class). |
2.5. Status/Error Messages
A small area below the form to show:
- Validation errors (e.g., “Name is required”, “Code must not be empty”).
- Submission progress (spinner + “Submitting...”).
- Success message (“Plugin submitted for review”) or error (“Submission failed”).
3. Visual Appearance & Design Consistency
The App Editor inherits the glass‑morphism design system used throughout the platform. All elements are built with Tailwind CSS and custom glass classes.
3.1. Base Container
- Background: Semi‑transparent with backdrop blur (
glass-cardclass). - Border: Light white/cyan border, subtle shadow.
- Border radius:
rounded-2xl(1rem) for a soft, modern look. - Padding:
p-6to give comfortable spacing.
3.2. Form Fields
- Input backgrounds: Semi‑transparent white/black depending on dark mode, with
backdrop-blur-sm. - Focus ring: Primary color (indigo/purple) with
focus:ring-2. - Labels: Small, semi‑bold, light gray color.
3.3. Buttons
- Primary button (Submit): Linear gradient from
primarytosecondary(bg-gradient-to-r from-primary to-secondary), white text, rounded‑xl, shadow. - Secondary button (Cancel): Glass background (
bg-white/20) with hover effect.
3.4. Icons
All icons from Lucide React (e.g., Upload, Code, Image, X). They are sized w-5 h-5 and aligned with text.
3.5. Dark Mode
The entire editor automatically adapts to the user’s dark mode preference:
- Background becomes darker with higher opacity.
- Text becomes lighter.
- Borders become more subtle.
4. Complete Code Example (Minimal, Yet Fully Functional)
Below is a self‑contained AppEditor.tsx component that matches the description. It uses Tailwind CSS and integrates with the useAppStore hook.
// src/components/app-store/AppEditor.tsx
import { useState } from 'react';
import { useAppStore } from '@/hooks/useAppStore';
import { X, Upload, Code, Image } from 'lucide-react';
import { GlassCard } from '@/components/ui/GlassCard';
interface AppEditorProps {
onClose: () => void;
}
export function AppEditor({ onClose }: AppEditorProps) {
const { submitPlugin, submitting } = useAppStore();
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [code, setCode] = useState(`<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>My Tool</title></head>
<body style="font-family: system-ui; padding: 1rem;">
<h1>Hello Researcher!</h1>
<p>Edit this code to create your tool.</p>
</body>
</html>`);
const [iconBase64, setIconBase64] = useState('');
const [error, setError] = useState('');
const handleIconUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (ev) => setIconBase64(ev.target?.result as string);
reader.readAsDataURL(file);
}
};
const handleSubmit = async () => {
if (!name.trim()) {
setError('Plugin name is required');
return;
}
if (!code.trim()) {
setError('Plugin code cannot be empty');
return;
}
const formData = new FormData();
formData.append('name', name.trim());
formData.append('description', description.trim());
formData.append('code', code);
if (iconBase64) formData.append('icon', iconBase64);
formData.append('executionMode', 'local');
setError('');
await submitPlugin(formData);
onClose();
};
return (
<GlassCard className="w-full max-w-2xl p-6 space-y-5">
<div className="flex justify-between items-center">
<h2 className="text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Create New Plugin
</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<X className="w-5 h-5" />
</button>
</div>
<div>
<label className="block text-sm font-medium mb-1">Name *</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 glass-card rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
placeholder="e.g., Quick Plotter"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Description *</label>
<textarea
rows={3}
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 glass-card rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
placeholder="Explain what your tool does..."
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Icon (optional)</label>
<div className="flex items-center gap-3">
<label className="glass-card px-3 py-2 rounded-lg cursor-pointer flex items-center gap-1">
<Image className="w-4 h-4" /> Upload
<input type="file" accept="image/*" onChange={handleIconUpload} className="hidden" />
</label>
{iconBase64 && <img src={iconBase64} className="w-8 h-8 rounded" alt="icon preview" />}
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1">HTML Code *</label>
<textarea
rows={10}
value={code}
onChange={(e) => setCode(e.target.value)}
className="w-full font-mono text-sm px-3 py-2 glass-card rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
/>
<div className="flex justify-end mt-1">
<label className="glass-card text-xs px-2 py-1 rounded cursor-pointer">
<Upload className="w-3 h-3 inline mr-1" /> Upload .html
<input type="file" accept=".html" onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (ev) => setCode(ev.target?.result as string);
reader.readAsText(file);
}
}} className="hidden" />
</label>
</div>
</div>
{error && <p className="text-red-500 text-sm">{error}</p>}
<div className="flex gap-3 pt-2">
<button onClick={handleSubmit} disabled={submitting} className="btn-primary flex-1 py-2 rounded-lg flex items-center justify-center gap-2">
{submitting && <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />}
Submit
</button>
<button onClick={onClose} className="btn-secondary flex-1 py-2 rounded-lg">Cancel</button>
</div>
</GlassCard>
);
}
5. Appearance in the Desktop Context
When the App Editor is opened, it is typically displayed as a modal overlay inside the App Store window, or as a separate desktop window (if you prefer). The style is fully consistent with other glass cards:
- Background: Translucent white with blur.
- Borders: 1px light border, rounded corners.
- Input fields: Matte with subtle background.
- Buttons: Rounded, gradient primary, glass secondary.
- Spacing: Comfortable, using Tailwind’s spacing scale.
The editor respects the user’s dark mode preference automatically, adjusting colours and opacities.
6. Integration Flow with the App Store
- User clicks “+” in
PluginStorePage. PluginStorePagesets a local stateshowEditor = true.- The
AppEditorcomponent is rendered as a modal (using a fixed overlay or inside aDesktopWindow). - User fills the form, clicks Submit.
AppEditorcallsuseAppStore.submitPlugin(formData).- The hook sends a
POSTrequest to/api/plugins. - On success, the editor closes, and the App Store refreshes the list (the new plugin appears after approval).
- Error messages are shown inline.
The entire experience is smooth, glassy, and fast – exactly matching the desktop environment’s design language.
7. Future Enhancements
- Live preview: A live iframe preview that updates as the user types, helping debug before submission.
- AI‑assisted filling: For premium users, an AI agent can auto‑generate name and description from the code.
- Template library: Pre‑built templates (chart, form, dashboard) that users can start from.
- Validation: Syntax checking and sandbox testing before submission.
All these enhancements can be added without changing the core UI structure.
Conclusion
The App Editor is a minimal but powerful tool that empowers researchers to publish their own plugins with ease. Its design is fully integrated with the platform’s glass‑morphism theme, and it seamlessly connects to the existing App Store backend and submission pipeline. By understanding each basic element and its appearance, you can extend the editor with additional features (like previews, templates, or AI assistance) while keeping a consistent, user‑friendly interface. 🎨🚀
Xet Storage Details
- Size:
- 318 kB
- Xet hash:
- 8c008c05de4873be6771098aad30d6d78451fa4e2774ad3dd7f3faf6c63eeff0
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.