diff --git a/apps/studymesh/src/components/Dasboard/Dashboard.tsx b/apps/studymesh/src/components/Dasboard/Dashboard.tsx
index 37faad33..4ab2bee6 100644
--- a/apps/studymesh/src/components/Dasboard/Dashboard.tsx
+++ b/apps/studymesh/src/components/Dasboard/Dashboard.tsx
@@ -92,6 +92,52 @@ const DEFAULT_STUDY_PATH_OPENED_KEY = 'studymesh-default-study-path-opened-v1'
const USER_ROLE_CHANGED_EVENT = 'studymesh-user-role-changed'
const OPEN_SAVED_DASHBOARDS_EVENT = 'studymesh-open-saved-dashboards'
+const createNotesPageDashboard = (markdown: string): DefaultDashboard => {
+ const createdAt = new Date().toISOString()
+ const titleMatch = markdown.match(/^#\s+(.+)$/m)
+ const title = titleMatch?.[1]?.trim() || 'Untitled notes'
+ const widgetId = `notes-page-${Date.now()}`
+
+ return {
+ name: title,
+ layout: {
+ type: 'row',
+ weight: 100,
+ children: [
+ {
+ type: 'tabset',
+ weight: 100,
+ active: true,
+ selected: 0,
+ children: [
+ {
+ type: 'tab',
+ name: 'Notes',
+ component: 'CustomWidget',
+ config: {
+ customProps: {
+ widgetId,
+ components: [
+ {
+ id: `${widgetId}-markdown`,
+ type: 'MarkdownBlock',
+ props: {
+ __blockType: 'MarkdownBlock',
+ title: 'Scratch notes',
+ markdown,
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ }
+}
+
const Dashboards = () => {
const {
theme,
@@ -448,6 +494,25 @@ const Dashboards = () => {
addDashboard()
}
+ const startNotesPageDashboard = (markdown: string) => {
+ if (!isAdmin || !markdown.trim()) {
+ return
+ }
+
+ const notesDashboard = createNotesPageDashboard(markdown.trim())
+
+ if (openDashboards[selectedDashboard] && selectedDashboardIsEmpty) {
+ replaceDashboard(selectedDashboard, notesDashboard)
+ } else {
+ addDashboard(notesDashboard)
+ }
+
+ dispatchWorkspaceOnboardingEvent({
+ type: 'saved-dashboard-opened',
+ dashboardName: notesDashboard.name,
+ })
+ }
+
const openSavedDashboardInWorkspace = (dashboard: SavedDashboard) => {
if (openDashboards[selectedDashboard] && selectedDashboardIsEmpty) {
replaceDashboard(selectedDashboard, {
@@ -1459,6 +1524,7 @@ const Dashboards = () => {
onMessagesChange={(messages) =>
updateDashboardChatMessages(currentDashboard, messages)
}
+ onCreateNotesPage={startNotesPageDashboard}
onClose={closeDashboardChatPanel}
/>
)
@@ -1604,6 +1670,7 @@ const Dashboards = () => {
onUploadMaterial={() => openCreationSources('upload')}
onPasteNotes={() => openCreationSources('paste')}
onQuickCreate={openQuickCreateFromEmptyDashboard}
+ onStartNotesPage={startNotesPageDashboard}
onOpenSavedLibrary={openSavedLibraryFromEmptyState}
dashboardOptions={visibleDashboardOptions}
onOpenDashboard={openSavedDashboardInWorkspace}
@@ -1813,6 +1880,7 @@ const Dashboards = () => {
onUploadMaterial={() => openCreationSources('upload')}
onPasteNotes={() => openCreationSources('paste')}
onQuickCreate={openQuickCreateFromEmptyDashboard}
+ onStartNotesPage={startNotesPageDashboard}
onOpenSavedLibrary={openSavedLibraryFromEmptyState}
dashboardOptions={visibleDashboardOptions}
onOpenDashboard={openSavedDashboardFromEmptyState}
diff --git a/apps/studymesh/src/components/Dasboard/DashboardEmptyState.tsx b/apps/studymesh/src/components/Dasboard/DashboardEmptyState.tsx
index dbbd3360..45b8ad8b 100644
--- a/apps/studymesh/src/components/Dasboard/DashboardEmptyState.tsx
+++ b/apps/studymesh/src/components/Dasboard/DashboardEmptyState.tsx
@@ -1,5 +1,13 @@
-import React from 'react'
-import { Box, Button, Chip, Paper, Stack, Typography } from '@mui/material'
+import React, { useState } from 'react'
+import {
+ Box,
+ Button,
+ Chip,
+ Paper,
+ Stack,
+ TextField,
+ Typography,
+} from '@mui/material'
import { alpha } from '@mui/material/styles'
import AutoStoriesIcon from '@mui/icons-material/AutoStories'
import CloudUploadIcon from '@mui/icons-material/CloudUpload'
@@ -23,6 +31,7 @@ interface DashboardEmptyStateProps {
onUploadMaterial: () => void
onPasteNotes: () => void
onQuickCreate: (intent: EmptyDashboardQuickCreate) => void
+ onStartNotesPage: (markdown: string) => void
onOpenSavedLibrary: () => void
dashboardOptions: SavedDashboard[]
onOpenDashboard: (dashboard: SavedDashboard) => void
@@ -61,6 +70,7 @@ const DashboardEmptyState = ({
onUploadMaterial,
onPasteNotes,
onQuickCreate,
+ onStartNotesPage,
onOpenSavedLibrary,
dashboardOptions,
onOpenDashboard,
@@ -87,6 +97,8 @@ const DashboardEmptyState = ({
: 'Open existing study guide'
: 'Open existing dashboard'
const featuredFolders = folderEntries.slice(0, 4)
+ const [notesDraft, setNotesDraft] = useState('')
+ const normalizedNotesDraft = notesDraft.trim()
return (
+ alpha(theme.palette.primary.main, 0.24),
+ bgcolor: (theme) => alpha(theme.palette.primary.main, 0.055),
+ }}
+ >
+
+
+
+ Start with notes, not AI
+
+
+ Write freely in Markdown like a blank Notion page. It opens as
+ a temporary dashboard with one notes widget, and it only
+ becomes a saved dashboard when you save it.
+
+
+ setNotesDraft(event.target.value)}
+ placeholder={
+ '# My notes\n\nDump your thoughts, links, questions, or source material here...'
+ }
+ multiline
+ minRows={5}
+ fullWidth
+ inputProps={{ 'aria-label': 'Markdown notes draft' }}
+ sx={{
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 2,
+ bgcolor: 'background.paper',
+ alignItems: 'flex-start',
+ fontFamily: 'JetBrains Mono, monospace',
+ fontSize: '0.92rem',
+ },
+ }}
+ />
+
+
+
+
+
+
+
void
+ onCreateNotesPage?: (markdown: string) => void
onClose: () => void
}
@@ -56,11 +57,13 @@ const DashboardChatPanel = ({
dashboard,
messages,
onMessagesChange,
+ onCreateNotesPage,
onClose,
}: DashboardChatPanelProps) => {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
const [draft, setDraft] = useState('')
+ const [notesDraft, setNotesDraft] = useState('')
const [error, setError] = useState('')
const [activeStartedAt, setActiveStartedAt] = useState(null)
const [elapsedSeconds, setElapsedSeconds] = useState(0)
@@ -289,6 +292,57 @@ const DashboardChatPanel = ({
+ {onCreateNotesPage && (
+
+
+
+
+ Private notes lane
+
+
+ Think out loud without asking AI. Turn it into a temporary
+ Markdown dashboard when it becomes useful.
+
+
+ setNotesDraft(event.target.value)}
+ placeholder="Write your own notes here..."
+ multiline
+ minRows={3}
+ fullWidth
+ size="small"
+ inputProps={{ 'aria-label': 'Private notes draft' }}
+ />
+
+
+
+ )}
{
expect(
screen.getByRole('button', { name: /paste notes/i }),
).toBeInTheDocument()
+ expect(screen.getByText(/start with notes, not ai/i)).toBeInTheDocument()
+ expect(
+ screen.getByRole('textbox', { name: /markdown notes draft/i }),
+ ).toBeInTheDocument()
expect(
screen.getByRole('button', { name: /create quiz/i }),
).toBeInTheDocument()
@@ -246,6 +250,30 @@ describe('Dashboards', () => {
).not.toBeInTheDocument()
})
+ it('turns an empty dashboard notes draft into a temporary Markdown dashboard', () => {
+ const replaceDashboard = vi.fn()
+ mockDashboardProvider({ replaceDashboard })
+
+ render()
+
+ fireEvent.change(
+ screen.getByRole('textbox', { name: /markdown notes draft/i }),
+ { target: { value: '# Biology dump\n\n- cells need energy' } },
+ )
+ fireEvent.click(screen.getByRole('button', { name: /open notes page/i }))
+
+ expect(replaceDashboard).toHaveBeenCalledWith(
+ 0,
+ expect.objectContaining({
+ name: 'Biology dump',
+ layout: expect.objectContaining({ type: 'row' }),
+ }),
+ )
+ expect(JSON.stringify(replaceDashboard.mock.calls[0][1].layout)).toContain(
+ 'cells need energy',
+ )
+ })
+
it('opens the Study Path modal when the primary empty workspace action is used', () => {
const createHubListener = vi.fn()
window.addEventListener('studymesh-open-create-hub', createHubListener)