# Tagging System > ArchiTools internal architecture reference -- cross-module tagging, tag model, and Tag Manager module. --- ## Overview ArchiTools uses a unified tagging system across all modules. Tags provide categorization, filtering, cross-referencing, and visual identification for entities throughout the platform. The tagging system is inspired by the existing ManicTime tag structure used by the office group and extends it into a structured, typed model. The Tag Manager module provides a dedicated CRUD interface for managing tags. All other modules consume tags through a shared `TagService` and a set of reusable UI components. --- ## Tag Model ```typescript // src/types/tags.ts interface Tag { id: string; // unique identifier (UUID v4) label: string; // display text (Romanian), e.g. "Verificare proiect" category: TagCategory; // semantic grouping color?: string; // hex color for visual distinction, e.g. "#2563eb" icon?: string; // optional Lucide icon name, e.g. "building" scope: TagScope; // visibility scope moduleId?: string; // if scope is 'module', which module owns this tag companyId?: CompanyId; // if scope is 'company', which company owns this tag parentId?: string; // for hierarchical tags (references another Tag's id) metadata?: Record; // extensible key-value pairs createdAt: string; // ISO 8601 updatedAt: string; // ISO 8601 } ``` ### Tag Categories ```typescript type TagCategory = | 'project' // project identifiers (e.g., "076 Casa Copernicus") | 'phase' // project phases (CU, DTAC, PT, etc.) | 'activity' // work activities (Releveu, Design interior, etc.) | 'document-type' // document classification (Regulament, Parte desenata, etc.) | 'company' // company association (Beletage, Urban Switch, Studii de Teren) | 'priority' // priority levels (Urgent, Normal, Scazut) | 'status' // status indicators (In lucru, Finalizat, In asteptare) | 'custom'; // user-defined tags that don't fit other categories ``` Each category serves a distinct semantic purpose. Tags within the same category are mutually comparable -- you can filter all `phase` tags to get a list of project phases, or all `activity` tags to see work activities. Categories are fixed in code; adding a new category requires a code change. The `custom` category is the escape hatch for tags that do not fit the predefined categories. ### Tag Scope ```typescript type TagScope = 'global' | 'module' | 'company'; ``` | Scope | Meaning | Example | |---|---|---| | `global` | Available to all modules across all companies | Phase tags like "DTAC", "PT" | | `module` | Scoped to a single module, not visible elsewhere | "Template favorit" in Prompt Generator | | `company` | Scoped to a single company | "Ofertare" scoped to Beletage | When `scope` is `module`, the `moduleId` field must be set. When `scope` is `company`, the `companyId` field must be set. When `scope` is `global`, both are `undefined`. --- ## Hierarchical Tags Tags support parent-child relationships through the `parentId` field. This enables structured navigation and drill-down filtering. ### Hierarchy Convention ``` Project (category: 'project') └── Phase (category: 'phase', parentId: project tag id) └── Task (category: 'activity', parentId: phase tag id) ``` **Example:** ``` 076 Casa Copernicus (project) ├── CU (phase) │ ├── Redactare (activity) │ └── Depunere (activity) ├── DTAC (phase) │ ├── Redactare (activity) │ ├── Verificare proiect (activity) │ └── Vizita santier (activity) └── PT (phase) └── Detalii de Executie (activity) ``` ### Hierarchy Rules - A tag's `parentId` must reference an existing tag. - Deleting a parent tag does not cascade-delete children. Children become root-level tags (their `parentId` is cleared). - Maximum nesting depth: 3 levels. This is enforced in the `TagService` on create/update. - A tag's category is independent of its parent's category. A `phase` tag can be a child of a `project` tag. ### Querying the Hierarchy ```typescript // Get all children of a tag function getChildren(tags: Tag[], parentId: string): Tag[] { return tags.filter((tag) => tag.parentId === parentId); } // Get the full ancestry chain (bottom-up) function getAncestors(tags: Tag[], tagId: string): Tag[] { const tagMap = new Map(tags.map((t) => [t.id, t])); const ancestors: Tag[] = []; let current = tagMap.get(tagId); while (current?.parentId) { const parent = tagMap.get(current.parentId); if (parent) ancestors.push(parent); current = parent; } return ancestors.reverse(); // root-first order } // Build a tree structure from flat tag list function buildTagTree(tags: Tag[]): TagTreeNode[] { const map = new Map(); const roots: TagTreeNode[] = []; for (const tag of tags) { map.set(tag.id, { tag, children: [] }); } for (const tag of tags) { const node = map.get(tag.id)!; if (tag.parentId && map.has(tag.parentId)) { map.get(tag.parentId)!.children.push(node); } else { roots.push(node); } } return roots; } interface TagTreeNode { tag: Tag; children: TagTreeNode[]; } ``` --- ## TagService The `TagService` is the central data access layer for tags. All modules interact with tags exclusively through this service. ```typescript // src/lib/tags/tag-service.ts interface TagService { /** Get all tags, optionally filtered by scope */ getAllTags(): Promise; /** Get tags belonging to a specific category */ getTagsByCategory(category: TagCategory): Promise; /** Get tags by scope, with optional scope identifier */ getTagsByScope(scope: TagScope, scopeId?: string): Promise; /** Get a single tag by ID */ getTag(id: string): Promise; /** Get all children of a parent tag */ getChildTags(parentId: string): Promise; /** Create a new tag. Validates uniqueness of label within category+scope. */ createTag(tag: Omit): Promise; /** Update an existing tag. Partial updates supported. */ updateTag(id: string, updates: Partial>): Promise; /** Delete a tag. Clears parentId on children. */ deleteTag(id: string): Promise; /** Full-text search on tag labels */ searchTags(query: string): Promise; /** Bulk import tags (used by Tag Manager for import/export) */ importTags(tags: Omit[]): Promise; /** Export all tags as a serializable array */ exportTags(): Promise; } ``` ### Storage Tags are stored under the `architools.tags` namespace in the storage abstraction layer. The storage key layout: ``` architools.tags.all -> Tag[] (master list) architools.tags.index -> Record (category -> tag ID index) ``` The index is a denormalized lookup table rebuilt on every write operation. It allows `getTagsByCategory` to resolve without scanning the full tag list. ### Validation Rules | Rule | Enforcement | |---|---| | Label must be non-empty and <= 100 characters | `createTag`, `updateTag` | | Label must be unique within the same `category` + `scope` + `scopeId` | `createTag`, `updateTag` | | `moduleId` required when `scope` is `module` | `createTag`, `updateTag` | | `companyId` required when `scope` is `company` | `createTag`, `updateTag` | | `parentId` must reference an existing tag | `createTag`, `updateTag` | | Max nesting depth: 3 | `createTag`, `updateTag` | | `color` must be a valid 6-digit hex (`#rrggbb`) | `createTag`, `updateTag` | | `icon` must be a valid Lucide icon name | `createTag`, `updateTag` | --- ## Tag UI Components Four reusable components provide the tag interface across all modules. ### `TagBadge` Displays a single tag as a colored chip. ```typescript // src/components/shared/tags/TagBadge.tsx interface TagBadgeProps { tag: Tag; size?: 'sm' | 'md'; // default: 'sm' removable?: boolean; // shows X button onRemove?: (tagId: string) => void; } ``` Renders a rounded badge with the tag's `color` as background (with opacity for readability), the `icon` if present, and the `label` as text. The `sm` size is used inline in tables and lists; `md` is used in detail views and forms. ### `TagSelector` Multi-select tag picker with category filtering, search, and tag creation. ```typescript // src/components/shared/tags/TagSelector.tsx interface TagSelectorProps { selectedTagIds: string[]; onChange: (tagIds: string[]) => void; categories?: TagCategory[]; // restrict to specific categories scope?: TagScope; // restrict to specific scope scopeId?: string; // module or company ID for scope filtering allowCreate?: boolean; // allow inline tag creation (default: false) placeholder?: string; maxTags?: number; // max selectable tags (default: unlimited) } ``` **Behavior:** - Opens a popover with a search input and categorized tag list. - Tags are grouped by category with category headers. - Search filters tags by label (case-insensitive, diacritics-insensitive). - Selected tags appear as `TagBadge` components above the input. - When `allowCreate` is true, typing a label that does not match any existing tag shows a "Creeaza tag: [label]" option. ### `TagFilter` Filter bar for lists and tables. Allows users to select tags and see only entities matching those tags. ```typescript // src/components/shared/tags/TagFilter.tsx interface TagFilterProps { activeTags: string[]; // currently active filter tag IDs onChange: (tagIds: string[]) => void; categories?: TagCategory[]; // which categories to show filter chips for tagCounts?: Record; // tag ID -> count of matching entities mode?: 'and' | 'or'; // filter logic (default: 'or') } ``` **Behavior:** - Displays as a horizontal chip bar above the list/table. - Each active tag is shown as a `TagBadge` with a remove button. - A "+" button opens the `TagSelector` to add more filter tags. - When `tagCounts` is provided, each tag shows its count in parentheses. - `mode: 'and'` requires entities to match all selected tags; `mode: 'or'` matches any. ### `TagManager` This is the root component of the **Tag Manager module** -- a dedicated interface for full CRUD operations on the global tag catalog. **Capabilities:** - Browse all tags in a searchable, filterable table. - Create, edit, and delete tags. - Bulk operations: delete multiple tags, change category, change scope. - Import tags from JSON or CSV. - Export all tags to JSON. - Visual hierarchy browser: tree view of parent-child relationships. - Color picker and icon selector for tag customization. The Tag Manager module config: ```typescript { id: 'tag-manager', name: 'Manager Etichete', description: 'Gestionare centralizata a etichetelor folosite in toate modulele.', icon: 'tags', route: '/tag-manager', category: 'management', featureFlag: 'module.tag-manager', visibility: 'internal', version: '1.0.0', storageNamespace: 'architools.tag-manager', navOrder: 40, tags: ['tags', 'management', 'configuration'], } ``` --- ## Pre-Seeded Tags The following tags are seeded on first initialization, derived from the existing ManicTime tag structure used by the office group. These provide immediate utility without requiring manual setup. ### Project Phases (`category: 'phase'`, `scope: 'global'`) | Label | Color | Description | |---|---|---| | CU | `#3b82f6` | Certificat de Urbanism | | Schita | `#8b5cf6` | Schita de proiect | | Avize | `#06b6d4` | Obtinere avize | | PUD | `#10b981` | Plan Urbanistic de Detaliu | | AO | `#f59e0b` | Autorizatie de Construire (obtinere) | | PUZ | `#ef4444` | Plan Urbanistic Zonal | | PUG | `#ec4899` | Plan Urbanistic General | | DTAD | `#6366f1` | Documentatie Tehnica pentru Autorizatia de Desfiintare | | DTAC | `#14b8a6` | Documentatie Tehnica pentru Autorizatia de Construire | | PT | `#f97316` | Proiect Tehnic | | Detalii de Executie | `#84cc16` | Detalii de executie | ### Activities (`category: 'activity'`, `scope: 'global'`) | Label | Color | |---|---| | Redactare | `#6366f1` | | Depunere | `#10b981` | | Ridicare | `#f59e0b` | | Verificare proiect | `#ef4444` | | Vizita santier | `#8b5cf6` | | Releveu | `#3b82f6` | | Reclama | `#ec4899` | | Design grafic | `#06b6d4` | | Design interior | `#14b8a6` | | Design exterior | `#84cc16` | ### Document Types (`category: 'document-type'`, `scope: 'global'`) | Label | Color | |---|---| | Regulament | `#6366f1` | | Parte desenata | `#10b981` | | Parte scrisa | `#3b82f6` | ### Company Tags (`category: 'company'`, `scope: 'global'`) | Label | Color | CompanyId | |---|---|---| | Beletage | `#2563eb` | `beletage` | | Urban Switch | `#16a34a` | `urban-switch` | | Studii de Teren | `#dc2626` | `studii-de-teren` | ### Priority Tags (`category: 'priority'`, `scope: 'global'`) | Label | Color | |---|---| | Urgent | `#ef4444` | | Normal | `#3b82f6` | | Scazut | `#6b7280` | ### Status Tags (`category: 'status'`, `scope: 'global'`) | Label | Color | |---|---| | In lucru | `#f59e0b` | | Finalizat | `#10b981` | | In asteptare | `#6b7280` | | Anulat | `#ef4444` | ### Seeding Implementation ```typescript // src/lib/tags/seed-tags.ts import type { Tag, TagCategory } from '@/types/tags'; const SEED_TAGS: Omit[] = [ { label: 'CU', category: 'phase', scope: 'global', color: '#3b82f6' }, { label: 'DTAC', category: 'phase', scope: 'global', color: '#14b8a6' }, // ... all tags from tables above ]; export async function seedTagsIfEmpty(tagService: TagService): Promise { const existing = await tagService.getAllTags(); if (existing.length > 0) return; // only seed into empty storage await tagService.importTags(SEED_TAGS); } ``` The seeding runs once on application startup. If any tags already exist, seeding is skipped entirely. Users can reset to defaults from the Tag Manager module. --- ## Cross-Module Usage Every module that supports tagging stores tag IDs as a `string[]` on its entities. The actual tag data lives in the shared tag storage, not duplicated per module. ### Registratura ```typescript interface RegistryEntry { id: string; // ... other fields tagIds: string[]; // typically: project + document-type + phase tags } ``` **Typical tagging pattern:** A registry entry for a building permit application might carry tags `["076 Casa Copernicus", "DTAC", "Depunere", "Parte scrisa"]`. ### Prompt Generator ```typescript interface PromptTemplate { id: string; // ... other fields tagIds: string[]; // typically: activity + custom domain tags } ``` **Typical tagging pattern:** An architectural prompt template might carry `["Design exterior", "Rendering"]`. ### IT Inventory ```typescript interface InventoryDevice { id: string; // ... other fields tagIds: string[]; // typically: company + location tags } ``` **Typical tagging pattern:** A laptop entry might carry `["Beletage", "Birou Centru"]`. ### Address Book ```typescript interface Contact { id: string; // ... other fields tagIds: string[]; // typically: custom contact-type tags } ``` **Typical tagging pattern:** A contact might carry `["Client", "Beletage"]` or `["Furnizor", "Materiale constructii"]`. ### Digital Signatures ```typescript interface SignatureRecord { id: string; // ... other fields tagIds: string[]; // typically: company + signer identity tags } ``` ### Integration Pattern All modules follow the same pattern for tag integration: 1. Entity has a `tagIds: string[]` field. 2. Forms include a `` for editing tags. 3. List/table views include a `` for filtering by tags. 4. Detail views render tags as `` components. 5. The module's service does not resolve tag data -- the UI layer calls `TagService.getTag()` or uses the `useTagsById(ids)` hook. ```typescript // src/hooks/useTags.ts /** Resolve an array of tag IDs into Tag objects */ function useTagsById(tagIds: string[]): Tag[] { const [tags, setTags] = useState([]); useEffect(() => { const tagService = getTagService(); tagService.getAllTags().then((allTags) => { const tagMap = new Map(allTags.map((t) => [t.id, t])); setTags(tagIds.map((id) => tagMap.get(id)).filter(Boolean) as Tag[]); }); }, [tagIds]); return tags; } ``` --- ## Tag Auto-Suggest The `TagSelector` component supports contextual auto-suggestion. When a module provides context about the current entity, the selector can prioritize relevant tags. ```typescript interface TagSuggestContext { moduleId: string; // which module is requesting suggestions existingTagIds: string[]; // tags already applied to the entity entityType?: string; // e.g., 'registry-entry', 'contact' } ``` ### Suggestion Algorithm 1. **Category affinity:** Suggest tags from categories most commonly used in this module. Registratura prioritizes `project`, `phase`, `document-type`. Address Book prioritizes `company`, `custom`. 2. **Co-occurrence:** Tags frequently applied alongside the already-selected tags are ranked higher. If the user selects "076 Casa Copernicus", phase tags like "DTAC" and "PT" that have been co-applied with that project before are suggested first. 3. **Recency:** Recently used tags (across all entities in the module) are ranked higher than stale ones. 4. **Hierarchy:** If a parent tag is selected, its children are suggested. If "076 Casa Copernicus" is selected, its child phase tags are surfaced. The suggestion ranking is computed client-side from the module's entity data and the global tag list. No server-side analytics or ML is involved. --- ## Import / Export ### Export Format (JSON) ```json { "version": "1.0", "exportedAt": "2025-03-15T10:30:00Z", "tags": [ { "label": "CU", "category": "phase", "scope": "global", "color": "#3b82f6", "parentId": null, "metadata": {} } ] } ``` Export strips `id`, `createdAt`, and `updatedAt` since these are regenerated on import. The `parentId` field uses the parent's `label` + `category` as a composite key for portability (since IDs are not stable across environments). ### Import Rules - Duplicate detection: if a tag with the same `label` + `category` + `scope` already exists, the import skips it (no overwrite). - Parent resolution: `parentId` in the import file is a `label:category` reference, resolved to the actual ID after all tags are created. - Validation: all import entries are validated against the same rules as `createTag`. Invalid entries are skipped and reported. ### CSV Format For simpler workflows, tags can be imported from CSV: ```csv label,category,scope,color,parentLabel,parentCategory CU,phase,global,#3b82f6,, Redactare,activity,global,#6366f1,CU,phase ```