Skip to content

Slash command creates custom entity nodes inline #971

Closed

Description

@malibio

Overview

The `/` slash command lets users create nodes inline (task, text, header, etc.). User-defined custom schema types (e.g. "Customer", "Invoice") are not yet available in the slash command picker. This issue adds them.

Custom schemas are created via the MCP handler and live in the database as nodes with `node_type = 'schema'` and `isCore = false`. They may optionally define a `title_template` (e.g. `"{first_name} {last_name}"`) that computes the node's display title from its properties — implemented in #824.

What needs to be built

Part 1 — Backend: `title_template` field name validation

File: `packages/core/src/behaviors/mod.rs`
Function: `SchemaNodeBehavior::validate_schema_node()`

If `title_template` is set on a schema, every `{field_name}` token in the template must match a field name defined in `schema.fields`. If any token does not match a defined field, reject the schema with a clear error message like:

`title_template references undefined field 'first_name' — add it to schema.fields`

File: `packages/core/src/mcp/handlers/schema.rs`
Functions: `handle_create_schema()` and `handle_update_schema()`

These functions call `NodeService` to create/update schema nodes. The validation above fires automatically through the existing behavior validation pipeline. Make sure any `ValidationFailed` error is returned to the MCP caller as a proper error response (not swallowed). Check how other validation errors are currently handled in this file and follow the same pattern.


Part 2 — Frontend: Add custom types to the slash command picker

Background — how the slash command works today:

The slash command picker is driven by a plugin registry. The service that assembles the list is:

  • `packages/desktop-app/src/lib/services/slash-command-service.ts` — `getCommands()` returns all registered commands from `pluginRegistry.getAllSlashCommands()`

Custom schema types are not registered as plugins, so they don't appear. The simplest approach is to extend `getCommands()` to append custom schema types from the schemas store after the plugin-registered commands.

The list of custom schemas is available from:

  • `packages/desktop-app/src/lib/stores/schemas.ts` — `customSchemas` derived store (already loaded, reactive)

Each custom schema needs to become a `SlashCommand` entry (see the `SlashCommand` interface in `slash-command-service.ts`):

  • `id`: schema id (e.g. `"customer"`)
  • `label`: schema display name (e.g. `"Customer"`)
  • `nodeType`: schema id
  • `icon`: use the generic circle/node icon (same as `text` node fallback)

The slash command dropdown component is at:

  • `packages/desktop-app/src/lib/components/ui/slash-command-dropdown/slash-command-dropdown.svelte`

Part 3 — Frontend: Node creation behavior

When a custom type is selected from the slash command, the node is created as a child of the current node — same as any other slash command today. No special parent handling needed.

Without `title_template`: the new node behaves exactly like a task node. `content` is editable directly inline. The user types the entity name into the block. No special handling needed beyond registering the type.

With `title_template`: the new node is created with `content = "Untitled"`. The inline block must:

  1. Show the raw template string (e.g. `"{first_name} {last_name}"`) as a read-only placeholder when `title` is `"Untitled"` or empty
  2. Show the interpolated `title` value (e.g. `"John Doe"`) once properties are filled
  3. Keep `content` read-only — the user cannot type into this block directly

The `title` field on the node is recomputed automatically by the Rust backend (`compute_title()` in `packages/core/src/services/node_service.rs`) whenever properties are updated. The frontend just needs to display `title ?? content` and react to node updates via the existing `sharedNodeStore`.

How to detect whether a schema has a `title_template`: the `SchemaNode` type (frontend) is at `packages/desktop-app/src/lib/types/schema-node.ts`. Add the `titleTemplate?: string` field if it isn't already there, and read it from the schema when rendering the inline node.


Part 4 — Frontend: "Open" button opens node in other pane

The inline block for custom entity nodes must include the existing "open" button — the same button that appears on task nodes that opens the node in a new tab in the other pane.

Look at `packages/desktop-app/src/lib/design/components/task-node.svelte` for the pattern. This is how the user fills in properties for `title_template` types. There is no auto-open — it's always user-initiated via the open button.


Acceptance Criteria

  • `validate_schema_node()` rejects a schema whose `title_template` references a field name not in `schema.fields`, with a clear error message
  • MCP `create_schema` and `update_schema` return a proper error response when validation fails (not a silent success)
  • Custom schema types (`isCore: false`) appear in the `/` slash command picker
  • Selecting a custom type without `title_template` creates a child node with editable `content`, behaves like a task node
  • Selecting a custom type with `title_template` creates a child node with `content = "Untitled"`
  • Node with `title_template`: shows raw template string as read-only placeholder when title is empty/Untitled
  • Node with `title_template`: shows interpolated title reactively once properties are filled via the side pane
  • Node with `title_template`: `content` field is not editable inline
  • Inline node has an "open" button that opens the node in a new tab in the other pane
  • All tests pass (`bun run test:all`)
  • Code passes `bun run quality:fix`

Technical Specifications

Key Files

Backend (Rust):

  • `packages/core/src/behaviors/mod.rs` — `SchemaNodeBehavior::validate_schema_node()`: add field name cross-validation for `title_template`
  • `packages/core/src/mcp/handlers/schema.rs` — `handle_create_schema()` and `handle_update_schema()`: ensure validation errors surface to MCP caller
  • `packages/core/src/models/schema_node.rs` — `SchemaNode` struct (has `title_template: Option`)
  • `packages/core/src/services/node_service.rs` — `compute_title()`: already handles template interpolation, no changes needed

Frontend (Svelte/TypeScript):

  • `packages/desktop-app/src/lib/services/slash-command-service.ts` — extend `getCommands()` to include custom schemas
  • `packages/desktop-app/src/lib/stores/schemas.ts` — `customSchemas` derived store (source of custom type list)
  • `packages/desktop-app/src/lib/types/schema-node.ts` — add `titleTemplate?: string` if missing
  • `packages/desktop-app/src/lib/design/components/task-node.svelte` — reference for "open" button pattern
  • `packages/desktop-app/src/lib/design/components/base-node.svelte` — base node rendering

How `title_template` validation should work (Rust)

The existing syntax validation (unclosed braces, empty placeholders) is already in `validate_schema_node()` from #824. Add a second pass after it:

```rust
// After syntax validation, cross-check field names against schema.fields
if let Some(template) = &schema.title_template {
let defined_fields: HashSet<&str> = schema.fields.iter().map(|f| f.name.as_str()).collect();
// extract {field_name} tokens and check each against defined_fields
// return Err if any token is not in defined_fields
}
```

Non-Goals

  • Custom icons/emojis per schema type (future — circle indicator is the placeholder for now)
  • `@mention` create flow for custom types (separate issue)
  • UI for creating/editing schemas (currently MCP-only, separate issue)

Related Issues

Activity

  1. malibio commented on Mar 17, 2026

    @malibio
    CollaboratorAuthor

    Frontend baseline: 118 test files, 3722 tests passed

  2. self-assigned this
    on Mar 17, 2026
  3. added a commit that references this issue on Mar 17, 2026
    d3e119b
  4. added a commit that references this issue on Jun 22, 2026
    02ef881
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions