feat: add order property to VaultEntry and install @dnd-kit

- Add `order: number | null` to VaultEntry (TS) and VaultEntry (Rust)
- Add order to Frontmatter struct and SKIP_KEYS in vault.rs
- Update all mock Type documents with order values (0-10)
- Add order: null to all non-Type mock entries and test fixtures
- Install @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-02-21 17:27:19 +01:00
parent 580ce5bcc0
commit 1dfbbb8a9e
10 changed files with 130 additions and 3 deletions

View File

@@ -19,6 +19,9 @@
"@blocknote/core": "^0.46.2",
"@blocknote/mantine": "^0.46.2",
"@blocknote/react": "^0.46.2",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@mantine/core": "^8.3.14",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-dialog": "^1.1.15",

56
pnpm-lock.yaml generated
View File

@@ -20,6 +20,15 @@ importers:
'@blocknote/react':
specifier: ^0.46.2
version: 0.46.2(@floating-ui/dom@1.7.5)(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dnd-kit/core':
specifier: ^6.3.1
version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dnd-kit/sortable':
specifier: ^10.0.0
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@19.2.4)
'@mantine/core':
specifier: ^8.3.14
version: 8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -322,6 +331,28 @@ packages:
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
engines: {node: '>=20.19.0'}
'@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies:
react: '>=16.8.0'
'@dnd-kit/core@6.3.1':
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@dnd-kit/sortable@10.0.0':
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
peerDependencies:
'@dnd-kit/core': ^6.3.0
react: '>=16.8.0'
'@dnd-kit/utilities@3.2.2':
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
peerDependencies:
react: '>=16.8.0'
'@emoji-mart/data@1.2.1':
resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==}
@@ -3811,6 +3842,31 @@ snapshots:
'@csstools/css-tokenizer@4.0.0': {}
'@dnd-kit/accessibility@3.1.1(react@19.2.4)':
dependencies:
react: 19.2.4
tslib: 2.8.1
'@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@dnd-kit/accessibility': 3.1.1(react@19.2.4)
'@dnd-kit/utilities': 3.2.2(react@19.2.4)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
tslib: 2.8.1
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
dependencies:
'@dnd-kit/core': 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dnd-kit/utilities': 3.2.2(react@19.2.4)
react: 19.2.4
tslib: 2.8.1
'@dnd-kit/utilities@3.2.2(react@19.2.4)':
dependencies:
react: 19.2.4
tslib: 2.8.1
'@emoji-mart/data@1.2.1': {}
'@esbuild/aix-ppc64@0.27.3':

View File

@@ -39,6 +39,8 @@ pub struct VaultEntry {
pub icon: Option<String>,
/// Accent color key for Type entries: "red", "purple", "blue", "green", "yellow", "orange".
pub color: Option<String>,
/// Display order for Type entries in sidebar (lower = higher). None = use default order.
pub order: Option<i64>,
}
/// Intermediate struct to capture YAML frontmatter fields.
@@ -68,6 +70,8 @@ struct Frontmatter {
icon: Option<String>,
#[serde(default)]
color: Option<String>,
#[serde(default)]
order: Option<i64>,
}
/// Handles YAML fields that can be either a single string or a list of strings.
@@ -214,7 +218,7 @@ fn parse_frontmatter(data: &HashMap<String, serde_json::Value>) -> Frontmatter {
/// Only skip keys that can never contain wikilinks.
const SKIP_KEYS: &[&str] = &[
"is a", "aliases", "status", "cadence", "archived", "created at", "created time",
"icon", "color",
"icon", "color", "order",
];
/// Check if a string contains a wikilink pattern `[[...]]`.
@@ -367,6 +371,7 @@ pub fn parse_md_file(path: &Path) -> Result<VaultEntry, String> {
modified_at, created_at, file_size,
icon: frontmatter.icon,
color: frontmatter.color,
order: frontmatter.order,
})
}

View File

