Title: Store & Template System Description: Module store for rapid application development, replacing the legacy template system --- Store & Template System The Store provides a curated catalogue of pre-built modules that can be installed into a project. Each module encapsulates one or more features together with optional extensions, allowing rapid application development with consistent data modelling patterns. Migration note: The legacy /settings/templates route has been removed. Users navigating to the old URL will receive a 404. All references should be updated to point directly to /settings/store. Overview The store system is built from three registry concepts defined in packages/shared/src/templates/: | Concept | Description | |---|---| | StoreModule | A single installable unit of functionality (e.g. Contacts, Real Estate) | | StoreTag | A classification label attached to modules (industry or function) | | StoreCollection | A curated bundle of modules with pre-selected extensions | Registries are populated at startup via a side-effect import of store.init.ts, which calls registerModule, registerTag, and registerCollection for every entry defined in store.modules.ts, store.tags.ts, and store.collections.ts. Navigating to the Store Open a project in the backoffice. Go to Project Settings. Click the Store tile (icon: fa-puzzle-piece). The store page is served from: /projects/[projectId]/settings/store Note: The old /projects/[projectId]/settings/templates route no longer exists. Update any bookmarks or links to use the store URL directly. Store UI Layout The store page (+page.svelte → StoreLayout.svelte) is composed of: Search bar – full-text search across module names and descriptions (searchModules()). Updates banner (StoreUpdatesBanner.svelte) – shown when one or more installed modules have a newer version available. Lists each outdated module with current and available version numbers, and the latest changelog entry. Sidebar (StoreSidebar.svelte) – filter panel with three sections: All Modules – clears any active filter. Industry tags – e.g. Real Estate, Handyman. Function tags – e.g. CRM, Blog. Collections – curated bundles (see below). Module list (StoreModuleList.svelte) – renders a StoreModuleRow for each filtered module. Module Row (StoreModuleRow.svelte) Each row displays: Module icon, label, and current/installed version. Tag labels derived from the module's tags array. A status badge or action button: Install (primary) – module not yet installed. Update (warning) – a newer version is available. Installed (success badge) – up to date. An expand chevron that reveals the module description and its extension list. Expanding a row shows all extensions via StoreExtensionToggle.svelte. Required extensions are checked and disabled; optional extensions can be toggled by the user. Dependencies between extensions are resolved automatically via resolveExtensionDependencies(). Clicking Install or Update opens the InstallModuleModal. Extension Toggle (StoreExtensionToggle.svelte) Each extension is shown with: Its translated label. A badge indicating whether it is required, auto-added (pulled in as a dependency of another selected extension, showing the requiring extension's name), or optional. A disabled checkbox when required or auto-added. Install Module Modal (InstallModuleModal.svelte) The modal drives the installation flow: Calls executeTemplate() with the module's feature name passed directly via the features array, along with the user's chosen extensions and a priorExecutionResult seeded from existing project data (see Cross-Module Dependency Resolution). Sends a PUT request to /api/projects/[projectId]/extensions with an ApplyTemplateBodyWithModule payload that includes moduleName, moduleVersion, and installedExtensions. Re-executes already-installed modules to resolve cross-module dependencies (see Re-Execution of Installed Modules). On success the project's installedModules array is updated server-side (see Module Installation Tracking). Creating a New Project with Modules The New Project page (/projects/new) allows modules to be selected before the project is created. Project creation always goes through this UI flow — modules cannot be selected via the API. UI Flow Navigate to /projects/new. Optionally click a Collection in the sidebar to pre-select an entire bundle of modules with their recommended extensions. The project name field is auto-populated from the collection label. Toggle individual modules on/off; expand a module row to adjust its extensions. Enter or adjust the project name using the project name input field. Click Create to submit. After creation, the browser is redirected to the new project URL (/projects/[projectId]). The page waits for a full load before proceeding. Note: The POST /api/projects endpoint no longer accepts a templateName parameter. Module selection is exclusively handled through the new project UI. Any integrations or scripts that previously passed templateName to the API must be updated to use the UI flow. On creation the server applies all selected module templates in a single transaction. StoreModule Defined in store.types.ts: interface StoreModule { name: FeatureName; // unique identifier (matches FeatureName enum) label: InlineTranslation; description: InlineTranslation; icon: string; // URL to module icon image version: string; // semver string, e.g. "1.2.0" tags: string[]; // StoreTag names requiredExtensions: FeatureExtensionName[]; optionalExtensions?: FeatureExtensionName[]; extensions: FeatureExtensionName[]; // all available extensions changelog?: ChangelogEntry[]; } FeatureExtension Dependencies Extensions can declare dependencies on other extensions via the optional requires field (added in this release): interface FeatureExtension { name: string; label: InlineTranslation; / Other extensions that must be installed before this one */ requires?: FeatureExtensionName[]; check: (options: FeatureExtensionCheckOptions) => FeatureExtensionCheckResult; execute: (options: FeatureExtensionExecuteOptions) => void; } When a user selects an extension that has requires, the required extensions are automatically added to the selection (shown with an "auto-added" badge). This is handled by resolveExtensionDependencies() in packages/shared. StoreCollection Collections are curated bundles defined in store.collections.ts: interface StoreCollection { name: string; label: InlineTranslation; description: InlineTranslation; icon: string; color: string; modules: FeatureName[]; preSelectedExtensions?: FeatureExtensionName[]; } Built-in Collections | Collection | Modules included | Description | |---|---|---| | Real Estate Pack | Contact, Todo, Real Estate | — | | CRM Pack | Contact, Email | — | | Blog Pack | Blog | — | | Handyman Pack | Handyman | — | | Warehouse Pack | Warehouse | Product catalog and inventory management — ready for warehouse teams | | SaaS Website Pack | Website SaaS | — | | Landing Page Pack | Website Landing Page | — | Selecting a collection in the sidebar filters the module list to the collection's modules and pre-selects the recommended extensions. The Warehouse Pack (warehouse-pack) is identified by the color #65A30D and the icon at https://img.icons8.com/fluency/96/warehouse.png. Selecting it on the new project page auto-populates the project name with "Warehouse" / "Lagerverwaltung". StoreTag Tags classify modules for filtering. Each tag has a type of either "industry" or "function": interface StoreTag { name: string; label: InlineTranslation; type: "industry" | "function"; icon?: string; // FontAwesome class } All tags are registered through store.tags.ts and can be queried with getTagsByType("industry") or getTagsByType("function"). Module Reference Check-in Module (FeatureName.CHECKIN) Visitor registration and check-in management with GDPR-compliant data retention, QR code scanning, and email notifications. Features: VISITOR, CONTACT Note: The comingSoon flag has been removed — this module is now fully available for installation. Extensions | Extension | Name constant | Type | Required? | |---|---|---|---| | Visitor Data Type | VISITORS_TYPE | Data type | ✅ Required | | Visitor Events Data Type | VISITOREVENTSTYPE | Data type | ✅ Required | | Check-in Application | CHECKIN_APP | Application | ✅ Required | | GDPR Visitor Cleanup | CHECKIN_GDPR | Action (cron) | ✅ Required | | Check-in Notifications | CHECKIN_NOTIFICATIONS | Actions (email) | Optional | | Visitors Backoffice Module | VISITORS_BOM | BOM | Optional | | Visitor Events Backoffice Module | VISITOREVENTSBOM | BOM | Optional | Extension Details Visitor Data Type (VISITORS_TYPE) Creates a data type for storing visitor records. Fields include: firstName — First name (string) lastName — Last name (string) email — Email address (string) company — Company name (string) contactPersonId — Foreign-key reference to a Contact record (linked to FeatureRef.CONTACT_TYPE when the Contact module is installed) latestEventType — Most recent check-in event type ("in" | "out") latestEventAt — Timestamp of the latest event (datetime) deleteOn — Scheduled deletion timestamp for GDPR retention (datetime) deleteOnPreference — Visitor-selected retention period in milliseconds (number) Navigation entry: /visitors (icon: fa-address-book) Visitor Events Data Type (VISITOREVENTSTYPE) Creates a data type for tracking individual sign-in and sign-out events. Fields include: visitorId — Foreign-key reference to a Visitor record (linked to FeatureRef.VISITOR_TYPE) type — Event type enum: "in" (Sign In) | "out" (Sign Out) createdAt — Event timestamp (datetime) Navigation entry: /visitor-events (icon: fa-clock-rotate-left) Check-in Application (CHECKIN_APP) Creates a SvelteKit application with a single public-facing page at / containing the CheckIn widget (WIDGET_CHECKIN). The application is configured with: No navbar or tab navbar (kiosk-style layout) Public access (no authentication required) Default widget data pre-populated with welcome text and GDPR small print in English and German QR code scanner enabled by default Configurable deletion period options for visitor data retention The CheckIn widget (WIDGET_CHECKIN / "CheckIn") can also be added independently to any page via the widget catalogue (category: content, visible in catalogue). GDPR Visitor Cleanup (CHECKIN_GDPR) Creates a cron-triggered action that runs daily at 02:00 (0 2 *) to automatically delete visitor records and associated events whose deleteOn timestamp has passed. The action is pre-configured with: visitorCollectionName — derived from the Visitor type visitorEventCollectionName — derived from the Visitor Events type Dependency: This extension requires both VISITORSTYPE and VISITOREVENTS_TYPE to be present. If either is absent, the extension is skipped silently. Check-in Notifications (CHECKIN_NOTIFICATIONS) Creates three separate email actions triggered via API: | Action | Purpose | |---|---| | Visitor Pass | Sends a visitor pass email to the registered visitor | | Contact Person Notification | Notifies the designated contact person of an arrival | | Calendar Invitation | Sends a calendar invite (iCal format) to relevant parties | Each action uses a SendMail block connected to an API trigger. The trigger's configuration schema includes a sendEmailConfigurationId field with a foreign-ID selector pre-scoped to the project's send-email configurations collection. Visitors Backoffice Module (VISITORS_BOM) Adds a Visitors management page to the backoffice navigation sidebar with: Header text: "Visitors" / "Besucher" Navigation entry icon: fa-address-book Navigation path: /visitors Visitor Events Backoffice Module (VISITOREVENTSBOM) Adds a Visitor Events log page to the backoffice navigation sidebar with: Header text: "Visitor Events" / "Besucherereignisse" Navigation entry icon: fa-clock-rotate-left Navigation path: /visitor-events Data Model Cross-Module Dependencies | Extension | Looks for | Effect when found | |---|---|---| | visitor.type | FeatureRef.CONTACT_TYPE | Adds contactPersonId foreign-key field linking visitors to contacts | | visitor-events.type | FeatureRef.VISITOR_TYPE | Adds visitorId foreign-key field linking events to visitor records | | checkin-app | FeatureRef.VISITOR_TYPE | Configures widget visitorTypeId | | checkin-app | FeatureRef.VISITOREVENTSTYPE | Configures widget visitorEventTypeId | | checkin-app | FeatureRef.CONTACT_TYPE | Configures widget contactsTypeId | | checkin-gdpr | FeatureRef.VISITOR_TYPE | Sets visitorCollectionName on the cron trigger | | checkin-gdpr | FeatureRef.VISITOREVENTSTYPE | Sets visitorEventCollectionName on the cron trigger | Check-in Widget (CheckInWidget.svelte) The CheckIn widget provides a self-contained visitor registration UI. Its schema-driven configuration options include: | Field | Type | Description | |---|---|---| | title | InlineTranslation | Heading shown on the welcome screen | | description | InlineTranslation | Sub-heading / instructions | | successSignInMessage | InlineTranslation | Message displayed after a successful check-in | | successSignOutMessage | InlineTranslation | Message displayed after a successful check-out | | smallPrint | InlineTranslation | GDPR / privacy notice text | | scannerEnabled | boolean | Whether the QR code scanner button is shown | | scannerButtonText | InlineTranslation | Label for the scanner button | | deletionPeriods | DeletionPeriod[] | Selectable GDPR retention periods offered to the visitor | | visitorTypeId | string | ID of the Visitor data type | | visitorEventTypeId | string | ID of the Visitor Events data type | | contactsTypeId | string | ID of the Contacts data type | The widget's internal state machine moves through the following states: welcome → register → success (check-in path) or welcome → scanner → profile → success (QR scan / check-out path). Fields latestEventType, latestEventAt, deleteOn, and deleteOnPreference are automatically managed by the widget and are hidden from the registration form shown to visitors. Visitor API Endpoints Three server-side API endpoints are registered under /api/projects/[projectId]/visitors/: POST /api/projects/[projectId]/visitors Creates a new visitor record. Request body: { "collectionName": "", "data": { "firstName": "Jane", "lastName": "Doe", "..." } } Response: 201 Created with { "id": "" } GET /api/projects/[projectId]/visitors/[visitorId] Retrieves a single visitor record. Query parameters: collectionName (required) — the visitor collection name Response: 200 OK with the visitor document, or 404 if not found. Returns 400 if collectionName is omitted. PATCH /api/projects/[projectId]/visitors/[visitorId] Partially updates a visitor record. Request body: { "collectionName": "", "data": { "latestEventType": "out", "latestEventAt": 1700000000000 } } Response: 200 OK with { "ok": true } DELETE /api/projects/[projectId]/visitors/[visitorId] Deletes a visitor record. Query parameters: collectionName (required) — the visitor collection name Response: 200 OK with { "ok": true }, or 400 if collectionName is omitted. POST /api/projects/[projectId]/visitors/[visitorId]/events Creates a new visitor event (sign-in or sign-out) linked to the given visitor. Request body: { "collectionName": "", "data": { "visitorId": "", "type": "in", "createdAt": 1700000000000 } } Response: 201 Created with { "id": "" } iCal Generation (packages/server/src/utils/ical.ts) A utility function generateICalContent(options: ICalEventOptions) is available on the server for generating RFC 5545-compliant .ics file content, used by the Calendar Invitation notification action. interface ICalEventOptions { title: string; description?: string; location?: string; start: Date; end: Date; organizer?: { name: string; email: string }; attendee?: { name: string; email: string }; url?: string; } The generated content uses PRODID:-//smallstack//Check-in//EN, METHOD:REQUEST, and includes proper text escaping (backslashes, semicolons, commas, newlines) per the RFC. Warehouse Module (FeatureName.WAREHOUSE) — v1.0.0 Inventory management with product catalog, stock levels, locations and movements. Tags: logistics, ecommerce Icon: https://img.icons8.com/fluency/96/warehouse.png Color: #65A30D Extensions | Extension | Name constant | Type | Required? | |---|---|---|---| | Product Data Type | PRODUCTS_TYPE | Data type | ✅ Required | | Products Backoffice Module | PRODUCTS_BOM | BOM | Optional | | Storage Location Data Type | WAREHOUSELOCATIONTYPE | Data type | ✅ Required | | Storage Locations Backoffice Module | WAREHOUSELOCATIONBOM | BOM | Optional | | Warehouse Stock Data Type | WAREHOUSE_TYPE | Data type | ✅ Required | | Warehouse Backoffice Module | WAREHOUSE_BOM | BOM | Optional | The three data-type extensions (PRODUCTSTYPE, WAREHOUSELOCATIONTYPE, WAREHOUSETYPE) are required and always installed. The three BOM (Backoffice Module) extensions are optional and provide the corresponding navigation entries and list views in the backoffice sidebar. Note: The Warehouse Stock type (WAREHOUSE_TYPE) depends on both the Product type and the Storage Location type being present. If either is skipped, the warehouse stock type is also skipped automatically. Data Model The Warehouse module installs three interconnected data types: Products (FeatureRef.PRODUCTS_TYPE) Multilingual title, short title, description, and short description tags — array of strings prices — array of price objects (value + price zone ID) options — product options (e.g. size, colour) variants — product variants files — attached files status — "active" | "inactive" (default: "active") supplier — supplier information Required field: title Navigation: /products (icon: fa-box-open) Storage Locations (FeatureRef.WAREHOUSELOCATIONTYPE) name — multilingual name of the storage location (e.g. "Hauptlager München", "Keller") zone — area or zone within the warehouse (e.g. Receiving, Shipping, Cold Storage) description — free-text description (textarea) Required field: name Navigation: /warehouse-locations (icon: fa-map-marker-alt) Warehouse Stock (FeatureRef.WAREHOUSE_TYPE) name — multilingual name of the stock item productId — foreign-key reference to a Product record (uses INPUTWIDGETFOREIGNID / VIEWWIDGETFOREIGNID) locationId — foreign-key reference to a Storage Location record quantity — numeric stock quantity minQuantity — minimum stock threshold unit — unit of measurement status — "instock" | "lowstock" | "outofstock" (default: "in_stock") notes — free-text notes (textarea) Required fields: name, productId, locationId, quantity Navigation: /warehouse (icon: fa-warehouse) Product Module (FeatureName.PRODUCT) — v1.0.0 Standalone product catalog with variants, pricing and media. Tags: ecommerce, logistics Icon: https://img.icons8.com/fluency/96/box.png Color: #EA580C Extensions | Extension | Name constant | Type | Required? | |---|---|---|---| | Product Data Type | PRODUCTS_TYPE | Data type | ✅ Required | | Products Backoffice Module | PRODUCTS_BOM | BOM | Optional | The Product module can be installed independently when only a product catalog is needed, without the full warehouse stock and location management provided by the Warehouse module. Module Installation Tracking When a module is installed through the store, the server records the installation on the project document. API endpoint PUT /api/projects/[projectId]/extensions The request body extends ApplyTemplateBody with optional module tracking fields: interface ApplyTemplateBodyWithModule extends ApplyTemplateBody { / When provided, records the installation on the project */ moduleName?: string; moduleVersion?: string; installedExtensions?: string[]; } Server logic (+server.ts) If moduleName and moduleVersion are present in the request body, the handler (inside the existing MongoDB transaction) writes to the projects collection: New module: $push an InstalledModule document onto project.installedModules. Existing module (re-install or update): $set the updatedAt, updatedVersion, and installedExtensions fields on the matching array element using the positional operator ($). The InstalledModule shape (from @smallstack/shared): interface InstalledModule { moduleName: string; installedAt: string; // ISO 8601 installedVersion: string; installedExtensions: InstalledExtension[]; updatedAt?: string; updatedVersion?: string; } interface InstalledExtension { extensionName: string; installedAt: string; } All writes participate in the same session/transaction as the template entity creation, so a failed entity application will also roll back the tracking update. Registry API (store.registry.ts) Helper functions available from @smallstack/shared: | Function | Description | |---|---| | listModules() | All registered StoreModule objects | | getModule(name) | Look up a module by FeatureName | | searchModules(query) | Full-text search over module names, labels, and descriptions | | listCollections() | All registered StoreCollection objects | | getTagsByType(type) | Tags filtered by "industry" or "function" | | getTag(name) | Look up a tag by name | | isModuleInstalled(project, name) | Whether a module is recorded in project.installedModules | | getInstalledVersion(project, name) | Returns the installed version string or undefined | | hasModuleUpdate(project, name) | true when the registry version is higher than the installed version | | getModulesWithUpdates(project) | Array of StoreModule objects that have updates available | | resolveExtensionDependencies(selected, getExt) | Expands a selection to include all required extensions | Template Execution (executeTemplate) The executeTemplate() function in packages/shared/src/services/template-execution.service.ts drives all module installation logic. It accepts a features array of feature name strings directly, rather than a FeatureTemplate wrapper object. interface TemplateExecutionOptions { / Feature names whose extensions should be executed */ features: string[]; currentData: FeatureExtensionExecuteCurrentDataOptions; answers?: { [extensionName: string]: { [code: string]: string } }; autoSelectRecommended?: boolean; selectedExtensions?: FeatureExtensionName[]; priorExecutionResult?: FeatureTemplateExecutionResult; } Callers pass the module's feature name(s) directly: executeTemplate({ features: [mod.name], currentData: ..., autoSelectRecommended: true, priorExecutionResult: accumulatedResult, }); This applies in all three call sites: Install Module Modal — installs a single module into an existing project. New Project page — accumulates results across all selected modules before project creation. Re-execution of installed modules — updates dependent modules after a new module is installed. Placeholder ID Generation & Updates When templates define relationships between newly added entities they rely on stable IDs to preserve referential integrity. The platform uses placeholder tokens of the form #generateId()#. Lifecycle Collection: All placeholder tokens are recursively collected from the template payload. Mapping: A deterministic mapping from placeholder token → newly generated ObjectId is built. The same placeholder string always resolves to the same ID within one update execution. Replacement: All occurrences of placeholder tokens are replaced before any entity is created. Persistence: The mapping is stored with the corresponding ProjectUpdate so later incremental updates can extend the existing mapping. Validation: After replacement a defensive assertion aborts the update if any #generateId( pattern still exists. Reserved Tokens #projectId# – replaced with the current project identifier wherever it appears in navigation definitions or other project-scoped structures. Update Operation Model (Discriminated Union) Template version diffs are expressed as a discriminated union: interface AddOperation { action: "add"; collectionName: string; templateId: string; // placeholder id from: null; to: unknown; // full document payload modifier: Modifier; description: InlineTranslation; safetyCheck?: SafetyCheckResult; } interface ChangeOrRemoveOperation { action: "change" | "remove"; collectionName: string; templateId: string; actualId: string; // existing DB id resolved from prior mapping from: unknown; // expected current value to: unknown; // new value or null for remove modifier: Modifier; description: InlineTranslation; safetyCheck?: SafetyCheckResult; } type UpdateOperation = AddOperation | ChangeOrRemoveOperation; Invariants: action === 'add' ⇒ no actualId field; inherently safe. action !== 'add' ⇒ actualId must be present; safety-checked against the live DB value. Template Versioning Policy Released Template Immutability CRITICAL RULE: Once a project template is released and deployed to production, it must NEVER be modified. Any changes require creating a new version. Why This Rule Exists Data Integrity: Existing projects depend on the template structure. Update Safety: The template update system relies on version comparison. Rollback Safety: Users can revert to previous template versions. Testing Consistency: Tests and validations are tied to specific versions. Version Management Process New Features/Changes: Always increment the version number. File Naming: Follow the convention template-name.v{X}.json. Testing Only: Use a separate template code (e.g. real-estate-test); never modify released versions. Allowed Operations on Released Templates ✅ Bug fixes in new versions — create a higher version ✅ Adding new fields — create a higher version ✅ Performance improvements — create a higher version ❌ Modifying existing versioned files ❌ Removing fields ❌ Breaking changes to existing field behaviour Extension Deselection When installing a module, users can toggle optional extensions on or off. The executeTemplate() function accepts an optional selectedExtensions parameter — an array of FeatureExtensionName values. When provided, only extensions in this list are executed during the check, execute, and postExecute phases. Required extensions should always be included. This filtering applies in three places: Install Module Modal — passes the user's toggle selection as selectedExtensions. New Project page — passes the extensions chosen during project creation. Re-execution of dependent modules — uses the installedExtensions stored on the project's InstalledModule record, so previously deselected extensions remain excluded. If selectedExtensions is omitted, all extensions are executed (backward-compatible default). Cross-Module Dependency Resolution Some extensions produce entities that reference entities from other modules. For example, the Real Estate type extension checks for an existing Contact type (via FeatureRef.CONTACT_TYPE) to add a contact-linking field. When modules are installed one-by-one in any order, these cross-module references must resolve regardless of installation sequence. The Problem executeTemplate() builds entities in currentExecutionResult, keyed by FeatureRef. Extensions look up peer entities in this map (e.g. currentExecutionResult[FeatureRef.CONTACT_TYPE]). When installing Real Estate into a project that already has Contacts, the Contact entities exist in the database but not in currentExecutionResult — so the cross-module link is silently skipped. The Solution: buildPriorResultFromCurrentData() Before calling executeTemplate(), the Install Module Modal calls buildPriorResultFromCurrentData(currentData). This helper scans all entity collections in the current project data for entries with a featureRef field and builds a FeatureTemplateExecutionResult map with action: "ignore" for each. This map is passed as priorExecutionResult to seed the execution context. The execution engine merges priorExecutionResult into currentExecutionResult before running extensions. Extensions can now find peer entities from previously installed modules. After execution, prior keys are stripped from the result so the caller only sees newly produced entities. Entity collections scanned: types, compoundWidgets, actions, aiAgents, applications, roles, configurations, navigationEntries. Known Cross-Module Dependencies | Extension | Looks for | Effect when found | |---|---|---| | real-estate.type | FeatureRef.CONTACT_TYPE | Adds contact-linking field to Real Estate type | | contact.type | FeatureRef.EMAILS_TYPE | Adds saved-queries filter for email-linked contacts | | email.type | FeatureRef.CONTACT_TYPE | Adds contact widget to Email type | | warehouse.type | FeatureRef.WAREHOUSELOCATIONTYPE | Links stock items to storage locations via foreign-key field | | warehouse.type | FeatureRef.PRODUCTS_TYPE | Links stock items to products via foreign-key field | | visitor.type | FeatureRef.CONTACT_TYPE | Adds contactPersonId foreign-key field to Visitor type | | visitor-events.type | FeatureRef.VISITOR_TYPE | Adds visitorId foreign-key field to Visitor Events type | | checkin-app | FeatureRef.VISITOR_TYPE | Configures the Check-in widget visitorTypeId | | checkin-app | FeatureRef.VISITOREVENTSTYPE | Configures the Check-in widget visitorEventTypeId | | checkin-app | FeatureRef.CONTACT_TYPE | Configures the Check-in widget contactsTypeId | | checkin-gdpr | FeatureRef.VISITOR_TYPE | Sets visitorCollectionName on the GDPR cron trigger | | checkin-gdpr | FeatureRef.VISITOREVENTSTYPE | Sets visitorEventCollectionName on the GDPR cron trigger | Re-Execution of Installed Modules When a new module is installed, already-installed modules may need to update their entities to pick up newly available cross-module links. For example, installing Contacts after Real Estate should retroactively add the contact-linking field to the Real Estate type. How It Works After the primary module is installed, InstallModuleModal calls getInstalledModulesForReExecution(project, newModuleName) which returns all other installed modules. For each: executeTemplate() is called with the dependent module's feature name passed directly via the features array, along with: priorExecutionResult — accumulated results from the primary install plus any prior re-executions selectedExtensions — the extensions originally chosen when the dependent module was installed (read from project.installedModules) Only entities with action: "update" are sent to the server — new entities and ignored entities are skipped. The server applies updates to existing entities without creating duplicates. This ensures that cross-module features work regardless of installation order. Integration The store integrates with: Widget System: Automatic UI generation based on extension-defined field types. Actions System: Workflow automation provided by action extensions. File Sharing: Document attachment extensions. SignalDB: Local-first synchronisation and offline support. Permissions / RLS: Row-level security enforced on every PUT /extensions call (RLSUPDATEPERMISSION). MongoDB Transactions: All entity creation and module tracking writes are wrapped in a single session to guarantee atomicity. Future Enhancements Planned improvements: Community modules: Third-party contributed store modules. Module dependencies: Automatic installation of prerequisite modules. Rollback support: Uninstall or downgrade an installed module. AI-assisted configuration: Suggest modules from a natural language project description. Import/export: Share custom module configurations between projects.