Skip to content
Documentation GitHub
Editor

Page Properties — Inline Content & FTS

Page Properties — Inline Content & FTS

Covers the full property lifecycle on a page: inserting property references inline via the editor autocomplete triggers, reading and writing property values, verifying FTS5 indexes property values so they are findable by search, and confirming the context-panel PropertySection reflects the current frontmatter state. This spec is P2 because typed properties and inline {{name:value}} syntax are a power-user differentiator — breakage silently degrades a core workflow without an obvious error.

Properties are stored as frontmatter on a page and exposed through two paths: the inline PropertyRef TipTap node rendered in the editor body, and the PropertySection component in the context panel. The two views must stay consistent. Values written via set_property_value must be immediately visible in get_page_properties. Values embedded as {{name:value}} in block markdown must survive a round-trip through the VFS markdown projection and re-import.

Preconditions

  • HTTP bridge running on port 9990
  • A workspace initialized via initialize_workspace before each scenario
  • Bridge shim injected via playwright.config.ts

Scenarios

Seed: seed.spec.ts

1. Set a text property value via API and retrieve it

The backend set_property_value command stores a frontmatter value and get_page_properties returns it immediately.

Steps:

  1. Create a page titled “Property Owner”.
  2. Call set_property_value with page_id, property_slug: "summary", and value: "A brief description".
  3. Call get_page_properties with the same page_id.

Expected: The response from get_page_properties contains an entry with slug: "summary" and value: "A brief description". The entry is classified as is_from_type: false (freeform) because no type definition was assigned.

2. Set a property and verify it appears in the PropertySection panel

After setting a property value, the context panel (PropertySection) for that page shows the key and value.

Steps:

  1. Create a page titled “Context Panel Test”.
  2. Navigate to the page so the editor and context panel are visible.
  3. Call set_property_value with property_slug: "status" and value: "draft".
  4. Open or reload the context panel (click the info/details icon or switch to the properties tab).

Expected: The PropertySection panel lists “status” with value “draft”. The value is rendered inline alongside the property label. No reload is required for the panel to update if it subscribes to the current page state.

