Notifications, scheduled tasks, global Assets library, and workspace enhancements#4
Conversation
Use beautiful-mermaid with markview-style layout scaling, browse/edit source modes, and roadmap docs for upcoming Markdown import/export. Co-authored-by: Cursor <cursoragent@cursor.com>
Add frontmatter serialization for page meta and fields, wire Export Markdown in page settings, and expose client subpath exports needed by headless editor tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Parse YAML frontmatter into meta type and fields, convert body via Tiptap markdown, and create the page document in one flow. Co-authored-by: Cursor <cursoragent@cursor.com>
…d normalizing output. Add a function to prepend the page title as a heading in the exported Markdown body. Normalize the serialized Markdown output to ensure consistent formatting. Update related tests to verify new functionality.
…ore. Strip exported title headings on import, map colanode links back to resource and block nodes, and add round-trip tests plus roadmap updates. Co-authored-by: Cursor <cursoragent@cursor.com>
…ment features Add new database tables for notifications and scheduled tasks, along with corresponding mutation and query handlers. Integrate notification service into the app and enhance the UI to support notification settings and scheduled task management. Update sidebar to include notifications and scheduled tasks sections, ensuring a cohesive user experience. Co-authored-by: Cursor <cursoragent@cursor.com>
Refactor scheduled task checks to improve field matching by introducing new helper functions for resolving field definitions and handling date fields. Update the ScheduledTaskConditionEditor to utilize a more streamlined approach for managing condition fields, including better handling of virtual fields and improved user feedback for unsupported properties. Co-authored-by: Cursor <cursoragent@cursor.com>
…ndling Refactor scheduled task checks to introduce a new 'none' type for reminders and improve the handling of group checks. Update the ScheduledTaskConditionEditor and related components to support the new structure, ensuring better user experience and condition management. Introduce utility functions for formatting condition previews and validating task inputs. Co-authored-by: Cursor <cursoragent@cursor.com>
Allow deleting notifications from the sidebar, show relative timestamps, navigate reminder notifications to scheduled tasks, and dedupe pure reminders to once per local day with clearer body text. Co-authored-by: Cursor <cursoragent@cursor.com>
…tion Refactor the DesktopNotificationService to manage notification clicks and navigate to specific targets within the app. Introduce a new method for focusing the main window and expose a callback for notification navigation in the preload script. Update the UI to utilize the new notification navigation feature. Co-authored-by: Cursor <cursoragent@cursor.com>
Enhance the task execution process by introducing a `forceNotify` option in the `WorkspaceTaskRunInput` type. Update the `executeTask` method to utilize this option, allowing for more flexible notification handling. Modify the UI to reflect changes in success messaging for task execution.
Add a new method to the DesktopNotificationService for sending fallback notifications when desktop notifications fail. Expose an `onNotificationFallback` callback in the preload script and integrate it into the UI components to enhance user experience with in-app notifications. Update the scheduled task form to clarify notification behavior for desktop users.
LOCAL_ONLY packages are now ad-hoc signed after packaging so Electron can show system notifications during local testing. Co-authored-by: Cursor <cursoragent@cursor.com>
…k checks Introduce a new `context` field in the ScheduledTaskTable to store contextual information. Update the mapping logic in `mapScheduledTask` to parse and handle the context. Enhance scheduled task checks by adding new record leaf check types and utility functions for better condition handling. Update UI components to support context-aware reminders and improve validation for scheduled task inputs.
…enhance task handling Add a new `scheduled_task_state` table to manage the state of scheduled tasks, including fields for tracking the last run time and result hash. Update the `ScheduledTaskService` to handle state operations, including upserting and deleting task states. Enhance the mapping logic to incorporate the new state information and improve task loading. Additionally, introduce a `ScheduledTaskScope` type for better context handling in scheduled tasks, ensuring a more robust and flexible task management system.
Use a ref-forwarding value slot for select and multi-select fields so popover triggers work again, and provide consistent clickable empty-state space in page/record properties. Co-authored-by: Cursor <cursoragent@cursor.com>
…field details Enhance documentation for page properties, clarifying editable fields and schema edit limitations. Introduce details about the new 'Reminders' system field, its functionality, and its integration with scheduled tasks. Update roadmap to reflect current capabilities and future enhancements related to notifications and task management.
Drop workspace-level tags, tag routes, and node tag UI so classification lives fully in meta-type properties and database fields. Co-authored-by: Cursor <cursoragent@cursor.com>
Add one-click property templates for meta types, align reminders with readonly rendering in non-table views, and fix empty-value/date/collaborator plus modal-focus interactions in properties. Co-authored-by: Cursor <cursoragent@cursor.com>
Add archiving capabilities to database, folder, and page components, allowing users to archive and restore nodes. Introduce UI elements for toggling archive state and update sidebar items to reflect archived status. Enhance query handlers to filter out archived nodes from mention searches. Co-authored-by: Cursor <cursoragent@cursor.com>
Exclude archived pages from meta-type database views, relation search, scheduled task checks, and command palette lookup via a shared helper. Co-authored-by: Cursor <cursoragent@cursor.com>
…aries Add a new feature to allow users to import files from a selected directory into the application. This includes the creation of temporary files with appropriate MIME types and relative paths. Update the UI to support the import dialog and enhance the folder creation process to accommodate asset libraries. Co-authored-by: Cursor <cursoragent@cursor.com>
…support and improved database handling - Introduced a new folder field in the asset library to organize assets. - Updated the asset library bundle to include a folder view and associated fields. - Enhanced the folder creation process to ensure proper integration with the asset library. - Implemented functions to ensure folder options are available in the asset library database. - Updated UI components to reflect changes in asset management and improve user experience. Co-authored-by: Cursor <cursoragent@cursor.com>
…ndling - Added new views (table and list) to the asset library bundle for enhanced asset organization. - Updated the asset library bundle return structure to include gallery, table, and list views. - Modified the folder creation process to ensure proper database integration without requiring a root ID. - Enhanced the folder container component to support dynamic layout switching between gallery, list, and table views. - Implemented legacy file backfilling functionality to migrate existing files into the asset library structure. Co-authored-by: Cursor <cursoragent@cursor.com>
Copy only main-process native deps during prePackage instead of the full monorepo node_modules, limit electron-rebuild to better-sqlite3, and bump better-sqlite3 for Node 26 support. Co-authored-by: Cursor <cursoragent@cursor.com>
- Added a new method to the BackupService for deleting backups, ensuring proper management of backup files and associated metadata. - Introduced IPC handlers for backup deletion in both desktop and web applications, allowing users to delete backups through the UI. - Updated the BackupPanel component to include a delete option, with confirmation dialogs to prevent accidental deletions. - Enhanced unit tests to cover the new deleteBackup functionality, ensuring robustness and reliability. Co-authored-by: Cursor <cursoragent@cursor.com>
…se integration - Introduced a new function to build missing asset library views, ensuring all necessary layouts are created dynamically. - Updated the asset library bundle to utilize the new view fields and ensure proper integration of the asset library purpose. - Enhanced the database container to ensure asset library databases are correctly initialized and updated with the appropriate views. - Refactored existing code to improve clarity and maintainability, including the use of helper functions for view management. Co-authored-by: Cursor <cursoragent@cursor.com>
…idebar navigation - Added functionality to ensure asset library databases are relocated to a dedicated meta space for better organization. - Introduced a new helper function to identify asset library system databases and redirect users accordingly. - Updated sidebar components to include links to asset library settings, improving user navigation. - Refactored existing code to streamline asset library view management and enhance overall maintainability. Co-authored-by: Cursor <cursoragent@cursor.com>
- Refactored the ensureFolderFieldOption function to use the database object directly instead of the database ID, improving clarity and consistency. - Updated multiple instances of ensureFolderFieldOption calls across the asset library to align with the new function signature. - Introduced a new utility function, updateNodeInCollection, to streamline node updates in the asset library, enhancing maintainability and reducing code duplication. Co-authored-by: Cursor <cursoragent@cursor.com>
…ints Add implementation docs, collection-update tests, global Assets shortcuts in the command palette and folder toolbar, and update the roadmap. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Code Review
This pull request introduces several major features, including a workspace-level global Asset Library, a robust Scheduled Tasks and Notifications system, Markdown import/export capabilities with frontmatter support, Mermaid diagram rendering in code blocks, and node archiving. It also adds backup deletion functionality and cleans up legacy tag-syncing code. The code review identified several critical issues: potential ReferenceErrors in forge.config.ts and field-create-popover.tsx due to missing variables or imports; performance and memory risks in main.ts and asset-library.ts from in-memory file reading and un-filtered node queries; a timezone bug in task date parsing; a data integrity risk during backup deletion; potential data loss when toggling scheduled tasks; a YAML serialization bug with reserved keywords; and a localization inconsistency in the backup panel.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| }, | ||
| postPackage: async () => { | ||
| postPackage: async (_forgeConfig, packageResult) => { | ||
| if (isLocalOnly && process.platform === 'darwin') { |
There was a problem hiding this comment.
ReferenceError Risk
isLocalOnly is used here but it does not appear to be defined or imported in this file. This will cause a ReferenceError during the post-package step. Use process.env.LOCAL_ONLY === 'true' directly or define isLocalOnly at the top of the file.
| if (isLocalOnly && process.platform === 'darwin') { | |
| if (process.env.LOCAL_ONLY === 'true' && process.platform === 'darwin') { |
| for (const entry of entries) { | ||
| try { | ||
| const buffer = await fs.promises.readFile(entry.absolutePath); | ||
| const mimeType = guessMimeTypeFromName(entry.name); | ||
| const tempFile = await createTempFileFromBuffer(app, { | ||
| buffer, | ||
| name: entry.name, | ||
| mimeType, | ||
| size: buffer.byteLength, | ||
| }); | ||
|
|
||
| imported.push({ | ||
| ...tempFile, | ||
| relativePath: entry.relativePath, | ||
| }); |
There was a problem hiding this comment.
Performance & Memory Optimization
Reading the entire file into memory using fs.promises.readFile is highly inefficient and can cause out-of-memory (OOM) crashes for large files. Since the files are already on the local disk, you should copy them directly using fs.promises.copyFile instead of loading their contents into memory.
for (const entry of entries) {
try {
const stat = await fs.promises.stat(entry.absolutePath);
const mimeType = guessMimeTypeFromName(entry.name);
const id = generateId(IdType.TempFile);
const extension = app.path.extension(entry.name);
const subtype = extractFileSubtype(mimeType);
const filePath = app.path.tempFile(id + extension);
await fs.promises.copyFile(entry.absolutePath, filePath);
await app.database
.insertInto('temp_files')
.values({
id,
name: entry.name,
size: stat.size,
mime_type: mimeType,
subtype,
path: filePath,
extension,
created_at: new Date().toISOString(),
opened_at: new Date().toISOString(),
})
.execute();
const url = await app.fs.url(filePath);
if (!url) {
await app.fs.delete(filePath);
await app.database
.deleteFrom('temp_files')
.where('id', '=', id)
.execute();
throw new Error('Failed to save temp file');
}
imported.push({
id,
name: entry.name,
size: stat.size,
mimeType,
subtype,
path: filePath,
extension,
url,
relativePath: entry.relativePath,
});
} catch (error) {| const parseFieldDate = (fieldValue: unknown): Date | null => { | ||
| if (!fieldValue || typeof fieldValue !== 'object' || !('value' in fieldValue)) { | ||
| return null; | ||
| } | ||
|
|
||
| const raw = (fieldValue as { value: unknown }).value; | ||
| if (typeof raw !== 'string' || raw.length === 0) { | ||
| return null; | ||
| } | ||
|
|
||
| const date = new Date(raw); | ||
| if (Number.isNaN(date.getTime())) { | ||
| return null; | ||
| } | ||
|
|
||
| return date; | ||
| }; |
There was a problem hiding this comment.
Timezone Bug
Parsing date-only strings (like 'YYYY-MM-DD') with new Date(raw) parses them as UTC midnight. Comparing this UTC date with a local-time-based endOfToday will cause reminders to trigger a day early or late depending on the user's local timezone offset. For example, a reminder for June 15th might trigger on the evening of June 14th in western timezones. Parse date-only strings in local time (e.g., by replacing hyphens with slashes: new Date(raw.replace(/-/g, '/'))) to avoid timezone shifts.
const parseFieldDate = (fieldValue: unknown): Date | null => {
if (!fieldValue || typeof fieldValue !== 'object' || !('value' in fieldValue)) {
return null;
}
const raw = (fieldValue as { value: unknown }).value;
if (typeof raw !== 'string' || raw.length === 0) {
return null;
}
const dateString = /^\d{4}-\d{2}-\d{2}$/.test(raw)
? raw.replace(/-/g, '/')
: raw;
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) {
return null;
}
return date;
};| for (const key of otherManifestKeys) { | ||
| try { | ||
| const data = await this.provider.download(key); | ||
| const otherManifest = JSON.parse(data.toString()) as BackupManifest; | ||
| for (const file of otherManifest.files) { | ||
| hashesStillNeeded.add(file.sha256); | ||
| } | ||
| } catch { | ||
| debug(`Failed to read manifest while deleting backup: ${key}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Data Integrity Risk
If downloading or parsing any of the other manifests fails (e.g., due to a transient network error), the catch block silently ignores it. This means hashesStillNeeded will be incomplete, and you may permanently delete shared blobs that are still referenced by those other backups, corrupting them. You should abort the deletion or avoid deleting blobs if any other manifest cannot be successfully read.
for (const key of otherManifestKeys) {
const data = await this.provider.download(key);
const otherManifest = JSON.parse(data.toString()) as BackupManifest;
for (const file of otherManifest.files) {
hashesStillNeeded.add(file.sha256);
}
}| const maxIndex = Object.values(options) | ||
| .map((option) => option.index) | ||
| .sort((a, b) => -compareString(a, b))[0]; | ||
| const index = generateFractionalIndex(maxIndex, null); |
There was a problem hiding this comment.
ReferenceError Risk
compareString is used here but it is not imported from @colanode/core in this file. This will cause a runtime ReferenceError. You can import compareString from @colanode/core, or simply use the native localeCompare method (e.g., b.index.localeCompare(a.index)) which is standard and requires no imports.
| const maxIndex = Object.values(options) | |
| .map((option) => option.index) | |
| .sort((a, b) => -compareString(a, b))[0]; | |
| const index = generateFractionalIndex(maxIndex, null); | |
| const maxIndex = Object.values(options) | |
| .map((option) => option.index) | |
| .sort((a, b) => b.localeCompare(a))[0]; | |
| const index = generateFractionalIndex(maxIndex ?? null, null); |
| task: { | ||
| name: task.name, | ||
| enabled, | ||
| schedule: task.schedule, | ||
| check: task.check, | ||
| notify: task.notify, | ||
| }, |
There was a problem hiding this comment.
Correctness / Data Loss
In handleToggleEnabled, the scope and context properties of the task are omitted when calling the update mutation. This will cause the task's scope and context to be cleared or incorrectly re-derived, potentially breaking the reminder's target association. Please pass scope: task.scope and context: task.context to preserve them.
task: {
name: task.name,
enabled,
schedule: task.schedule,
check: task.check,
notify: task.notify,
scope: task.scope,
context: task.context,
},
| <AlertDialogTitle>确认删除备份</AlertDialogTitle> | ||
| <AlertDialogDescription> | ||
| 将永久删除该备份,无法恢复。其他备份中仍被引用的文件块会保留以节省空间。 | ||
| </AlertDialogDescription> |
There was a problem hiding this comment.
i18n Consistency
The new backup deletion dialog and toast messages are written in Chinese, whereas the rest of the file and settings panel (like 'Restore', 'Delete backup', 'Scheduled Tasks') are in English. To maintain consistency, please translate these strings to English or use the project's i18n localization system if available.
| <AlertDialogTitle>确认删除备份</AlertDialogTitle> | |
| <AlertDialogDescription> | |
| 将永久删除该备份,无法恢复。其他备份中仍被引用的文件块会保留以节省空间。 | |
| </AlertDialogDescription> | |
| <AlertDialogTitle>Confirm Backup Deletion</AlertDialogTitle> | |
| <AlertDialogDescription> | |
| This will permanently delete the backup. It cannot be undone. Shared file blocks referenced by other backups will be kept to save space. | |
| </AlertDialogDescription> |
| const yamlScalar = (value: string | number | boolean): string => { | ||
| if (typeof value === 'number' || typeof value === 'boolean') { | ||
| return String(value); | ||
| } | ||
|
|
||
| if ( | ||
| value === '' || | ||
| /[:#\n\r]/.test(value) || | ||
| value.startsWith(' ') || | ||
| value.endsWith(' ') | ||
| ) { | ||
| return JSON.stringify(value); | ||
| } | ||
|
|
||
| return value; | ||
| }; |
There was a problem hiding this comment.
YAML Serialization Bug
In yamlScalar, string values that look like booleans ("true", `
const yamlScalar = (value: string | number | boolean): string => {
if (typeof value === 'number' || typeof value === 'boolean') {
return String(value);
}
const lower = value.toLowerCase();
const isReservedKeyword =
lower === 'true' ||
lower === 'false' ||
lower === 'yes' ||
lower === 'no' ||
lower === 'on' ||
lower === 'off' ||
lower === 'null';
const isNumeric = /^-?\d+(\.\d+)?$/.test(value);
if (
value === '' ||
isReservedKeyword ||
isNumeric ||
/[:#\n\r]/.test(value) ||
value.startsWith(' ') ||
value.endsWith(' ')
) {
return JSON.stringify(value);
}
return value;
};| const nodes = await window.colanode.executeQuery({ | ||
| type: 'node.list', | ||
| userId: workspace.userId, | ||
| filters: [ | ||
| { | ||
| field: ['type'], | ||
| operator: 'in', | ||
| value: ['database_view', 'record', 'file'], | ||
| }, | ||
| ], | ||
| sorts: [], | ||
| }); |
There was a problem hiding this comment.
Performance & Scalability
relocateAssetLibraryToMetaSpace fetches all database_view, record, and file nodes in the entire workspace and filters them in memory. For workspaces with a large number of files or records, this will be extremely slow and consume significant memory. Consider querying only the nodes associated with the target database by applying filters (e.g., filtering by parentId or databaseId) directly in the node.list query.
Summary
asset_librarydatabase (like Scheduled Tasks); folders are filter tags; Settings → Assets with Gallery/Table/List/By folder; migration to meta-types space; collection-safe updates for on-demand node syncTest plan
LOCAL_ONLY=true npm run packageon Node 22; smoke-test app launchnpm run testinpackages/clientandpackages/uiMade with Cursor