Skip to content
Documentation GitHub
Workspace

Container Rules & Auto-Type Assignment

Container Rules & Auto-Type Assignment

Covers container rule lifecycle: setting rules on folder pages to auto-assign types to child pages (with direct_children or recursive depth), listing rules for a folder, removing rules, and verifying that the auto-assignment mechanism fires correctly when pages are created inside a controlled folder. This spec is P1 because a broken container rule silently produces pages without expected type assignments, corrupting collection view queries and structured data workflows that depend on those types.

Container rules live in the ContainerRule domain entity and are managed via three Tauri commands: set_container_rule, remove_container_rule, and get_container_rules. The SetContainerRuleUseCase enforces that only Folder-type pages can carry rules and that the referenced type must exist. Auto-assignment during page creation is handled by AssignmentScope::Container in the type assignment layer — the type is applied by the write-side pipeline when the new page’s parent has an applicable rule.

Preconditions

  • HTTP bridge running on port 9990
  • A workspace initialized via initialize_workspace before each scenario
  • Bridge shim injected via playwright.config.ts
  • The HTTP bridge exposes container rule routes: set_container_rule, remove_container_rule, and get_container_rules. All scenarios in this spec are exercisable via the bridge.

Scenarios

Seed: seed.spec.ts

1. Set a container rule on a folder with direct-children depth

A folder can be configured so that pages created directly inside it automatically receive a specified type.

Steps:

  1. Call create_type with name = "Creature". Capture type_id.
  2. Call create_page with title = "Bestiary", page_type = "folder". Capture folder_id.
  3. Call set_container_rule with folder_page_id = folder_id, type_id, depth = "direct_children".
  4. Capture the returned ContainerRule.

Expected: The returned ContainerRule has a non-nil id, folder_page_id equal to folder_id, type_id matching the Creature type, and depth = "direct_children". created_at is a valid ISO-8601 timestamp.

2. List container rules for a folder

get_container_rules returns all rules attached to a specific folder page.

Steps:

  1. Call create_type with name = "Spell". Capture spell_type_id.
  2. Call create_type with name = "Component". Capture component_type_id.
  3. Call create_page with title = "Spell Library", page_type = "folder". Capture folder_id.
  4. Call set_container_rule with folder_page_id = folder_id, type_id = spell_type_id, depth = "direct_children".
  5. Call set_container_rule with folder_page_id = folder_id, type_id = component_type_id, depth = "recursive".
  6. Call get_container_rules with folder_page_id = folder_id.

Expected: The returned array contains exactly 2 rules, one for each type. One rule has depth = "direct_children" and the other has depth = "recursive". Both reference folder_id as folder_page_id.

3. Remove a container rule by ID

remove_container_rule deletes a specific rule by its UUID without affecting other rules on the same folder.

Steps:

  1. Call create_type with name = "Artifact". Capture type_id.
  2. Call create_page with title = "Vault", page_type = "folder". Capture folder_id.
  3. Call set_container_rule with folder_page_id = folder_id, type_id, depth = "direct_children". Capture rule_id.
  4. Call remove_container_rule with rule_id.
  5. Call get_container_rules with folder_page_id = folder_id.

Expected: remove_container_rule returns no error. get_container_rules returns an empty array — the rule is gone. No other data in the workspace is affected.

4. Pages created inside a container-rule folder receive the auto-assigned type

When a folder has an active container rule, pages created as children automatically receive the configured type with Container scope.

Steps:

  1. Call create_type with name = "Location". Capture type_id.
  2. Call create_page with title = "Regions", page_type = "folder". Capture folder_id.
  3. Call set_container_rule with folder_page_id = folder_id, type_id, depth = "direct_children".
  4. Call create_page with title = "Whispering Hills", parent_id = folder_id.
  5. Call get_page_types with the new page’s page_id.

Expected: get_page_types returns at least one TypeAssignment for the Location type. That assignment has scope = "container" and source_id equal to the container rule id set in step 3.

5. Recursive container rule applies to grandchild pages

A rule with depth = "recursive" propagates the auto-assignment to all descendants, not only direct children.

Steps:

  1. Call create_type with name = "NPC". Capture type_id.
  2. Call create_page with title = "World", page_type = "folder". Capture world_id.
  3. Call set_container_rule with folder_page_id = world_id, type_id, depth = "recursive".
  4. Call create_page with title = "Kingdom", page_type = "folder", parent_id = world_id. Capture kingdom_id.
  5. Call create_page with title = "Castle Guard", parent_id = kingdom_id. Capture npc_page_id.
  6. Call get_page_types with npc_page_id.

Expected: get_page_types for “Castle Guard” includes a TypeAssignment for the NPC type with scope = "container". The rule from the grandparent folder (“World”) applied recursively.

6. Direct-children rule does not apply to grandchild pages

A rule with depth = "direct_children" only auto-assigns types to immediate children, not deeper descendants.

Steps:

  1. Call create_type with name = "Item". Capture type_id.
  2. Call create_page with title = "Inventory", page_type = "folder". Capture folder_id.
  3. Call set_container_rule with folder_page_id = folder_id, type_id, depth = "direct_children".
  4. Call create_page with title = "Section A", page_type = "folder", parent_id = folder_id. Capture sub_folder_id.
  5. Call create_page with title = "Sword", parent_id = sub_folder_id. Capture grandchild_id.
  6. Call get_page_types with grandchild_id.

