diff --git a/src-tauri/src/vault.rs b/src-tauri/src/vault.rs index 14f6e479..930dd0c6 100644 --- a/src-tauri/src/vault.rs +++ b/src-tauri/src/vault.rs @@ -34,6 +34,10 @@ pub struct VaultEntry { /// Generic relationship fields: any frontmatter key whose value contains wikilinks. /// Key is the original frontmatter field name (e.g. "Has", "Topics", "Events"). pub relationships: HashMap>, + /// Phosphor icon name (kebab-case) for Type entries, e.g. "cooking-pot". + pub icon: Option, + /// Accent color key for Type entries: "red", "purple", "blue", "green", "yellow", "orange". + pub color: Option, } /// Intermediate struct to capture YAML frontmatter fields. @@ -57,6 +61,10 @@ struct Frontmatter { created_at: Option, #[serde(rename = "Created time")] created_time: Option, + #[serde(default)] + icon: Option, + #[serde(default)] + color: Option, } /// Handles YAML fields that can be either a single string or a list of strings. @@ -203,6 +211,7 @@ fn parse_frontmatter(data: &HashMap) -> Frontmatter { /// Only skip keys that can never contain wikilinks. const SKIP_KEYS: &[&str] = &[ "is a", "aliases", "status", "cadence", "created at", "created time", + "icon", "color", ]; /// Check if a string contains a wikilink pattern `[[...]]`. @@ -352,6 +361,8 @@ pub fn parse_md_file(path: &Path) -> Result { owner: frontmatter.owner, cadence: frontmatter.cadence, modified_at, created_at, file_size, + icon: frontmatter.icon, + color: frontmatter.color, }) } diff --git a/src/components/Editor.test.tsx b/src/components/Editor.test.tsx index 313f523d..d65b4460 100644 --- a/src/components/Editor.test.tsx +++ b/src/components/Editor.test.tsx @@ -49,6 +49,8 @@ const mockEntry: VaultEntry = { fileSize: 1024, snippet: '', relationships: {}, + icon: null, + color: null, } const mockContent = `--- diff --git a/src/components/Inspector.test.tsx b/src/components/Inspector.test.tsx index d3a1cc63..555ab734 100644 --- a/src/components/Inspector.test.tsx +++ b/src/components/Inspector.test.tsx @@ -19,6 +19,8 @@ const mockEntry: VaultEntry = { fileSize: 1024, snippet: '', relationships: {}, + icon: null, + color: null, } const mockContent = `--- @@ -54,6 +56,8 @@ const referrerEntry: VaultEntry = { fileSize: 200, snippet: '', relationships: {}, + icon: null, + color: null, } const allContent: Record = { diff --git a/src/components/NoteList.test.tsx b/src/components/NoteList.test.tsx index b07d369f..fc6834a1 100644 --- a/src/components/NoteList.test.tsx +++ b/src/components/NoteList.test.tsx @@ -25,6 +25,8 @@ const mockEntries: VaultEntry[] = [ relationships: { 'Related to': ['[[topic/software-development]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/note/facebook-ads-strategy.md', @@ -45,6 +47,8 @@ const mockEntries: VaultEntry[] = [ 'Belongs to': ['[[project/26q1-laputa-app]]'], 'Related to': ['[[topic/growth]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/person/matteo-cellini.md', @@ -62,6 +66,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 320, snippet: 'Sponsorship manager.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/event/2026-02-14-kickoff.md', @@ -79,6 +85,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 512, snippet: 'Project kickoff meeting notes.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/topic/software-development.md', @@ -96,6 +104,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 256, snippet: 'Frontend, backend, and systems programming.', relationships: {}, + icon: null, + color: null, }, ] diff --git a/src/components/Sidebar.test.tsx b/src/components/Sidebar.test.tsx index 281a63fb..8a0ac073 100644 --- a/src/components/Sidebar.test.tsx +++ b/src/components/Sidebar.test.tsx @@ -20,6 +20,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 1024, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/responsibility/grow-newsletter.md', @@ -37,6 +39,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 512, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/experiment/stock-screener.md', @@ -54,6 +58,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 256, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/procedure/weekly-essays.md', @@ -71,6 +77,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 128, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/topic/software-development.md', @@ -88,6 +96,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 256, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/topic/trading.md', @@ -105,6 +115,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 180, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/person/alice.md', @@ -122,6 +134,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 100, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/event/kickoff.md', @@ -139,6 +153,8 @@ const mockEntries: VaultEntry[] = [ fileSize: 200, snippet: '', relationships: {}, + icon: null, + color: null, }, ] @@ -283,6 +299,8 @@ describe('Sidebar', () => { fileSize: 200, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/type/book.md', @@ -300,6 +318,8 @@ describe('Sidebar', () => { fileSize: 200, snippet: '', relationships: {}, + icon: null, + color: null, }, { path: '/vault/recipe/pasta.md', @@ -317,6 +337,8 @@ describe('Sidebar', () => { fileSize: 300, snippet: '', relationships: {}, + icon: null, + color: null, }, ] @@ -362,6 +384,8 @@ describe('Sidebar', () => { fileSize: 200, snippet: '', relationships: {}, + icon: null, + color: null, } render( {}} />) // "Projects" should appear once (the built-in section), not twice diff --git a/src/hooks/useNoteActions.ts b/src/hooks/useNoteActions.ts index ad9bd0eb..f844d02c 100644 --- a/src/hooks/useNoteActions.ts +++ b/src/hooks/useNoteActions.ts @@ -214,7 +214,7 @@ export function useNoteActions( aliases: [], belongsTo: [], relatedTo: [], status: noStatusTypes.has(type) ? null : 'Active', owner: null, cadence: null, modifiedAt: now, createdAt: now, fileSize: 0, - snippet: '', relationships: {}, + snippet: '', relationships: {}, icon: null, color: null, } const frontmatter = [ @@ -242,7 +242,7 @@ export function useNoteActions( aliases: [], belongsTo: [], relatedTo: [], status: null, owner: null, cadence: null, modifiedAt: now, createdAt: now, fileSize: 0, - snippet: '', relationships: {}, + snippet: '', relationships: {}, icon: null, color: null, } const content = `---\nIs A: Type\n---\n\n# ${typeName}\n\n` diff --git a/src/mock-tauri.ts b/src/mock-tauri.ts index e6f07b44..1ee86e49 100644 --- a/src/mock-tauri.ts +++ b/src/mock-tauri.ts @@ -552,6 +552,8 @@ A **general-purpose document** — research notes, meeting notes, strategy docs. // --- Custom type documents (user-created types) --- '/Users/luca/Laputa/type/recipe.md': `--- Is A: Type +icon: cooking-pot +color: orange --- # Recipe @@ -565,6 +567,8 @@ A **recipe** for cooking or baking. Recipes have ingredients, steps, and serving `, '/Users/luca/Laputa/type/book.md': `--- Is A: Type +icon: book-open +color: green --- # Book @@ -631,6 +635,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Related to': ['[[topic/software-development]]'], 'Type': ['[[type/project]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/responsibility/grow-newsletter.md', @@ -657,6 +663,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Related to': ['[[topic/growth]]'], 'Type': ['[[type/responsibility]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/responsibility/manage-sponsorships.md', @@ -677,6 +685,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Owner': ['[[person/matteo-cellini|Matteo Cellini]]'], 'Type': ['[[type/responsibility]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/procedure/write-weekly-essays.md', @@ -697,6 +707,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/procedure]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/procedure/run-sponsorships.md', @@ -717,6 +729,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Belongs to': ['[[responsibility/manage-sponsorships]]'], 'Type': ['[[type/procedure]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/experiment/stock-screener.md', @@ -738,6 +752,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Has Data': ['[[data/ema200-backtest-results]]'], 'Type': ['[[type/experiment]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/note/facebook-ads-strategy.md', @@ -759,6 +775,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Related to': ['[[topic/growth]]', '[[topic/ads]]'], 'Type': ['[[type/note]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/note/budget-allocation.md', @@ -779,6 +797,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Belongs to': ['[[project/26q1-laputa-app]]'], 'Type': ['[[type/note]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/person/matteo-cellini.md', @@ -798,6 +818,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ relationships: { 'Type': ['[[type/person]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/event/2026-02-14-laputa-app-kickoff.md', @@ -818,6 +840,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Related to': ['[[project/26q1-laputa-app]]', '[[person/matteo-cellini]]'], 'Type': ['[[type/event]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/topic/software-development.md', @@ -838,6 +862,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Notes': ['[[note/facebook-ads-strategy]]', '[[note/budget-allocation]]'], 'Type': ['[[type/topic]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/topic/trading.md', @@ -858,6 +884,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Notes': ['[[experiment/stock-screener]]'], 'Type': ['[[type/topic]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/essay/on-writing-well.md', @@ -878,6 +906,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/essay]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/essay/engineering-leadership-101.md', @@ -899,6 +929,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Related to': ['[[topic/software-development]]'], 'Type': ['[[type/essay]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/essay/ai-agents-primer.md', @@ -919,6 +951,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ 'Belongs to': ['[[responsibility/grow-newsletter]]'], 'Type': ['[[type/essay]]'], }, + icon: null, + color: null, }, // --- Type documents --- { @@ -937,6 +971,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 320, snippet: 'A time-bound initiative that advances a Responsibility. Projects have a clear start, end, and deliverables.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/responsibility.md', @@ -954,6 +990,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 280, snippet: 'An ongoing area of ownership — something you\'re accountable for indefinitely.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/procedure.md', @@ -971,6 +1009,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 310, snippet: 'A recurring process tied to a Responsibility. Procedures have a cadence and describe how to do something.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/experiment.md', @@ -988,6 +1028,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 290, snippet: 'A hypothesis-driven investigation with a clear test and measurable outcome.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/person.md', @@ -1005,6 +1047,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 200, snippet: 'A person you interact with — team members, collaborators, contacts.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/event.md', @@ -1022,6 +1066,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 180, snippet: 'A point-in-time occurrence — meetings, launches, milestones.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/topic.md', @@ -1039,6 +1085,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 170, snippet: 'A subject area for categorization. Topics group related notes, projects, and resources by theme.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/essay.md', @@ -1056,6 +1104,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 200, snippet: 'A published piece of writing — newsletter essays, blog posts, articles.', relationships: {}, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/type/note.md', @@ -1073,6 +1123,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 190, snippet: 'A general-purpose document — research notes, meeting notes, strategy docs.', relationships: {}, + icon: null, + color: null, }, // --- Custom type documents (user-created types) --- { @@ -1091,6 +1143,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 250, snippet: 'A recipe for cooking or baking. Recipes have ingredients, steps, and serving info.', relationships: {}, + icon: 'cooking-pot', + color: 'orange', }, { path: '/Users/luca/Laputa/type/book.md', @@ -1108,6 +1162,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ fileSize: 220, snippet: 'A book you\'re reading or have read. Track reading progress, notes, and key takeaways.', relationships: {}, + icon: 'book-open', + color: 'green', }, // --- Instances of custom types --- { @@ -1128,6 +1184,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ relationships: { 'Type': ['[[type/recipe]]'], }, + icon: null, + color: null, }, { path: '/Users/luca/Laputa/book/designing-data-intensive-applications.md', @@ -1147,6 +1205,8 @@ const MOCK_ENTRIES: VaultEntry[] = [ relationships: { 'Type': ['[[type/book]]'], }, + icon: null, + color: null, }, ] diff --git a/src/types.ts b/src/types.ts index 0f15d9eb..2a973fb0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,6 +15,10 @@ export interface VaultEntry { snippet: string /** Generic relationship fields: any frontmatter key whose value contains wikilinks. */ relationships: Record + /** Phosphor icon name (kebab-case) for Type entries, e.g. "cooking-pot" */ + icon: string | null + /** Accent color key for Type entries: "red" | "purple" | "blue" | "green" | "yellow" | "orange" */ + color: string | null } export interface GitCommit {