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_workspacebefore each scenario - Bridge shim injected via
playwright.config.ts - The HTTP bridge exposes container rule routes:
set_container_rule,remove_container_rule, andget_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:
- Call
create_typewithname = "Creature". Capturetype_id. - Call
create_pagewithtitle = "Bestiary",page_type = "folder". Capturefolder_id. - Call
set_container_rulewithfolder_page_id = folder_id,type_id,depth = "direct_children". - 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:
- Call
create_typewithname = "Spell". Capturespell_type_id. - Call
create_typewithname = "Component". Capturecomponent_type_id. - Call
create_pagewithtitle = "Spell Library",page_type = "folder". Capturefolder_id. - Call
set_container_rulewithfolder_page_id = folder_id,type_id = spell_type_id,depth = "direct_children". - Call
set_container_rulewithfolder_page_id = folder_id,type_id = component_type_id,depth = "recursive". - Call
get_container_ruleswithfolder_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:
- Call
create_typewithname = "Artifact". Capturetype_id. - Call
create_pagewithtitle = "Vault",page_type = "folder". Capturefolder_id. - Call
set_container_rulewithfolder_page_id = folder_id,type_id,depth = "direct_children". Capturerule_id. - Call
remove_container_rulewithrule_id. - Call
get_container_ruleswithfolder_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:
- Call
create_typewithname = "Location". Capturetype_id. - Call
create_pagewithtitle = "Regions",page_type = "folder". Capturefolder_id. - Call
set_container_rulewithfolder_page_id = folder_id,type_id,depth = "direct_children". - Call
create_pagewithtitle = "Whispering Hills",parent_id = folder_id. - Call
get_page_typeswith the new page’spage_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:
- Call
create_typewithname = "NPC". Capturetype_id. - Call
create_pagewithtitle = "World",page_type = "folder". Captureworld_id. - Call
set_container_rulewithfolder_page_id = world_id,type_id,depth = "recursive". - Call
create_pagewithtitle = "Kingdom",page_type = "folder",parent_id = world_id. Capturekingdom_id. - Call
create_pagewithtitle = "Castle Guard",parent_id = kingdom_id. Capturenpc_page_id. - Call
get_page_typeswithnpc_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:
- Call
create_typewithname = "Item". Capturetype_id. - Call
create_pagewithtitle = "Inventory",page_type = "folder". Capturefolder_id. - Call
set_container_rulewithfolder_page_id = folder_id,type_id,depth = "direct_children". - Call
create_pagewithtitle = "Section A",page_type = "folder",parent_id = folder_id. Capturesub_folder_id. - Call
create_pagewithtitle = "Sword",parent_id = sub_folder_id. Capturegrandchild_id. - Call
get_page_typeswithgrandchild_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:
- Call
create_typewithname = "Scene". Capturetype_id. - Call
create_pagewithtitle = "Regular Page"(nopage_type, defaults to"page"). Capturepage_id. - Attempt to call
set_container_rulewithfolder_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:
- Call
create_pagewithtitle = "Archive",page_type = "folder". Capturefolder_id. - Construct a random UUID that does not correspond to any type definition.
- Attempt to call
set_container_rulewithfolder_page_id = folder_id, the non-existenttype_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:
- Call
create_typewithname = "Document". Capturetype_id. - Call
create_pagewithtitle = "Documents",page_type = "folder". Capturefolder_id. - Call
set_container_rulewithfolder_page_id = folder_id,type_id,depth = "direct_children". - Call
close_workspace(or equivalent restart). - Re-open the same workspace.
- Call
get_container_ruleswithfolder_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:
- Call
create_pagewithtitle = "Unconfigured Folder",page_type = "folder". Capturefolder_id. - Call
get_container_ruleswithfolder_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:
- Construct a random UUID that does not correspond to any container rule.
- Attempt to call
remove_container_rulewith thatrule_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:
- Call
create_typewithname = "Entity". Captureentity_type_id. - Call
create_typewithname = "Specific Entity". Capturespecific_type_id. - Call
create_pagewithtitle = "Root Folder",page_type = "folder". Captureroot_id. - Call
set_container_rulewithfolder_page_id = root_id,type_id = entity_type_id,depth = "recursive". - Call
create_pagewithtitle = "Sub Folder",page_type = "folder",parent_id = root_id. Capturesub_id. - Call
set_container_rulewithfolder_page_id = sub_id,type_id = specific_type_id,depth = "direct_children". - Call
create_pagewithtitle = "Leaf Page",parent_id = sub_id. Captureleaf_id. - Call
get_page_typeswithleaf_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
| Key | Value | Notes |
|---|---|---|
| valid_depth_direct | direct_children | Canonical string for ContainerRuleDepth::DirectChildren |
| valid_depth_recursive | recursive | Canonical string for ContainerRuleDepth::Recursive |
| invalid_depth | deep | Unknown depth string — triggers validation error from parse_depth |
| folder_page_type | folder | Pages must have this page_type to accept container rules |
| container_scope | container | TypeAssignment.scope value for auto-assigned rules |
Notes
set_container_rulerequires the page to already be ofpage_type = "folder". Creating a folder page and immediately setting a rule in the same test sequence is the canonical setup pattern.- The
depthparameter is passed as a string from the frontend:"direct_children"or"recursive". An invalid depth string (e.g.,"deep") returns a validation error fromparse_depthbefore 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_typescall made immediately aftercreate_pageshould 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_rulecall is needed afterdelete_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 wiresset_container_rule,remove_container_rule, andget_container_rulescommands. Verify availability before running.
Was this page helpful?
Thanks for your feedback!