Repository navigation
Slash command creates custom entity nodes inline - #972
Conversation
- Fix contentTemplate: '' → 'Untitled' in createPluginFromSchema() so custom
entity nodes start with a name instead of being empty
- Add titleTemplate?: string to SchemaNode frontend interface so computed titles
are accessible in CustomEntityNode
- Update CustomEntityNode to show computed title (or raw template as placeholder)
in read-only view mode when schema has title_template; add "open" button on
hover (same pattern as TaskNode) so entity can be opened in the other pane
- Use sharedNodeStore + structureTree in CustomEntityNode for cross-pane
reactivity (matches TaskNode pattern)
- Backend: add cross-validation in validate_schema_node() — every {token} in
title_template must reference a field defined in schema.fields; undefined
tokens return ValidationFailed with a descriptive error
- Update existing test_title_template_valid to include fields in schema
- Add tests: test_title_template_undefined_field_rejected,
test_title_template_defined_field_accepted (behaviors/mod.rs)
- Update create_custom_schema test helper to auto-generate fields from template
tokens so existing node_service title_template tests continue passing
- Update contentTemplate assertion in schema-plugin-loader.test.ts; add
dedicated test for contentTemplate: 'Untitled'
- Narrow builtInSchemas in schemas.ts to SIDENAV_CORE_TYPES (only 'task')
so structural node types don't clutter the sidenav
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Code Review SummaryReview Type: Initial Review This PR delivers a well-scoped, coherent set of changes across backend validation, frontend node rendering, and the plugin/store layers. The implementation follows established patterns (TaskNode open button, sharedNodeStore reactivity) and the commit message accurately describes the change surface. The change is a definite net improvement. Two issues require attention before merge: a backend gap where `handle_create_schema` and `handle_update_schema` never pass `title_template` through to the node properties (meaning the validation added in `behaviors/mod.rs` can never actually trigger from the MCP path), and a template deduplication problem in `create_custom_schema` that silently allows duplicate field entries for repeated tokens. Requirements Check
FindingsCritical Issues[Critical/Blocker] `packages/core/src/mcp/handlers/schema.rs` — `title_template` never reaches the stored node, defeating its own validation `handle_create_schema` builds the properties JSON at line ~165 without including `titleTemplate`: ```rust `CreateSchemaParams` also does not declare a `title_template` / `title_template` field, so even if an MCP caller sends one it is silently dropped. The same problem exists in `handle_update_schema` around line ~508, which rebuilds properties without forwarding `title_template`. The acceptance criterion says "MCP create_schema and update_schema return a proper error response when validation fails". The new cross-validation in `validate_schema_node()` is correct, but it can only fire when a schema node is constructed with the field present. The MCP path never puts it there, so the validation is unreachable from MCP. The fix is to add `title_template: Option` to `CreateSchemaParams` (and the update params struct) and include it in the `properties` JSON when present. [Critical/Blocker] `packages/core/src/services/node_service.rs` — `create_custom_schema` helper produces duplicate fields for repeated tokens The new token-extraction loop in the test helper (lines ~10597–10613) does not deduplicate field names. A template like `"{first_name} {first_name}"` produces two `first_name` fields in the `fields` array. This will cause the schema to be rejected by any future uniqueness validation and produces misleading test setups today. The fix is a simple contains-check before pushing: ```rust While this is a test helper rather than production code, it is shared across multiple tests and incorrect shared helpers are a common source of silent false-positives. Suggested Improvements[Improvement] `packages/desktop-app/src/lib/design/components/custom-entity-node.svelte` — `BaseNode` event forwarding is duplicated verbatim The two `` branches in the `{#if hasTitleTemplate}` block are identical except for the `customViewContent` prop. This violates DRY and will require maintaining both copies in sync every time new events are added to BaseNode. A single `` with a conditional `customViewContent` prop would reduce the template by ~25 lines and eliminate the maintenance risk: ```svelte [Improvement] `packages/desktop-app/src/lib/design/components/custom-entity-node.svelte` — `typingTimer` not cleared on component destroy The `typingTimer` timeout is set via `handleTypingStart()` but never cleared when the component unmounts. In Svelte 5 the correct pattern is `$effect(() => () => { if (typingTimer) clearTimeout(typingTimer); })` to register an on-destroy cleanup. Without it, if the node is removed from the DOM while the timer is pending, the callback fires against a stale `isTyping` reference. This is a minor leak but matches the same pattern TaskNode should follow. [Improvement] `packages/core/src/behaviors/mod.rs` — cross-validation is order-dependent on the existing syntax pass The `defined_fields` HashSet is built once before the byte-scanning loop, then the field-name lookup is interleaved inside the loop. This is correct but subtly brittle: the cross-validation only reaches the `defined_fields.contains()` check after the syntax checks pass for that token. If a future developer reorders the early-return conditions inside the loop, they could accidentally skip the cross-check. Consider extracting a small `validate_field_names(template, defined_fields)` helper after the syntax loop completes — separating syntax validation from semantic validation makes both easier to test and modify independently. Nitpicks
Recommendation: REQUEST CHANGES The two critical issues need resolution before merge. The backend gap (MCP never passing `title_template`) means the stated acceptance criterion about MCP returning validation errors is not met, and the deduplication gap in the test helper produces unreliable test scaffolding. The frontend improvements are non-blocking but the duplicated BaseNode block is worth fixing in the same pass since it is cheaply addressed. |
🔴 Critical: Add title_template to MCP create_schema / update_schema
- Add title_template: Option<String> to CreateSchemaParams and UpdateSchemaParams
- Include titleTemplate in the properties JSON sent to the node store so it is
actually persisted when called via MCP
- Explicit SchemaNodeBehavior::validate_schema_node() call in handle_update_schema
before saving (update_node_unchecked bypasses the behavior pipeline; this ensures
cross-validation fires on update just as it does on create)
- Document title_template in the MCP tool input schemas (create_schema, update_schema)
🔴 Critical: Deduplicate field names in create_custom_schema test helper
- Use HashSet to skip duplicate token names so repeated {token}s in a template
do not produce multiple fields with the same name
🟡 Important: Collapse duplicate BaseNode blocks in CustomEntityNode
- Replace {#if hasTitleTemplate}/{:else} with a single <BaseNode> that passes
customViewContent conditionally; eliminates ~20 lines of duplicated event wiring
🟡 Important: Clear typingTimer on destroy in CustomEntityNode
- Add onDestroy(() => clearTimeout(typingTimer)) to prevent stale callbacks
after the component is unmounted
🟢 Suggestion: Replace showEntityHeader = $derived(true) with plain const
- Derived with constant value adds reactive overhead for no benefit
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Review AddressedAll findings from the code review have been fixed. ✅ 🔴 Critical: title_template unreachable via MCPAdded ✅ 🔴 Critical: Duplicate field names in test helperAdded a ✅ 🟡 Important: Duplicate BaseNode blocksCollapsed the ✅ 🟡 Important: typingTimer not cleared on destroyAdded ✅ 🟢 showEntityHeader = $derived(true)Replaced with a plain ⏭️ Skipped: SIDENAV_CORE_TYPES extraction — out of scope, fine as-is for now |
Show syntax + 3 concrete examples (customer, invoice, contact) in both
create_schema and update_schema tool schemas so AI agents understand
the {field_name} token format without having to guess.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Covers all title_template edge cases end-to-end through handle_create_schema and handle_update_schema against a real NodeService/SurrealStore: - create with valid template → succeeds - create with template referencing undefined field → rejected - create without template → succeeds - update: add valid template to existing schema → succeeds - update: add template referencing undefined field → rejected - update: remove field still referenced by existing template → rejected - update: remove unrelated field while template exists → succeeds - update: clear template (empty string) then remove field → succeeds Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Code Review Summary — Re-ReviewReview Type: Re-Review (addressing findings from review-20260317-213612) Three commits landed since the last review tag:
Both Critical/Blocker issues are resolved. Both Important improvements are resolved. All three commits are well-scoped and cohesive. The test suite added in the third commit is particularly thorough. The change is approved. Verification of Previous Findings[Critical/Blocker — RESOLVED]
[Critical/Blocker — RESOLVED] Duplicate field names in The [Improvement — RESOLVED] Duplicate BaseNode blocks The [Improvement — RESOLVED]
[Nit — RESOLVED] Replaced with a plain [Nit — ACKNOWLEDGED / SKIPPED] The author explicitly noted this as out of scope. Acceptable for now. New Issues in the Three CommitsSuggested Improvements[Improvement] The merge strategy [Improvement] In #[cfg(test)]
#[path = "schema_test.rs"]
mod schema_test;
#[cfg(test)]
mod tests {
...
}The Nitpicks
Final Recommendation: APPROVEBoth blockers are resolved correctly. The improvement commits (doc enrichment, integration test coverage) go beyond what was asked and meaningfully strengthen the feature. The one new Improvement finding (undocumentable title_template clear) is low-severity and easily addressed with a one-line description update — it does not block merge. |
…onomics - Document that passing empty string \"\" clears an existing title_template in the update_schema MCP tool description (title_template is irremovable without this hint since omitting the field preserves the existing value) - Replace .unwrap() with .expect(context) throughout schema_test.rs so test failures point to the cause rather than a bare line number - Add comment explaining why create_test_service uses `let mut store = Arc::new(...)` (NodeService::new requires &mut Arc<SurrealStore>) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* feat: slash command creates custom entity nodes inline (closes #971) - Fix contentTemplate: '' → 'Untitled' in createPluginFromSchema() so custom entity nodes start with a name instead of being empty - Add titleTemplate?: string to SchemaNode frontend interface so computed titles are accessible in CustomEntityNode - Update CustomEntityNode to show computed title (or raw template as placeholder) in read-only view mode when schema has title_template; add "open" button on hover (same pattern as TaskNode) so entity can be opened in the other pane - Use sharedNodeStore + structureTree in CustomEntityNode for cross-pane reactivity (matches TaskNode pattern) - Backend: add cross-validation in validate_schema_node() — every {token} in title_template must reference a field defined in schema.fields; undefined tokens return ValidationFailed with a descriptive error - Update existing test_title_template_valid to include fields in schema - Add tests: test_title_template_undefined_field_rejected, test_title_template_defined_field_accepted (behaviors/mod.rs) - Update create_custom_schema test helper to auto-generate fields from template tokens so existing node_service title_template tests continue passing - Update contentTemplate assertion in schema-plugin-loader.test.ts; add dedicated test for contentTemplate: 'Untitled' - Narrow builtInSchemas in schemas.ts to SIDENAV_CORE_TYPES (only 'task') so structural node types don't clutter the sidenav Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Address review: MCP title_template support, dedup fields, cleanup 🔴 Critical: Add title_template to MCP create_schema / update_schema - Add title_template: Option<String> to CreateSchemaParams and UpdateSchemaParams - Include titleTemplate in the properties JSON sent to the node store so it is actually persisted when called via MCP - Explicit SchemaNodeBehavior::validate_schema_node() call in handle_update_schema before saving (update_node_unchecked bypasses the behavior pipeline; this ensures cross-validation fires on update just as it does on create) - Document title_template in the MCP tool input schemas (create_schema, update_schema) 🔴 Critical: Deduplicate field names in create_custom_schema test helper - Use HashSet to skip duplicate token names so repeated {token}s in a template do not produce multiple fields with the same name 🟡 Important: Collapse duplicate BaseNode blocks in CustomEntityNode - Replace {#if hasTitleTemplate}/{:else} with a single <BaseNode> that passes customViewContent conditionally; eliminates ~20 lines of duplicated event wiring 🟡 Important: Clear typingTimer on destroy in CustomEntityNode - Add onDestroy(() => clearTimeout(typingTimer)) to prevent stale callbacks after the component is unmounted 🟢 Suggestion: Replace showEntityHeader = $derived(true) with plain const - Derived with constant value adds reactive overhead for no benefit Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * docs(mcp): enrich title_template descriptions with multiple examples Show syntax + 3 concrete examples (customer, invoice, contact) in both create_schema and update_schema tool schemas so AI agents understand the {field_name} token format without having to guess. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(mcp): add integration tests for title_template handler validation Covers all title_template edge cases end-to-end through handle_create_schema and handle_update_schema against a real NodeService/SurrealStore: - create with valid template → succeeds - create with template referencing undefined field → rejected - create without template → succeeds - update: add valid template to existing schema → succeeds - update: add template referencing undefined field → rejected - update: remove field still referenced by existing template → rejected - update: remove unrelated field while template exists → succeeds - update: clear template (empty string) then remove field → succeeds Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Address re-review: document title_template clearing, improve test ergonomics - Document that passing empty string \"\" clears an existing title_template in the update_schema MCP tool description (title_template is irremovable without this hint since omitting the field preserves the existing value) - Replace .unwrap() with .expect(context) throughout schema_test.rs so test failures point to the cause rather than a bare line number - Add comment explaining why create_test_service uses `let mut store = Arc::new(...)` (NodeService::new requires &mut Arc<SurrealStore>) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Closes #971