3. Insert a property reference via {{ autocomplete trigger

Typing {{ in the editor opens a property autocomplete menu for inserting an inline PropertyRef node.

Steps:

  1. Create a page titled “Inline Props”.
  2. Click into the editor and place the cursor after the initial empty block.
  3. Type {{.
  4. Observe whether an autocomplete menu or suggestion popover appears.
  5. If a menu appears, select or type a property name such as “summary” and confirm the insertion.

Expected: Typing {{ opens a property suggestion menu. Selecting “summary” inserts an inline {{summary}} or {{summary:value}} node. The node renders as a visible pill or badge inside the editor content.

4. Insert a property reference via /property slash command

The /property slash command is a second trigger for the same inline insertion flow.

Steps:

  1. On a page with at least one block, type /property in the editor.
  2. Observe whether the slash-command menu shows a “property” option.
  3. Select the “property” option from the menu.
  4. When prompted for a property name, enter “author”.
  5. Confirm the insertion.

Expected: A {{author}} or {{author:value}} inline node appears at the cursor position. The editor does not show a duplicate autocomplete popup from the {{ trigger.

5. Property reference renders as inline badge in editor

A saved PropertyRef node displays as a visual pill inside the block content, not as raw {{name}} text.

Steps:

  1. Create a page and set a property value for property_slug: "priority" and value: "high".
  2. Navigate to the page in the editor.
  3. Inspect the rendered block content area.

Expected: The property reference appears as a styled inline chip or badge with the property name visible (e.g., priority or priority: high). The raw {{priority:high}} syntax is not shown as plain text.

6. Property value displays in {{name:value}} format in saved markdown

When a block containing a property reference is saved, the serialized markdown uses the {{name:value}} colon-separated format.

Steps:

  1. Create a page titled “Value Format”.
  2. Insert the text {{status:published}} into a block directly via save_block_content_markdown with content "Status: {{status:published}}".
  3. Call get_page and inspect the block content.

Expected: The stored block content includes {{status:published}} with the colon separator. The status key appears in the page frontmatter with value "published" after the VFS write-back parses the inline syntax.

7. Clear a property value by setting it to null

Calling set_property_value with value: null removes the frontmatter key entirely.

Steps:

  1. Create a page and set property_slug: "summary" with value: "initial".
  2. Verify get_page_properties returns summary: "initial".
  3. Call set_property_value again with value: null (JSON null) for the same slug.
  4. Call get_page_properties again.

Expected: The second call to get_page_properties either does not include a "summary" entry or returns it with value: null. The frontmatter key has been removed from the page’s persisted state.

8. FTS5 indexes property values — page is findable by property content

Property values stored in frontmatter are indexed by FTS5, so searching for the value text returns the page.

Steps:

  1. Create a page titled “Indexed Properties”.
  2. Call set_property_value with property_slug: "protagonist" and value: "Elara Vance".
  3. Call search_pages with query "Elara Vance".

Expected: The search results include “Indexed Properties”. The page is discoverable by its property value without the property name being mentioned in the page title or block content.

9. Multiple properties on a single page are all returned

A page can hold many property keys and all are returned from get_page_properties.

Steps:

  1. Create a page titled “Multi-Prop”.
  2. Call set_property_value three times with different slugs: "genre""fantasy", "wordCount" → set via the API as a number or string, "rating""5".
  3. Call get_page_properties for the page.

Expected: The response contains at least three entries — one for each slug. Each entry has the correct slug and value fields populated. The order may vary.

10. Property value validates against typed property definition

When a property slug matches a registered PropertyDefinition with a specific value_type, setting an incompatible value is rejected.

Steps:

  1. Verify a typed property definition exists for slug "word-count" with value_type: "number" (or create one if the API supports it).
  2. Call set_property_value with property_slug: "word-count" and value: "not-a-number" (a string).

Expected: The call returns a validation error (HTTP 422 or equivalent). The error message indicates the value does not match the expected type. The page’s frontmatter is not updated.

11. Freeform property slug bypasses type validation

A slug with no matching PropertyDefinition is stored as freeform frontmatter with no type check.

Steps:

  1. Create a page titled “Freeform”.
  2. Call set_property_value with property_slug: "completely-custom" and value: {"nested": true} (an object).

Expected: The call succeeds. get_page_properties returns the slug with the stored JSON object as its value and is_from_type: false.

12. Property persists after navigating away and returning

Frontmatter properties survive a navigation round-trip and are not lost on page reload.

Steps:

  1. Create a page titled “Persistent Props” and set property_slug: "chapter" with value: "7".
  2. Navigate to a different page (e.g., create and open another page).
  3. Navigate back to “Persistent Props”.
  4. Call get_page_properties for “Persistent Props”.

Expected: The property chapter: "7" is still present. No data was lost during the navigation.

Test Data

KeyValueNotes
text_prop_slugsummaryFreeform text property slug used across basic scenarios
typed_number_slugword-countSlug for typed Number property (validates value type)
freeform_slugcompletely-customUnknown slug — stored without validation
fts_prop_slugprotagonistProperty whose value is searched via FTS5
fts_prop_valueElara VanceUnique search term expected to return exactly one page
clear_sentinelnullJSON null passed to remove a frontmatter key
multi_prop_count3Minimum number of properties set in scenario 9
inline_md_format{{status:published}}Exact colon-separated format expected in serialized markdown

Notes

  • get_page_properties returns both typed properties (from the page’s TypeAssignment) and freeform frontmatter keys not matched to a definition. The is_from_type boolean distinguishes them.
  • The HTTP bridge’s get_page_properties handler is currently a stub returning [] (as of the current codebase). These scenarios exercise the Tauri command path. When the bridge is updated to wire the full use case, the bridge-based tests can be enabled. Until then, scenarios involving property retrieval must be driven through the Tauri app (not the bridge stub).
  • set_property_value is fully wired in the bridge (/invoke/set_property_value).
  • The "tags" property slug is special-cased at the Tauri framework layer — it routes to the tag repository rather than generic frontmatter storage. Do not use "tags" as a test slug for generic frontmatter validation.
  • VFS write-back (scenario 6) requires the VFS to be mounted. The bridge returns 501 for VFS commands. Scenario 6 covers the markdown format stored in the block, not the full VFS projection round-trip.
  • FTS5 indexing of frontmatter happens via pages.raw_markdown column update on save. There may be a brief delay if background indexing is involved. Retry the search query if the first attempt returns no results within the same test.
  • The dual-trigger pattern ({{ and /property) is mutually exclusive — only one autocomplete popup should be visible at a time. If both fire simultaneously, this is a regression.

Was this page helpful?