feat(desktop): store humans as markdown files with YAML frontmatter#2791
feat(desktop): store humans as markdown files with YAML frontmatter#2791
Conversation
- Change human persister to store each human as humans/<UUID>.md - Store structure info (user_id, created_at, name, email, etc.) as YAML frontmatter - Store memo field as markdown body content - Add automatic migration from humans.json to new format - Add cleanup of orphan human files when humans are deleted - Add yaml dependency for frontmatter parsing - Add 'text' operation type to persister utils for raw text file writes - Update tests for new markdown file format Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
✅ Deploy Preview for hyprnote canceled.
|
✅ Deploy Preview for hyprnote-storybook canceled.
|
✅ Deploy Preview for howto-fix-macos-audio-selection canceled.
|
| const dirExists = await exists(humansDir); | ||
| if (dirExists) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Migration incompleteness bug: If both humans.json and the humans/ directory exist, the migration exits early without deleting the old JSON file. This can occur if a previous migration was interrupted after creating the directory but before deleting the JSON file. On subsequent runs, humans.json will persist indefinitely, potentially causing data inconsistency.
Fix: Change the logic to attempt cleanup of humans.json whenever it exists and the directory exists:
const dirExists = await exists(humansDir);
if (dirExists) {
// Directory exists - cleanup old JSON if present
try {
await remove(humansJsonPath);
console.log("[HumanPersister] Cleaned up old humans.json file");
} catch (error) {
if (!isFileNotFoundError(error)) {
console.error("[HumanPersister] Failed to cleanup humans.json:", error);
}
}
return;
}| const dirExists = await exists(humansDir); | |
| if (dirExists) { | |
| return; | |
| } | |
| const dirExists = await exists(humansDir); | |
| if (dirExists) { | |
| // Directory exists - cleanup old JSON if present | |
| try { | |
| await remove(humansJsonPath); | |
| console.log("[HumanPersister] Cleaned up old humans.json file"); | |
| } catch (error) { | |
| if (!isFileNotFoundError(error)) { | |
| console.error("[HumanPersister] Failed to cleanup humans.json:", error); | |
| } | |
| } | |
| return; | |
| } |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
- Fix Content<Schemas> return type using 'as unknown as' pattern - Add missing DirEntry properties (isFile, isSymlink) in test mocks - Fix exists mock to accept string | URL parameter type - Remove unused asTableChanges import Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
…n-persister-markdown
- Add frontmatter crate as dependency to export plugin - Add parse_frontmatter, serialize_frontmatter, export_frontmatter, and export_frontmatter_batch commands - Update human persister to use plugin commands instead of yaml npm package - Add frontmatter-batch operation type to createSessionDirPersister - Remove yaml npm dependency from desktop package.json - Update tests to mock new plugin commands Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
| "[HumanPersister] Failed to parse frontmatter:", | ||
| result.error, | ||
| ); | ||
| return { frontmatter: {}, body: content }; |
There was a problem hiding this comment.
The .trim() call modifies user data by removing leading/trailing whitespace from memo content. This creates an asymmetric transformation:
Issue: When a memo is saved with intentional trailing/leading whitespace → loaded (trimmed) → saved again without modification, the whitespace is permanently lost.
Impact: Data loss for users who intentionally format their memos with specific whitespace.
Fix:
body: result.data.content, // Remove .trim()If trimming is needed for display purposes, it should be done in the UI layer, not during data persistence.
| return { frontmatter: {}, body: content }; | |
| body: result.data.content, |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
| } | ||
| return { | ||
| frontmatter: result.data.frontmatter as Record<string, unknown>, | ||
| body: result.data.content.trim(), |
There was a problem hiding this comment.
The body content is trimmed after parsing, which will cause data loss on round-trip if the memo field contains intentional trailing/leading whitespace or newlines. When saving (collect.ts:46), the memo is stored as-is, but when loading, it's trimmed. This creates data integrity issues.
// Fix: Remove the trim() to preserve exact memo content
body: result.data.content,| body: result.data.content.trim(), | |
| body: result.data.content, |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
…ontmatter - Change organization persister from JSON to markdown files with frontmatter - Store each organization as organizations/<UUID>.md - Add auto-migration from organizations.json on first load - Add cleanup of orphan files when records are deleted - Follow the same pattern as the human persister from PR #2791 Organization schema fields (user_id, created_at, name) are stored in YAML frontmatter. The markdown body is always empty since organizations have no memo field. Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
feat(desktop): store humans as markdown files with YAML frontmatter
Summary
Changes the human persister to store each human as an individual markdown file at
humans/<UUID>.mdinstead of a singlehumans.jsonfile. Structure info is stored as YAML frontmatter, and thememofield becomes the markdown body content.Example output format:
Key changes:
collect.ts,load.ts,migrate.ts,utils.tsin the human persister directoryfrontmatterRust crate via export plugin (no npm yaml dependency)parse_frontmatter,serialize_frontmatter,export_frontmatter,export_frontmatter_batchfrontmatter-batchoperation type inpersister/utils.tsfor batch writeshumans.json(deletes old file after migration)Updates since last revision
Review & Testing Checklist for Human
humans.jsondata. The migration deletes the old JSON file after batch export - verify no data loss occurs if the app crashes mid-migration.Recommended test plan:
humans.jsonfile with test data in the app's data directory.mdfiles are created inhumans/directoryNotes
pnpm-lock.yamlchanges are pnpm re-resolving transitive dependencies after removing the yaml package---delimiters and sorts keys alphabeticallyLink to Devin run: https://app.devin.ai/sessions/b519cf12b5db4537b1c8a6f706af4943
Requested by: yujonglee (@yujonglee)