@@ -60,6 +60,7 @@ const mockEntry: VaultEntry = {
relationships: {},
icon: null,
color: null,
order: null,
}
const mockContent = `---

View File

@@ -22,6 +22,7 @@ const mockEntry: VaultEntry = {
relationships: {},
icon: null,
color: null,
order: null,
}
const mockContent = `---
@@ -61,6 +62,7 @@ const referrerEntry: VaultEntry = {
relationships: {},
icon: null,
color: null,
order: null,
}
const allContent: Record<string, string> = {

View File

@@ -28,6 +28,7 @@ const mockEntries: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/note/facebook-ads-strategy.md',
@@ -51,6 +52,7 @@ const mockEntries: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/person/matteo-cellini.md',
@@ -71,6 +73,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/event/2026-02-14-kickoff.md',
@@ -91,6 +94,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/topic/software-development.md',
@@ -111,6 +115,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
]
@@ -268,6 +273,7 @@ describe('getSortComparator', () => {
relationships: {},
icon: null,
color: null,
order: null,
...overrides,
})
@@ -343,6 +349,7 @@ describe('NoteList sort controls', () => {
relationships: {},
icon: null,
color: null,
order: null,
...overrides,
})

View File

@@ -35,6 +35,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/responsibility/grow-newsletter.md',
@@ -55,6 +56,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/experiment/stock-screener.md',
@@ -75,6 +77,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/procedure/weekly-essays.md',
@@ -95,6 +98,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/topic/software-development.md',
@@ -115,6 +119,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/topic/trading.md',
@@ -135,6 +140,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/person/alice.md',
@@ -155,6 +161,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/event/kickoff.md',
@@ -175,6 +182,7 @@ const mockEntries: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: null,
},
]
@@ -322,6 +330,7 @@ describe('Sidebar', () => {
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/type/book.md',
@@ -342,6 +351,7 @@ describe('Sidebar', () => {
relationships: {},
icon: null,
color: null,
order: null,
},
{
path: '/vault/recipe/pasta.md',
@@ -362,6 +372,7 @@ describe('Sidebar', () => {
relationships: {},
icon: null,
color: null,
order: null,
},
]
@@ -410,6 +421,7 @@ describe('Sidebar', () => {
relationships: {},
icon: null,
color: null,
order: null,
}
render(<Sidebar entries={[...mockEntries, projectTypeEntry]} selection={defaultSelection} onSelect={() => {}} />)
// "Projects" should appear once (the built-in section), not twice

View File

@@ -214,7 +214,7 @@ export function useNoteActions(
aliases: [], belongsTo: [], relatedTo: [],
status: noStatusTypes.has(type) ? null : 'Active',
owner: null, cadence: null, archived: false, modifiedAt: now, createdAt: now, fileSize: 0,
snippet: '', relationships: {}, icon: null, color: null,
snippet: '', relationships: {}, icon: null, color: null, order: null,
}
const frontmatter = [
@@ -242,7 +242,7 @@ export function useNoteActions(
aliases: [], belongsTo: [], relatedTo: [],
status: null, owner: null, cadence: null, archived: false,
modifiedAt: now, createdAt: now, fileSize: 0,
snippet: '', relationships: {}, icon: null, color: null,
snippet: '', relationships: {}, icon: null, color: null, order: null,
}
const content = `---\nIs A: Type\n---\n\n# ${typeName}\n\n`

View File

@@ -449,6 +449,7 @@ AI agents are autonomous systems that can plan, execute, and adapt to achieve go
// --- Type documents ---
'/Users/luca/Laputa/type/project.md': `---
Is A: Type
order: 0
---
# Project
@@ -462,6 +463,7 @@ A **time-bound initiative** that advances a [[type/responsibility|Responsibility
`,
'/Users/luca/Laputa/type/responsibility.md': `---
Is A: Type
order: 1
---
# Responsibility
@@ -474,6 +476,7 @@ An **ongoing area of ownership** — something you're accountable for indefinite
`,
'/Users/luca/Laputa/type/procedure.md': `---
Is A: Type
order: 2
---
# Procedure
@@ -488,6 +491,7 @@ A **recurring process** tied to a [[type/responsibility|Responsibility]]. Proced
`,
'/Users/luca/Laputa/type/experiment.md': `---
Is A: Type
order: 3
---
# Experiment
@@ -500,6 +504,7 @@ A **hypothesis-driven investigation** with a clear test and measurable outcome.
`,
'/Users/luca/Laputa/type/person.md': `---
Is A: Type
order: 4
---
# Person
@@ -511,6 +516,7 @@ A **person** you interact with — team members, collaborators, contacts. People
`,
'/Users/luca/Laputa/type/event.md': `---
Is A: Type
order: 5
---
# Event
@@ -522,6 +528,7 @@ A **point-in-time occurrence** — meetings, launches, milestones. Events are li
`,
'/Users/luca/Laputa/type/topic.md': `---
Is A: Type
order: 6
---
# Topic
@@ -533,6 +540,7 @@ A **subject area** for categorization. Topics group related notes, projects, and
`,
'/Users/luca/Laputa/type/essay.md': `---
Is A: Type
order: 7
---
# Essay
@@ -544,6 +552,7 @@ A **published piece of writing** — newsletter essays, blog posts, articles. Es
`,
'/Users/luca/Laputa/type/note.md': `---
Is A: Type
order: 8
---
# Note
@@ -683,6 +692,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/responsibility/grow-newsletter.md',
@@ -712,6 +722,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/responsibility/manage-sponsorships.md',
@@ -735,6 +746,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/procedure/write-weekly-essays.md',
@@ -758,6 +770,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/procedure/run-sponsorships.md',
@@ -781,6 +794,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/experiment/stock-screener.md',
@@ -805,6 +819,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/note/facebook-ads-strategy.md',
@@ -829,6 +844,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/note/budget-allocation.md',
@@ -852,6 +868,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/person/matteo-cellini.md',
@@ -874,6 +891,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/event/2026-02-14-laputa-app-kickoff.md',
@@ -897,6 +915,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/topic/software-development.md',
@@ -920,6 +939,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/topic/trading.md',
@@ -943,6 +963,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/essay/on-writing-well.md',
@@ -966,6 +987,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/essay/engineering-leadership-101.md',
@@ -990,6 +1012,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/essay/ai-agents-primer.md',
@@ -1013,6 +1036,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
// --- Type documents ---
{
@@ -1034,6 +1058,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 0,
},
{
path: '/Users/luca/Laputa/type/responsibility.md',
@@ -1054,6 +1079,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 1,
},
{
path: '/Users/luca/Laputa/type/procedure.md',
@@ -1074,6 +1100,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 2,
},
{
path: '/Users/luca/Laputa/type/experiment.md',
@@ -1094,6 +1121,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 3,
},
{
path: '/Users/luca/Laputa/type/person.md',
@@ -1114,6 +1142,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 4,
},
{
path: '/Users/luca/Laputa/type/event.md',
@@ -1134,6 +1163,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 5,
},
{
path: '/Users/luca/Laputa/type/topic.md',
@@ -1154,6 +1184,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 6,
},
{
path: '/Users/luca/Laputa/type/essay.md',
@@ -1174,6 +1205,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 7,
},
{
path: '/Users/luca/Laputa/type/note.md',
@@ -1194,6 +1226,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: null,
color: null,
order: 8,
},
// --- Custom type documents (user-created types) ---
{
@@ -1215,6 +1248,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: 'cooking-pot',
color: 'orange',
order: 9,
},
{
path: '/Users/luca/Laputa/type/book.md',
@@ -1235,6 +1269,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
relationships: {},
icon: 'book-open',
color: 'green',
order: 10,
},
// --- Instances of custom types ---
{
@@ -1258,6 +1293,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
{
path: '/Users/luca/Laputa/book/designing-data-intensive-applications.md',
@@ -1280,6 +1316,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
},
icon: null,
color: null,
order: null,
},
// --- Archived entries ---
{
@@ -1296,6 +1333,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
archived: true,
icon: null,
color: null,
order: null,
modifiedAt: Date.now() / 1000 - 86400 * 120,
createdAt: Date.now() / 1000 - 86400 * 200,
fileSize: 680,
@@ -1319,6 +1357,7 @@ const MOCK_ENTRIES: VaultEntry[] = [
archived: true,
icon: null,
color: null,
order: null,
modifiedAt: Date.now() / 1000 - 86400 * 90,
createdAt: Date.now() / 1000 - 86400 * 150,
fileSize: 520,

View File

@@ -20,6 +20,8 @@ export interface VaultEntry {
icon: string | null
/** Accent color key for Type entries: "red" | "purple" | "blue" | "green" | "yellow" | "orange" */
color: string | null
/** Display order for Type entries in sidebar (lower = higher). null = use default order. */
order: number | null
}
export interface GitCommit {