Expected: get_page_types for “Sword” does not include the Item type. The direct-children rule on “Inventory” did not propagate to “Sword” (a grandchild). The direct child “Section A” would have the type, but “Sword” does not.

7. Setting a container rule on a non-folder page is rejected

Container rules may only be attached to pages with page_type = "folder".

Steps:

  1. Call create_type with name = "Scene". Capture type_id.
  2. Call create_page with title = "Regular Page" (no page_type, defaults to "page"). Capture page_id.
  3. Attempt to call set_container_rule with folder_page_id = page_id, type_id, depth = "direct_children".

Expected: The call returns a validation error containing the phrase “not a folder”. No container rule is created. get_container_rules for page_id returns an empty array.

8. Setting a container rule with a non-existent type is rejected

The type referenced by a container rule must exist in the workspace.

Steps:

  1. Call create_page with title = "Archive", page_type = "folder". Capture folder_id.
  2. Construct a random UUID that does not correspond to any type definition.
  3. Attempt to call set_container_rule with folder_page_id = folder_id, the non-existent type_id, depth = "direct_children".

Expected: The call returns a NotFound error. get_container_rules for folder_id returns an empty array.

9. Container rules persist across workspace sessions

A rule created in one session is still active after re-opening the workspace.

Steps:

  1. Call create_type with name = "Document". Capture type_id.
  2. Call create_page with title = "Documents", page_type = "folder". Capture folder_id.
  3. Call set_container_rule with folder_page_id = folder_id, type_id, depth = "direct_children".
  4. Call close_workspace (or equivalent restart).
  5. Re-open the same workspace.
  6. Call get_container_rules with folder_page_id = folder_id.

Expected: The rule is still present. The returned ContainerRule has the same id, folder_page_id, type_id, and depth as when it was created. Creating a new page inside the folder after the restart still auto-assigns the “Document” type.

10. Get container rules for a folder with no rules returns empty array

A folder with no rules configured returns an empty list, not an error.

Steps:

  1. Call create_page with title = "Unconfigured Folder", page_type = "folder". Capture folder_id.
  2. Call get_container_rules with folder_page_id = folder_id.

Expected: The call succeeds and returns an empty array. No error is thrown.

11. Removing a non-existent rule returns NotFound

Attempting to delete a rule by an ID that does not exist must not silently succeed.

Steps:

  1. Construct a random UUID that does not correspond to any container rule.
  2. Attempt to call remove_container_rule with that rule_id.

Expected: The call returns a NotFound error. No workspace state is modified.

12. Nested folder container rules — parent Recursive, child Direct — both apply correctly

When a parent folder has a recursive rule and a child folder has its own direct-children rule, both rules apply to the child folder’s immediate children.

Steps:

  1. Call create_type with name = "Entity". Capture entity_type_id.
  2. Call create_type with name = "Specific Entity". Capture specific_type_id.
  3. Call create_page with title = "Root Folder", page_type = "folder". Capture root_id.
  4. Call set_container_rule with folder_page_id = root_id, type_id = entity_type_id, depth = "recursive".
  5. Call create_page with title = "Sub Folder", page_type = "folder", parent_id = root_id. Capture sub_id.
  6. Call set_container_rule with folder_page_id = sub_id, type_id = specific_type_id, depth = "direct_children".
  7. Call create_page with title = "Leaf Page", parent_id = sub_id. Capture leaf_id.
  8. Call get_page_types with leaf_id.

Expected: get_page_types returns two type assignments for “Leaf Page”: one for “Entity” (from the recursive rule on the root folder) and one for “Specific Entity” (from the direct-children rule on the sub folder). Both assignments have scope = "container".

Test Data

KeyValueNotes
valid_depth_directdirect_childrenCanonical string for ContainerRuleDepth::DirectChildren
valid_depth_recursiverecursiveCanonical string for ContainerRuleDepth::Recursive
invalid_depthdeepUnknown depth string — triggers validation error from parse_depth
folder_page_typefolderPages must have this page_type to accept container rules
container_scopecontainerTypeAssignment.scope value for auto-assigned rules

Notes

  • set_container_rule requires the page to already be of page_type = "folder". Creating a folder page and immediately setting a rule in the same test sequence is the canonical setup pattern.
  • The depth parameter is passed as a string from the frontend: "direct_children" or "recursive". An invalid depth string (e.g., "deep") returns a validation error from parse_depth before the use case is invoked. The legacy alias "direct" is accepted for backward compatibility but is not the recommended test value.
  • Auto-assignment during page creation is a write-side effect coordinated by the application layer. The get_page_types call made immediately after create_page should reflect the assignment synchronously (no polling required in bridge tests).
  • get_applicable_rules (the underlying repository method) walks the ancestor chain: it applies direct-parent rules at any depth, and recursive rules from any ancestor. Scenarios 5 and 12 verify this traversal.
  • Deleting a type that is referenced by a container rule cascades via FK constraint and removes the rule automatically. No explicit remove_container_rule call is needed after delete_type.
  • The bridge (apps/http-bridge/src/routes/) does not currently expose dedicated container rule routes. These scenarios require the Tauri desktop app or a bridge build that wires set_container_rule, remove_container_rule, and get_container_rules commands. Verify availability before running.

Was this page helpful?