--- name: ms-access-vcs description: Working with Microsoft Access databases exported via VCS (Version Control System for Access). Use when editing Access form definitions (.bas), report definitions, query files, VBA modules, linked table definitions, or VCS configuration files. Covers structural rules, common gotchas, and safe editing practices for the binary text format used by Access's LoadFromText/SaveAsText. --- # Microsoft Access VCS Project Guide Rules and gotchas for programmatically editing Access objects exported by VCS 4.x. Violations cause silent corruption or `Error 2128` on import via `LoadComponentFromText`. ## Project Structure A VCS-exported Access project lives in a directory named `.accdb.src/` (e.g., `CRM.accdb.src/`). This is the root of all exported source files. The directory name **must** match the `.accdb` filename with `.src` appended — VCS uses this convention to locate source files for import. ### Directory Layout ``` .accdb.src/ ├── forms/ # Form definitions (required for any UI) ├── reports/ # Report definitions ├── queries/ # Query definitions (SQL + metadata) ├── modules/ # VBA standard modules ├── macros/ # Access macros (not VBA — UI action sequences) ├── tbldefs/ # Table definitions (linked tables + local tables) ├── relations/ # Table relationships (foreign keys, join types) ├── images/ # Shared image gallery items ├── themes/ # Office theme files ├── imexspecs/ # Import/Export specifications │ ├── vcs-options.json # VCS addon configuration (REQUIRED) ├── vcs-index.json # Component index with file hashes (REQUIRED) ├── project.json # Database file format settings (REQUIRED) ├── vbe-project.json # VBE project metadata (REQUIRED) ├── vbe-references.json # External VBA library references (REQUIRED) ├── dbs-properties.json # Database properties — startup form, etc. (REQUIRED) ├── db-connection.json # ODBC connection string cache (REQUIRED if linked tables) ├── documents.json # Document-level properties (optional) ├── nav-pane-groups.json # Navigation pane custom groups (optional) ├── hidden-attributes.json # Hidden object flags (optional) │ ├── Build.log # Last build (import) log — generated by VCS ├── Export.log # Last export log — generated by VCS └── Merge.log # Last merge log — generated by VCS ``` ### Object Directories and Their File Types | Directory | Object Type | File Extensions | Notes | |-----------|------------|-----------------|-------| | `forms/` | Forms | `.bas` (layout) + optional `.cls` (VBA code-behind) + optional `.json` (print settings) | `.bas` is required. `.cls` omitted if no VBA code. `.json` omitted if no custom print settings. | | `reports/` | Reports | `.bas` (layout) + optional `.cls` (VBA code-behind) + optional `.json` (print settings) | Same triplet pattern as forms. `.cls` is uncommon for reports but valid. | | `queries/` | Queries | `.bas` (metadata/designer state) + `.sql` (SQL text) | Always paired 1:1. Both required. | | `modules/` | VBA Modules | `.bas` | Standard VBA module code. One file per module. | | `macros/` | Macros | `.bas` | Access macro actions (not VBA). Simpler binary text format. | | `tbldefs/` | Linked Tables | `.json` | ODBC connection, source table name, attributes. | | `tbldefs/` | Local Tables | `.xml` | XSD schema defining columns, data types, indexes, properties. Used for local (non-linked) tables stored inside the .accdb. | | `relations/` | Relationships | `.json` | Foreign key relationships between tables (join type, referential integrity). | | `images/` | Shared Images | `.json` (metadata) + image file (`.png`, `.jpg`, etc.) | Gallery items referenced by forms/reports. Paired by name. | | `themes/` | Themes | `.thmx` | Office Open XML theme files. Binary — do not edit. | | `imexspecs/` | Import/Export Specs | `.json` | Column definitions, delimiters, data types for file import/export operations. | ### Configuration File Reference #### `vcs-options.json` — VCS Addon Configuration (REQUIRED) Controls how VCS exports and imports. Key settings: ```json { "Info": { "AddinVersion": "4.1.2", "AccessVersion": "16.0 64-bit" }, "Options": { "ShowDebug": true, "UseFastSave": true, "UseMergeBuild": false, "SavePrintVars": true, "SaveQuerySQL": true, "FormatSQL": true, "SplitLayoutFromVBA": true, "SanitizeLevel": 2, "HashAlgorithm": "SHA256", "UseShortHash": true, "TablesToExportData": {}, "ExportPrintSettings": { "Orientation": true, "PaperSize": true } } } ``` - `SplitLayoutFromVBA`: When `true`, form/report layout (`.bas`) and VBA code (`.cls`) are exported as separate files. When `false`, VBA is embedded in the `.bas` file. **Must be `true` for proper version control.** - `SaveQuerySQL`: When `true`, exports `.sql` files alongside query `.bas` metadata. - `FormatSQL`: When `true`, SQL is formatted with line breaks for readability. - `SanitizeLevel`: Controls removal of machine-specific data (0=none, 1=basic, 2=aggressive). - `TablesToExportData`: Tables whose **data** (not just schema) should be exported. Used for system/lookup tables. #### `project.json` — Database File Format (REQUIRED) ```json { "Info": {"Class": "clsDbProject", "Description": "Project"}, "Items": { "FileFormat": 12, "RemovePersonalInformation": false } } ``` - `FileFormat`: Access file format version. `12` = Access 2007+ (.accdb format). #### `vbe-project.json` — VBE Project Metadata (REQUIRED) ```json { "Info": {"Class": "clsDbVbeProject", "Description": "VBE Project"}, "Items": { "Name": "MyProject", "Description": "", "FileName": "MyProject.accdb", "HelpFile": "", "HelpContextId": 0, "ConditionalCompilationArguments": "", "Mode": 0, "Protection": 0, "Type": 100 } } ``` - `Name`: VBA project name (shown in VBA editor). Must match database name. - `FileName`: Must match the `.accdb` filename exactly. - `Type`: `100` = standard project. - `Protection`: `0` = unprotected, `1` = locked. #### `vbe-references.json` — External VBA Library References (REQUIRED) ```json { "Info": {"Class": "clsDbVbeReference", "Description": "VBE References"}, "Items": { "stdole": {"GUID": "{00020430-0000-0000-C000-000000000046}", "Version": "2.0"}, "DAO": {"GUID": "{4AC9E1DA-5BAD-4AC7-86E3-24F4CDCECA28}", "Version": "12.0"}, "Office": {"GUID": "{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}", "Version": "2.8"}, "ADODB": {"GUID": "{B691E011-1797-432E-907A-4D8C69339129}", "Version": "2.8"} } } ``` - GUIDs **must** match the exact libraries installed on the target machine. Incorrect GUIDs cause `Error 48: Error in loading DLL` on import. - Common references: `stdole` (OLE Automation), `DAO` (Data Access Objects — required for `CurrentDb`, `Recordset`, etc.), `Office` (Office object library), `ADODB` (ActiveX Data Objects). - Optional references depending on features: `Excel` (for export), `Outlook` (for email). #### `dbs-properties.json` — Database Properties (REQUIRED) DAO-level database properties. Key entries: ```json { "Info": {"Class": "clsDbProperty", "Description": "Database Properties (DAO)"}, "Items": { "StartUpForm": {"Value": "fStartup", "Type": 10}, "AccessVersion": {"Value": "09.50", "Type": 10}, "Build": {"Value": 1, "Type": 4}, "Theme Resource Name": {"Value": "Office Theme", "Type": 10}, "NavPane Category": {"Value": 0, "Type": 4}, "Show Hidden Objects": {"Value": false, "Type": 1}, "Show System Objects": {"Value": false, "Type": 1} } } ``` - `StartUpForm`: Form that opens automatically on database launch. - Property types: `1` = Boolean, `3` = Integer, `4` = Long, `10` = Text, `12` = Memo, `15` = GUID. #### `db-connection.json` — ODBC Connection Cache (REQUIRED if linked tables) Primes Access's internal ODBC connection cache during import. Structure: ```json { "Info": { "Class": "clsDbConnection", "Description": "Database connections used within the project used to prime Access' internal cache during import." }, "Items": { "": { "": "" } } } ``` - The DSN key is a short-form identifier (e.g., `ODBC;DSN=CRM;DATABASE=CRM`). - The full connection string is the canonical ODBC string used by all linked tables and pass-through queries. - The query name is any pass-through query that uses this connection (used to establish the connection on import). - **Must contain exactly one entry** matching the canonical ODBC connection string used by all linked tables and pass-through queries in the project. #### `documents.json` — Document Properties (Optional) ```json { "Info": {"Class": "clsDbDocument", "Description": "Database Documents Properties (DAO)"}, "Items": { "Databases": { "SummaryInfo": {"Author": "Owner", "Company": "Company", "Title": "DatabaseTitle"} }, "Tables": {} } } ``` - `SummaryInfo`: Author, company, title metadata. - `Tables`: Optional descriptions for individual tables/queries. #### `nav-pane-groups.json` — Navigation Pane Groups (Optional) ```json { "Info": {"Class": "clsDbNavPaneGroup", "Description": "Navigation Pane Custom Groups"}, "Items": { "Categories": [ { "Name": "Custom", "Flags": 0, "Position": 3, "Groups": [ {"Name": "Custom Group 1", "Flags": 0, "Position": 1, "Objects": []}, {"Name": "Unassigned Objects", "Flags": 4, "Position": 2, "Objects": []} ] } ] } } ``` #### `hidden-attributes.json` — Hidden Object Flags (Optional) ```json { "Info": {"Class": "clsDbHiddenAttribute", "Description": "Database objects hidden attribute"}, "Items": { "Tables": [], "Queries": [], "Forms": [], "Reports": [], "Macros": [], "Modules": [] } } ``` - Lists objects that are hidden in the Navigation Pane. Each array contains object names as strings. ### `vcs-index.json` — Component Index (REQUIRED, auto-generated) Tracks every exported component with file hashes, export/import dates, and source modification timestamps. **Regenerated on every build/export** — manual edits are overwritten. Structure: ```json { "Info": {"Class": "clsVCSIndex", "Description": "Version Control System Index"}, "Items": { "MergeBuildDate": null, "FullBuildDate": "2026-02-07T09:39:43.000Z", "ExportDate": "2026-02-06T00:58:35.000Z", "FullExportDate": "2026-02-06T00:58:35.000Z", "OptionsHash": "14944f8", "Components": { "DB Connections": { "db-connection.json": { "FileHash": "...", "ExportDate": "...", "ImportDate": "..." } }, "DB Properties": { "dbs-properties.json": { ... } }, "Doc Properties": { "documents.json": { ... } }, "Forms": { "fFormName.bas": { ... }, ... }, "Hidden Attributes": { "hidden-attributes.json": { ... } }, "IMEX Specs": { "Spec Name.json": { ... } }, "Macros": { "mcrMacroName.bas": { ... } }, "Modules": { "ModuleName.bas": { ... } }, "Nav Pane Groups": { "nav-pane-groups.json": { ... } }, "Proj Properties": { "proj-properties.json": { ... } }, "Project": { "project.json": { ... } }, "Queries": { "qQueryName.bas": { ... } }, "Relations": { "tParenttChild.json": { ... } }, "Reports": { "rReportName.bas": { ... } }, "Shared Images": { "ImageName.json": { ... } }, "Tables": { "tTableName.json": { ... }, "tLocalTable.xml": { ... } }, "Themes": { "Office Theme.thmx": { ... } }, "VB Project": { "vbe-project.json": { ... } }, "VBE References": { "vbe-references.json": { ... } } } } } ``` Component category names (used as keys): `DB Connections`, `DB Properties`, `Doc Properties`, `Forms`, `Hidden Attributes`, `IMEX Specs`, `Macros`, `Modules`, `Nav Pane Groups`, `Proj Properties`, `Project`, `Queries`, `Relations`, `Reports`, `Shared Images`, `Tables`, `Themes`, `VB Project`, `VBE References`. ### VCS Build (Import) Order When VCS imports source files back into an `.accdb`, it processes components in this order: 1. DB Connections → 2. Project → 3. VB Project → 4. VBE References → 5. Modules → 6. Shared Images → 7. Themes → 8. DB Properties → 9. IMEX Specs → 10. Tables (local `.xml` first, then linked `.json`) → 11. Relations → 12. Queries → 13. Forms → 14. Macros → 15. Reports → 16. Doc Properties → 17. Nav Pane Groups → 18. Hidden Attributes This order matters: modules must exist before forms/reports that reference their functions, tables must exist before relationships and queries that reference them, relationships must exist before queries that use their join types, and queries must exist before forms that use them as RecordSource. ### Object File Types Not Exported by Default The build log confirms these categories are **scanned but typically empty** in most projects: | Category | Directory | Notes | |----------|-----------|-------| | VBE Forms (UserForms) | — | ActiveX UserForms (`.frm` + `.frx`). Uncommon in Access. | | Proj Properties | — | `proj-properties.json` referenced in index but often empty/missing. | | Saved Specs (Import/Export) | `imexspecs/` | Only present if import/export specs have been saved. | | CommandBars | — | Custom command bars. Rare in modern Access (ribbon replaced toolbars). | | Table Data | — | Controlled by `TablesToExportData` in `vcs-options.json`. | | Table Data Macros | — | Data-level event macros on tables. Uncommon with SQL Server backend. | | Relations | `relations/` | Table relationships. Only absent when relationships are defined in SQL Server instead of Access. | ### Encoding All JSON configuration files and `.sql` files use **UTF-8 with BOM** (byte order mark `EF BB BF`). The BOM must be preserved when editing. Binary text `.bas` files (forms, reports, macros) also use UTF-8 with BOM. ## File Pairing Rules | Object Type | Files | Notes | |-------------|-------|-------| | Form | `.bas` + optional `.cls` + optional `.json` | `.bas` = layout, `.cls` = VBA code-behind (omitted if no code), `.json` = print settings | | Report | `.bas` + optional `.cls` + optional `.json` | Same binary text format as forms, `.cls` = VBA code-behind (uncommon but valid), `.json` = print settings | | Query | `.bas` + `.sql` | `.bas` = metadata/designer state, `.sql` = SQL text. Always paired | | Module | `.bas` | Standard VBA module | | Relationship | `.json` | Foreign key definition, join type, referential integrity | | Linked Table | `.json` | Connection string, source table, attributes | | Local Table | `.xml` | XSD schema defining columns, data types, indexes. For tables stored inside the .accdb | | Macro | `.bas` | Access macro actions (not VBA). Simpler binary text format | | Shared Image | `.json` + image file | `.json` = metadata (name, extension, hash), paired with actual image file (`.png`, `.jpg`, etc.) | | Import/Export Spec | `.json` | Column definitions, delimiters, data types | | Theme | `.thmx` | Office theme (binary OOXML archive — do not edit) | When deleting an object, delete ALL its associated files plus its entry in `vcs-index.json`. ## Binary Text Format (Forms & Reports) Forms and reports use Access's binary text format. **Every `.bas` file must start with a version header** before the `Begin Form` or `Begin Report` block. The overall structure has exactly **one** `Begin...End` container block inside `Begin Form` (or `Begin Report`). This container holds **both** the default control styles **and** all sections. Nothing except form/report-level properties may appear outside this container. ``` Version =20 VersionRequired =20 Begin Form (or Begin Report) RecordSource ="..." <- Form/report-level properties go here Caption ="..." Width =... Begin <- ONE container for everything below Begin Label <- Default control styles (Label, TextBox, CommandButton, etc.) BackStyle =0 FontSize =11 FontName ="Calibri" End Begin TextBox FELineBreak = NotDefault BorderLineStyle =0 Width =1701 LabelX =-1701 FontSize =11 BorderColor =12632256 FontName ="Calibri" AsianLineBreak =1 End Begin CommandButton Width =1701 Height =283 FontSize =11 FontWeight =400 ForeColor =-2147483630 FontName ="Calibri" BorderLineStyle =0 End Begin FormHeader <- Sections follow default styles, INSIDE the same container Height =500 Name ="FormHeader" Begin <- Section controls container Begin Label Name ="lblTitle" ... End End End Begin Section <- Detail section Name ="Detail" Begin ...controls... End End Begin FormFooter (also used for ReportFooter — see report section table) ... End End <- Closes the ONE container End <- Closes Begin Form / Begin Report CodeBehindForm <- Required when SplitLayoutFromVBA=true and .cls exists ' See "fFormName.cls" ``` **Reports** use the same structure as forms. Report-specific sections use these keywords: | Section | Keyword | Name Property | |---|---|---| | Report Header | `FormHeader` | `Name ="ReportHeader"` | | Page Header | `PageHeader` | `Name ="PageHeaderSection"` or `Name ="PageHeader"` | | Group Header | `BreakHeader` | `Name ="GroupHeader0"` (0-indexed) | | Detail | `Section` | `Name ="Detail"` | | Group Footer | `BreakFooter` | `Name ="GroupFooter0"` or `Name ="GroupFooter1"` | | Page Footer | `PageFooter` | `Name ="PageFooterSection"` or `Name ="PageFooter"` | | Report Footer | `FormFooter` | `Name ="ReportFooter"` | Note: Report Header/Footer sections use the **`FormHeader`/`FormFooter` keywords** (not `ReportHeader`/`ReportFooter`). Group Header/Footer sections use **`BreakHeader`/`BreakFooter`** (not `GroupHeader0`/`GroupFooter0`). ### Report Sorting and Grouping Reports define grouping via `Begin BreakLevel` blocks inside the container, placed **after** default control styles and **before** sections. Each `BreakLevel` defines one sorting/grouping level. ``` Begin <- Container Begin Label <- Default styles first ... End Begin TextBox ... End Begin BreakLevel <- Group level definitions GroupHeader = NotDefault <- Show group header section GroupFooter = NotDefault <- Show group footer section ControlSource ="OrderID" <- Field to group by End Begin FormHeader <- Sections follow Name ="ReportHeader" ... End Begin PageHeader ... End Begin BreakHeader <- Group header section Name ="GroupHeader0" Begin ...controls... End End Begin Section Name ="Detail" ... End Begin BreakFooter <- Group footer section Name ="GroupFooter0" Begin ...controls... End End Begin PageFooter ... End Begin FormFooter Name ="ReportFooter" End End <- Closes container ``` **BreakLevel properties:** - `ControlSource ="FieldName"` — the field to sort/group by (required) - `GroupHeader = NotDefault` — show a group header section (optional; omit for sort-only levels) - `GroupFooter = NotDefault` — show a group footer section (optional; omit for sort-only levels) **Multiple break levels:** Define multiple `Begin BreakLevel...End` blocks for multi-level sorting/grouping. Only levels with `GroupHeader`/`GroupFooter = NotDefault` produce visible sections. When multiple break levels exist, add `BreakLevel =N` (0-indexed) on the `BreakHeader`/`BreakFooter` sections to indicate which level they belong to. When there is only one break level, the `BreakLevel` property can be omitted. Example with 4 break levels (only level 2 has visible header/footer): ``` Begin BreakLevel ControlSource ="JobDate" End Begin BreakLevel ControlSource ="Driver" End Begin BreakLevel GroupHeader = NotDefault GroupFooter = NotDefault ControlSource ="Driver" End Begin BreakLevel ControlSource ="StoreArrivalTime" End ... Begin BreakHeader BreakLevel =2 Name ="GroupHeader0" ... End ... Begin BreakFooter BreakLevel =2 Name ="GroupFooter1" ... End ``` **Common structural errors:** - Placing `Begin Label` or sections directly inside `Begin Form` without the container `Begin` → `Expected: 'Begin'. Found: Label.` - Using `Begin Sorting` → `Expected: 'Begin'. Found: Sorting.` (`Sorting` is not valid syntax; use `BreakLevel` instead) - Using `Begin GroupHeader0` or `Begin GroupFooter0` → `Expected: Object Type Name. Found: GroupHeader0.` (use `BreakHeader`/`BreakFooter` instead) - Using `Begin ReportHeader` or `Begin ReportFooter` → use `Begin FormHeader`/`Begin FormFooter` with the appropriate `Name` property instead - Wrapping default styles in their own separate `Begin...End` then having sections outside it → `Expected: 'End'. Found: Begin.` **Key rule:** After the form/report-level properties, there is exactly ONE `Begin...End` container block. Default control styles, break level definitions, and ALL sections live inside it. ### Critical Rules 1. **TabIndex values MUST be contiguous within each section (0, 1, 2, 3...).** Gaps cause `Error 2128` on import. When removing a control, renumber all subsequent TabIndex values in that section downward to fill the gap. This applies to both forms and reports. 2. **VBA event procedures MUST be wired up in the `.bas` file.** Adding a `Private Sub Form_Load()` (or any event) to the `.cls` code-behind is **not sufficient** — Access will silently ignore it. You must also add the corresponding event property in the `.bas` layout file. The property goes on the **object that owns the event**: - **Form-level events** (`Form_Load`, `Form_Current`, `Form_Open`, etc.) → property on the form object itself (top-level, before sections): `OnLoad ="[Event Procedure]"`, `OnCurrent ="[Event Procedure]"`, etc. - **Report-level events** (`Report_Open`, `Report_Load`, etc.) → property on the report object: `OnOpen ="[Event Procedure]"`, `OnLoad ="[Event Procedure]"`, etc. - **Control events** (`cmdOpen_Click`, `txtName_AfterUpdate`, etc.) → property on the control block: `OnClick ="[Event Procedure]"`, `AfterUpdate ="[Event Procedure]"`, etc. - **Section events** (`Detail_Format`, `FormHeader_Click`, etc.) → property on the section block: `OnFormat ="[Event Procedure]"`, `OnClick ="[Event Procedure]"`, etc. The property name maps from the VBA event name by dropping the object prefix and using the `On` + PascalCase form. Common mappings: | VBA Event Sub | `.bas` Property | |---|---| | `Form_Load` | `OnLoad ="[Event Procedure]"` | | `Form_Current` | `OnCurrent ="[Event Procedure]"` | | `Form_Open` | `OnOpen ="[Event Procedure]"` | | `Form_Close` | `OnClose ="[Event Procedure]"` | | `Form_BeforeUpdate` | `BeforeUpdate ="[Event Procedure]"` | | `Form_AfterUpdate` | `AfterUpdate ="[Event Procedure]"` | | `Report_Open` | `OnOpen ="[Event Procedure]"` | | `cmdButton_Click` | `OnClick ="[Event Procedure]"` (on the control) | | `txtField_AfterUpdate` | `AfterUpdate ="[Event Procedure]"` (on the control) | **Without this property, the VBA code compiles but never executes.** 3. **Never edit binary data blocks.** Leave `ImageData = Begin...End`, `RecSrcDt = Begin...End`, and other hex-encoded blocks untouched. Modifying these corrupts the object. For `ConditionalFormat` and `ConditionalFormat14` blocks specifically, see the **Conditional Formatting** section — these must be removed entirely and replaced with VBA code. 4. **Preserve Begin/End nesting exactly.** Every `Begin` must have a matching `End`. Mismatched nesting causes import failure. 5. **Control order matters.** Controls are rendered in file order (affects z-order). Don't reorder controls unless intentionally changing layering. 6. **Property values use specific formats:** - Numeric: `Width =1701` (units are twips: 1 inch = 1440 twips) - String: `Name ="Detail"` (always double-quoted) - Boolean flags: presence of property name implies true (e.g., `NotDefault`) - **String escaping uses backslash (`\"`), NOT VBA-style doubled quotes (`""`).** This is the binary text format's own convention and differs from VBA code in `.cls` files. Example — a `DLookUp` ControlSource: ``` ControlSource ="=DLookUp(\"Description\",\"vwMyView\",\"Id=\" & [Id])" ``` Using `""` instead of `\"` will cause the value to be parsed incorrectly on import, corrupting the expression. Always check existing `.bas` files for examples when unsure. 7. **When removing a control block,** remove everything from `Begin ControlType` through its closing `End`, including any trailing `LayoutCached*` properties and `PictureCaptionArrangement` that appear between the inner `End` (closing ImageData) and the outer `End` (closing the control). A complete control block looks like: ``` Begin CommandButton <- Start of control Name ="btnExample" Caption ="Click Me" TabIndex =2 ImageData = Begin <- Inner Begin/End for binary data 0x... End <- Closes ImageData LayoutCachedLeft =226 <- Layout cache (part of this control) LayoutCachedTop =113 LayoutCachedWidth =802 LayoutCachedHeight =689 PictureCaptionArrangement =5 End <- Closes CommandButton ``` 8. **Recalculate form width when adding or modifying controls.** After adding, removing, or resizing controls in a form or report, recalculate the form/report `Width` property to equal the rightmost edge of any control (`Left + Width`). Update the `LayoutCachedWidth` of any affected controls and their associated header/footer labels to match. **If the form is used as a subform**, also update the parent/master form: - Find the `Begin Subform` block in the parent form that references this form via `SourceObject` - Calculate the **subform control width** = child form `Width` + UI chrome (see below) - Update the subform control's `Width` and `LayoutCachedWidth` to the calculated value - If the calculated value exceeds the parent form's `Width`, increase the parent form's `Width` to fit **How to find the parent form:** Search all `.bas` files in `forms/` for `SourceObject ="Form."` to locate the master form containing the subform control. **Subform UI chrome:** The child form's `Width` property is only the content area. The subform control in the parent must also account for UI chrome that sits outside the content area. Check the child form's properties and add: | Chrome Element | Condition (on child form) | Approximate Width | |---|---|---| | Record selector | `RecordSelectors = NotDefault` is **absent** (default = visible) | ~340 twips | | Vertical scrollbar | `ScrollBars` is absent (default = 3/Both) or set to 2 or 3 | ~300 twips | | Borders | Default border style | ~60 twips (30 per side) | Formula: `subform control Width = child form Width + record_selector + scrollbar + borders` Example — after shrinking `txtField` from 3535 to 2828 twips at `Left =21465`: - New right edge = 21465 + 2828 = 24293 - Set child form `Width =24293` - Set `txtField` `LayoutCachedWidth =24293` - Set associated header label `Width =2828`, `LayoutCachedWidth =24293` - Child form has record selectors (on) + scrollbar (default/both) + borders: 340 + 300 + 60 = 700 - In parent form: set subform control `Width =24993`, `LayoutCachedWidth =24993` - In parent form: set form-level `Width =24993` (if new value exceeds current width) 9. **Subform default style blocks have restricted properties.** In the default control styles area (inside the container), the `Begin Subform...End` block must only contain layout/border properties. **Do NOT include font properties** — they don't apply to Subform controls and cause `This property does not apply to this control` on import. Valid Subform default style properties: `BorderLineStyle`, `Width`, `BorderColor`, `Height`, `Left`, `Top`. Invalid (will cause import error): `FontSize`, `FontName`, `FontWeight`, `ForeColor`, `FontItalic`, `FontUnderline`. Correct: ``` Begin Subform BorderLineStyle =0 Width =1701 BorderColor =12632256 End ``` Wrong (causes error): ``` Begin Subform BorderLineStyle =0 Width =1701 FontSize =11 <- ERROR: not applicable to Subform BorderColor =12632256 FontName ="Calibri" <- ERROR: not applicable to Subform End ``` ## Conditional Formatting (Forms & Reports) `ConditionalFormat` and `ConditionalFormat14` are hex-encoded binary properties that appear on form and report controls (TextBox, ComboBox, etc.) when conditional formatting rules have been set via the Access designer. Example: ``` Begin TextBox ... ConditionalFormat = Begin 0x0100000... End ConditionalFormat14 = Begin 0x0100000... End ... End ``` ### Decoding ConditionalFormat Binary Blocks The binary data inside `ConditionalFormat` and `ConditionalFormat14` blocks follows a structured format. When you encounter an existing block, attempt to decode it to understand the rules before removing it. `ConditionalFormat14` is the modern (Access 2007+) variant with the same logical structure but different header/version bytes. #### Header Structure | Offset | Size | Field | Notes | |--------|------|-------|-------| | 0x00 | DWORD | Version | Format version identifier | | 0x04 | DWORD | Total size | Total byte count of the binary block | | 0x08 | DWORD | Condition count | Number of condition records that follow | #### Per-Condition Record Fields Each condition record contains: **Condition type** (byte): | Value | Type | |-------|------| | `0x00` | Field Value | | `0x01` | Expression | | `0x02` | Field Has Focus | **Operator** (byte, applicable when condition type = Field Value): | Value | Operator | |-------|----------| | `0x00` | Between | | `0x01` | Not Between | | `0x02` | Equal To | | `0x03` | Not Equal To | | `0x04` | Greater Than | | `0x05` | Less Than | | `0x06` | Greater Than Or Equal | | `0x07` | Less Than Or Equal | **Formatting flags:** Bold, Italic, Underline (each as a flag/byte). **BackColor** (background fill): COLORREF as little-endian DWORD (`0x00BBGGRR`). **ForeColor** (font colour): COLORREF as little-endian DWORD (`0x00BBGGRR`). **Expression text:** UTF-16LE encoded string (for Expression-type conditions or Field Value comparison values). **Sort/order flag:** Possible ordering byte for rule evaluation precedence. #### "Compare to Other Records" (Data Bars) Data bar conditions include additional fields: | Field | Description | |-------|-------------| | Show Bar Only | Flag indicating whether to hide the value and show only the bar | | Shortest Bar | Value representing the minimum bar length | | Longest Bar | Value representing the maximum bar length | | Type | Data bar type identifier | | Value | Data bar value reference | | Color | Bar fill colour as COLORREF | ### Rules: Decoding Allowed, Encoding Banned **Decoding is allowed:** When encountering existing binary blocks, attempt to decode and document the rules found (condition type, operator, expressions, colours, formatting). If decoding fails or is ambiguous, ask the user to clarify. **Encoding/copying is still banned:** Never manually construct, modify, or copy binary `ConditionalFormat` or `ConditionalFormat14` hex blocks. All conditional formatting must be applied programmatically using VBA via the `FormatConditions` collection. ### Workflow: Modifying Conditional Formatting on a Control 1. **Decode** the existing binary block to extract the current rules (condition types, operators, expressions, colours, formatting flags). If decoding fails, ask the user to provide the rules. 2. **Document** the extracted rules (conditions, colours, formatting) so nothing is lost. 3. **Delete** the binary `ConditionalFormat = Begin...End` and `ConditionalFormat14 = Begin...End` blocks from the `.bas` file entirely. 4. **Recreate** using VBA `FormatConditions` collection in the `.cls` code-behind (see patterns below). 5. **Then** make any additional modifications the user requested (adding new rules, changing colours, etc.). ### Workflow: Copying Conditional Formatting to Another Control 1. **Decode** the source control's binary block to extract the rules (or ask the user if decoding fails). 2. **Delete** the source binary block (optional — if converting the source control to VBA too). 3. **Create** the VBA `FormatConditions` on the target control using the decoded rules in the `.cls` code-behind. ### VBA Patterns for Conditional Formatting #### Choosing the Right Event | Form/Report Type | Event | Why | |---|---|---| | **Single-record form** | `Form_Current` | Fires each time a record gets focus | | **Continuous/Datasheet form (list form)** | `Form_Load` | Rules are set once; Access auto-evaluates them across all visible rows. `Form_Load` fires after the recordset is loaded but before display. | | **Report** | `Report_Open` | Rules are set once before the report renders | **Important for list forms:** `FormatConditions` are rule-based, not row-by-row — Access re-evaluates them automatically for every visible row. You only need to set them up once. However, certain actions **destroy** format conditions and require re-applying them: - `Me.Requery` or `Me.RecordSource = ...` (rebuilds the form's recordset) - `DoCmd.ApplyFilter` in some contexts - Any operation that effectively recreates the controls For list forms that requery or change RecordSource, call `ApplyConditionalFormats` after those operations as well. #### List Form Pattern (Continuous/Datasheet) ```vba Private Sub Form_Load() ApplyConditionalFormats End Sub Private Sub ApplyConditionalFormats() ' Clear any existing format conditions Me.txtFieldName.FormatConditions.Delete ' Add conditions (up to 50 in Access 2007+) ' Type 0 = acFieldValue, Type 1 = acExpression ' Example: Field value > 100 — red background With Me.txtFieldName.FormatConditions.Add(acFieldValue, acGreaterThan, 100) .BackColor = RGB(255, 200, 200) .ForeColor = RGB(139, 0, 0) End With ' Example: Expression-based — bold green when above target With Me.txtFieldName.FormatConditions.Add(acExpression, , "[Amount] > [Target]") .BackColor = RGB(200, 255, 200) .FontBold = True End With End Sub ' If the form requeries, re-apply after the requery Private Sub btnRefresh_Click() Me.Requery ApplyConditionalFormats End Sub ``` #### Single-Record Form Pattern ```vba Private Sub Form_Current() ApplyConditionalFormats End Sub Private Sub ApplyConditionalFormats() Me.txtStatus.FormatConditions.Delete With Me.txtStatus.FormatConditions.Add(acFieldValue, acEqual, "Overdue") .BackColor = RGB(255, 200, 200) .ForeColor = RGB(139, 0, 0) .FontBold = True End With End Sub ``` #### Report Pattern ```vba Private Sub Report_Open(Cancel As Integer) ApplyConditionalFormats End Sub Private Sub ApplyConditionalFormats() Me.txtTotal.FormatConditions.Delete With Me.txtTotal.FormatConditions.Add(acFieldValue, acGreaterThan, 10000) .BackColor = RGB(255, 255, 200) .FontBold = True End With End Sub ``` ### Why This Matters - Hex blobs are **not diffable** — changes are invisible in code review - They **cannot be merged** — any conflict requires discarding one side entirely - They **cannot be hand-edited** — a single byte error corrupts the control - VBA-based formatting is **readable, diffable, and mergeable** ## Queries **`.bas` metadata file** contains: - `Operation` flag (1 = SELECT, etc.) - `InputTables`, `OutputColumns`, `Joins` blocks for the visual designer - Designer window coordinates at end of file (don't modify) - Pass-through queries: `dbMemo "Connect"` and `dbMemo "SQL"` properties **There must be at most one `Begin Joins` block per query `.bas` file.** When a query has multiple joins, list all join definitions inside a single `Begin Joins...End` block — do NOT use separate `Begin Joins` blocks for each join. A second `Begin Joins` block causes a fatal import error: ``` Error encountered at line N. Expected: End of file. Found: Joins. ``` Correct (multiple joins in one block): ``` Begin Joins LeftTable ="tOrders" RightTable ="tCustomers" Expression ="tOrders.CustomerID = tCustomers.ID" Flag =1 LeftTable ="tOrders" RightTable ="tOrderItems" Expression ="tOrders.ID = tOrderItems.OrderID" Flag =1 End ``` Wrong (separate blocks — causes import error): ``` Begin Joins LeftTable ="tOrders" RightTable ="tCustomers" Expression ="tOrders.CustomerID = tCustomers.ID" Flag =1 End Begin Joins LeftTable ="tOrders" RightTable ="tOrderItems" Expression ="tOrders.ID = tOrderItems.OrderID" Flag =1 End ``` **`.sql` file** contains the actual SQL. For pass-through queries, this is the T-SQL sent to SQL Server. Both files must stay synchronized. Editing `.sql` alone may not be sufficient if the `.bas` designer metadata contradicts it. ### `dbMemo "SQL"` and `SaveQuerySQL` Interaction **When `SaveQuerySQL=true`** in `vcs-options.json` (the recommended setting), VCS reads query SQL exclusively from the paired `.sql` file during import. The `.bas` file must **NOT** contain a `dbMemo "SQL"` property. Including it causes a fatal import error: ``` Error encountered at line N. Could not create or set the property SQL. ``` This happens because VCS sets the SQL property from the `.sql` file first, then the `.bas` file tries to set it again via `dbMemo "SQL"`, and Access rejects the duplicate assignment. **Rules:** - **`SaveQuerySQL=true`** (default/recommended): Never include `dbMemo "SQL"` in the `.bas` file. The `.sql` file is the single source of truth for the SQL text. - **`SaveQuerySQL=false`**: The `.bas` file **must** contain `dbMemo "SQL"` because there is no `.sql` file. - **Pass-through queries**: Always use `dbMemo "SQL"` in the `.bas` file (pass-through SQL is not split to `.sql` regardless of the setting). Also include `dbMemo "Connect"` for the ODBC connection string. ## VBA Modules - Start with `Attribute VB_Name = "ModuleName"` - Use `#If VBA7 Then` / `#Else` / `#End If` for API declarations (required for 64-bit Access) - `Option Compare Database` is standard ## VBA Code-Behind Files (.cls) Form and report code-behind files (`.cls`) are created when `SplitLayoutFromVBA=true`. These files must **NOT** include the `VERSION 1.0 CLASS` header block or `Attribute VB_Name`. VCS handles class registration internally during import — including these causes import errors. **Correct `.cls` format:** ```vba Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = True Attribute VB_PredeclaredId = True Attribute VB_Exposed = False Option Compare Database Private Sub cmdOpen_Click() DoCmd.OpenForm "fDetail" End Sub ``` **Wrong — do NOT include:** ```vba VERSION 1.0 CLASS <- WRONG: remove this entire block BEGIN <- WRONG MultiUse = -1 'True <- WRONG END <- WRONG Attribute VB_Name = "Form_fMyForm" <- WRONG: remove this line Attribute VB_GlobalNameSpace = False ... ``` The `VERSION 1.0 CLASS` block and `Attribute VB_Name` are standard VBA class file headers used by the VB editor, but VCS strips them on export and does not expect them on import. ## ODBC Connection String Consistency **All linked tables and pass-through queries in a project must use exactly the same ODBC connection string.** Mixed connection strings cause Access to open multiple ODBC connections to the same server, wasting resources and causing unpredictable authentication behaviour. Rules: 1. The canonical connection string is defined in `db-connection.json`. All linked tables and pass-through queries must use this exact string. 2. When creating or editing a linked table definition, set `"Connect"` to the canonical string. 3. When creating or editing a pass-through query, set `dbMemo "Connect"` to the canonical string. 4. Never embed user-specific credentials (`UID=`, `PWD=`), per-user DSN variations, or protocol overrides (`Network=`) in connection strings. 5. `db-connection.json` must contain only one entry matching the canonical connection string. ## Linked Table Definitions (tbldefs/*.json) ```json { "Info": {"Class": "clsDbTableDef", "Description": "Linked Table"}, "Items": { "Name": "tTableName", "Connect": "ODBC;DSN=...;", "SourceTableName": "dbo.tTableName", "Attributes": 536870912, "PrimaryKey": "[ID]" } } ``` - `Attributes: 536870912` (0x20000000) = linked ODBC table - DSN names are case-sensitive in connection strings - `SourceTableName` uses the format `schema.tablename` (e.g., `dbo.tMyTable`) - `PrimaryKey` uses bracket notation (e.g., `[ID]`, `[CompoundKey1];[CompoundKey2]`) ## Local Table Definitions (tbldefs/*.xml) Local tables (stored inside the .accdb, not linked to SQL Server) are exported as XSD schema files: ```xml ``` - Common `od:jetType` values: `autonumber`, `text`, `longinteger`, `datetime`, `yesno`, `currency`, `double`, `memo` - Local tables are typically used for temporary/working data (e.g., `tTEMP_*`, `tSelect_*`), lookup data, and system tables - **Do not edit these unless necessary** — schema changes are usually made in SQL Server for linked tables ### Table Creation Rules When creating new local tables, always follow these conventions: 1. **Every table MUST have an `ID` column as primary key** — `autonumber` type, non-nullable, auto-incrementing integer. This is the standard Access pattern for surrogate keys. 2. **Every foreign key column MUST have a corresponding relationship file** in the `relations/` directory. For example, if `tOrderItems` has a `OrderID` column referencing `tOrders.ID`, create a relationship file `relations/tOrderstOrderItems.json`. 3. **Foreign key columns** should be named `ID` (without the `t` prefix) — e.g., `CustomerID` references `tCustomers.ID`, `OrderID` references `tOrders.ID`. ## Relationships (relations/*.json) Table relationships define foreign key constraints, referential integrity, and join types between tables. Each relationship is a `.json` file in the `relations/` directory. ### File Naming Convention The filename concatenates the parent table name and child table name: `.json` Example: `tOrderstOrderItems.json` (relationship from `tOrders` to `tOrderItems`) For relationships with custom join types, append a suffix: ` - join type N.json` ### Relationship Format ```json { "Info": { "Class": "clsDbRelation", "Description": "Database relationship" }, "Items": { "Name": "tOrderstOrderItems", "Table": "tOrders", "ForeignTable": "tOrderItems", "Attributes": 2, "Fields": [ { "Name": "ID", "ForeignName": "OrderID" } ] } } ``` - `Name`: Relationship name (typically ``) - `Table`: The **parent** (primary key) table - `ForeignTable`: The **child** (foreign key) table - `Fields`: Array of field mappings. `Name` = field in parent table, `ForeignName` = field in child table - `Attributes`: Controls referential integrity and join type (see below) ### Attributes Reference The `Attributes` value controls referential integrity enforcement and join type: | Attributes | Referential Integrity | Join Type | Description | |---|---|---|---| | `2` | No | Inner Join (default) | No enforcement — allows orphan records. Default for simple relationships without referential integrity. | | `0` | Yes | Inner Join (default) | Enforced — prevents orphan records. Access rejects inserts/updates that violate the relationship. | | `16777216` | Yes | Left Outer Join | Enforced. Include ALL records from the parent table (`Table`) and only matching records from the child table (`ForeignTable`). | | `33554432` | Yes | Right Outer Join | Enforced. Include ALL records from the child table (`ForeignTable`) and only matching records from the parent table (`Table`). | **When to use each:** - `Attributes: 2` — Loose relationship, no data integrity checks. Use for optional or legacy associations. - `Attributes: 0` — Standard enforced relationship. Use for required foreign keys (e.g., every order must have a valid customer). - `Attributes: 16777216` — Left join. Use when the parent record may not have any child records but you still want to see it (e.g., show all customers, even those without orders). - `Attributes: 33554432` — Right join. Use when the child record's parent may be missing but you still want to see it (e.g., show all order items, even if the order header is incomplete). ### Compound Key Relationships For tables with composite foreign keys, add multiple entries to the `Fields` array: ```json "Fields": [ {"Name": "CompanyID", "ForeignName": "CompanyID"}, {"Name": "BranchID", "ForeignName": "BranchID"} ] ``` ## ComboBox Controls ### BoundColumn `BoundColumn` in `.bas` files is **0-based** — this differs from the Access UI and VBA, which display it as **1-based**. A `.bas` value of `0` corresponds to column 1 in the UI. This offset is a common source of bugs. **Example:** A ComboBox with `RowSource` returning `ID, CustomerName`: ``` ColumnCount =2 ColumnWidths ="0;3000" BoundColumn =0 ``` - Column 0 = `ID` (hidden by `ColumnWidths ="0;..."`) - Column 1 = `CustomerName` (displayed, width 3000 twips) - `BoundColumn =0` → stores the `ID` value in `ControlSource` **Common mistake:** Setting `BoundColumn =1` when the intent is to store the ID. In the `.bas` file, `1` means the **second** column (the display name), not the first. This stores the display name instead of the foreign key, causing data integrity issues. **Always verify that `BoundColumn` points to the primary key column (usually column 0 in `.bas`), not the display column.** **Rule:** When using a multi-column ComboBox as a foreign key lookup: 1. The first column should be the ID/key field 2. Set `ColumnWidths` to hide the ID column: `"0;3000"` (zero width hides it) 3. Set `BoundColumn =0` to store the ID value (0-based in `.bas` files) 4. Set `ControlSource` to the foreign key field name ## List/Detail Form Navigation Pattern A common Access pattern is a list form (continuous/datasheet) that opens a detail form for add/edit, then needs to refresh when the user returns. The list form's data must be requeried to show changes made in the detail form. ### Pattern: Requery on Return from Detail Form **List form (`.cls`)** — opens the detail form for viewing/editing: ```vba Private Sub cmdOpen_Click() If Not IsNull(Me.txtID) Then DoCmd.OpenForm "fCustomerDetail", , , "ID = " & Me.txtID End If End Sub Private Sub cmdAdd_Click() DoCmd.OpenForm "fCustomerDetail", , , , acFormAdd End Sub ``` **Detail form (`.cls`)** — saves/discards and requeries the calling list form: ```vba Private Sub cmdSaveClose_Click() If Me.Dirty Then Me.Dirty = False RequeryCallingForm DoCmd.Close acForm, Me.Name End Sub Private Sub cmdDiscardClose_Click() Me.Undo DoCmd.Close acForm, Me.Name End Sub Private Sub RequeryCallingForm() ' Requery the list form so it reflects changes Dim frm As Form For Each frm In Forms If frm.Name <> Me.Name Then frm.Requery End If Next frm End Sub ``` Alternatively, use a **shared module** function for reusable save/close logic: ```vba ' In modNavigation.bas: Public Sub SaveAndCloseForm() Dim frmName As String frmName = Screen.ActiveForm.Name With Screen.ActiveForm If .Dirty Then .Dirty = False End With ' Requery all other open forms Dim frm As Form For Each frm In Forms If frm.Name <> frmName Then frm.Requery End If Next frm DoCmd.Close acForm, frmName End Sub ``` ### Wiring Up Events in `.bas` Remember that the `.bas` file must have the event properties wired: ``` OnClick ="[Event Procedure]" ``` on each button control (see Critical Rule 2). Without this, the VBA event handlers will never fire. ### Key Points - **Always requery the list form** after saving in the detail form, otherwise the list shows stale data - The requery should happen **before** closing the detail form (while the calling form is still accessible) - `Me.Dirty = False` forces a save of the current record before closing - `Me.Undo` discards unsaved changes before closing - If the list form uses conditional formatting via VBA (`FormatConditions`), re-apply it after requery (see Conditional Formatting section) ## Macros (macros/*.bas) Access macros (not VBA) use a simplified binary text format: ``` Version =196611 ColumnsShown =8 Begin Action ="Maximize" End ``` Multi-action macro: ``` Version =196611 ColumnsShown =8 Begin Action ="RunCode" Argument ="MyFunction() " End Begin Action ="Maximize" End ``` - `Version =196611` is standard for Access 2007+ - `ColumnsShown =8` is the default designer column visibility - Each `Begin...End` block is one macro action - Common actions: `Maximize`, `RunCode`, `OpenForm`, `OpenReport`, `Close`, `Requery` - `Argument` provides parameters for the action (function calls need trailing space before closing quote) ## Shared Images (images/*.json + image file) Image gallery items used by forms and reports: ```json { "Info": {"Class": "clsDbSharedImage", "Description": "Shared Image Gallery Item"}, "Items": { "Name": "MyLogo", "FileName": "MyLogo.PNG", "Extension": "PNG", "ContentHash": "b26e789" } } ``` - The `.json` metadata file must be paired with the actual image file in the same directory - `FileName` must match the actual image file name exactly (case-sensitive) - `ContentHash` is a short SHA256 hash of the image content (auto-generated by VCS) - Referenced in forms/reports via the image gallery — typically used for logos and icons ## Import/Export Specifications (imexspecs/*.json) Define column mappings for importing/exporting data files: ```json { "Info": {"Class": "clsDbImexSpec", "Description": "Import/Export Specification from MSysIMEXSpecs"}, "Items": { "DateDelim": "/", "FieldSeparator": ",", "FileType": 0, "SpecType": 0, "StartRow": 1, "TextDelim": "\"", "Columns": { "ColumnName": { "Attributes": 0, "DataType": 10, "IndexType": 0, "SkipColumn": false, "Start": 0, "Width": 20 } } } } ``` - `FileType`: `0` = delimited text, `1` = fixed width - `SpecType`: `0` = import, `1` = export - `DataType` values: `1` = Boolean, `4` = Long, `5` = Currency, `7` = Date/Time, `10` = Text ## VCS Configuration Files | File | Purpose | Safe to Edit | Required | |------|---------|-------------|----------| | `vcs-options.json` | VCS addon configuration | Rarely | Yes | | `vcs-index.json` | Build metadata, file hashes | Yes, but regenerated on rebuild | Yes | | `project.json` | Database file format | Rarely | Yes | | `vbe-project.json` | VBE project metadata (name, filename) | Yes | Yes | | `vbe-references.json` | VBA library GUIDs + versions | Carefully (GUID must match installed libs) | Yes | | `dbs-properties.json` | Database properties (startup form, etc.) | Yes | Yes | | `db-connection.json` | ODBC connection cache for import | Yes | Yes (if linked tables) | | `documents.json` | Document properties (author, title) | Yes | No | | `nav-pane-groups.json` | Navigation pane custom groups | Yes | No | | `hidden-attributes.json` | Hidden object flags | Yes | No | See the **Configuration File Reference** in the Project Structure section for full schemas and templates of each file. When removing objects, also clean their entries from: `vcs-index.json`, `db-connection.json`, `hidden-attributes.json`. ## Safe Deletion Checklist Before deleting any Access object: 1. **Search for references** in all `.cls`, `.bas`, `.sql` files. Objects may be referenced by: - VBA code (`DoCmd.OpenForm`, `DoCmd.OpenReport`, `DoCmd.OutputTo`) - Query SQL (`FROM tableName`, subreport `SourceObject`) - Form RecordSource properties - DLookup expressions 2. **Check for name collisions.** Objects with similar names may serve completely different purposes (e.g., `rInvoice_EXTERNALSYSTEM` = active invoice report vs `EXTERNALSYSTEM_Accounts` = dead integration table). 3. **Delete all paired files** (.bas + .sql, .bas + .cls + .json, etc.) 4. **Remove from config files:** vcs-index.json, db-connection.json, hidden-attributes.json 5. **Renumber TabIndex** values in any form/report section where a control was removed 6. **Test import** in Access after changes ## Common Pitfalls - **"EXTERNALSYSTEM-style" naming traps:** Object names may contain legacy prefixes that suggest a connection to a subsystem, but the objects are actually part of a completely different, active feature. Always verify by tracing references before removing. - **Commented-out code referencing kept objects:** Leave commented VBA lines alone if they reference objects you're keeping. - **Guard clauses in VBA:** Functions that skip objects by name prefix (e.g., `If Left$(name, 4) <> "EXTERNALSYSTEM"`) become unnecessary after removing those objects. Remove the guards to avoid confusion. - **db-connection.json:** Must contain only one connection entry matching the canonical ODBC connection string used by all linked tables and pass-through queries. Never introduce plaintext passwords or user-specific credentials. - **UTF-8 BOM:** VCS config files use UTF-8 with BOM. Preserve the BOM when editing.