Who you are. user.name is the single source of truth for the caseworker name and is mirrored to the top-level caseworkerName automatically on export.
Every shared filesystem location. ~ and $HOME both expand. filingSubdir is a subdir name only (joined onto openCasesDir).
The drafting solicitor's biography for the statement tool's *-rep / reps variants. Inlined at compose time: not a merge token.
Static identity fields for Staff Setup Email signature. Personal details already in user.* (name, job, phone, email) are not duplicated here.
The org register read (read-only) by the Database tool via register.*. Offices & locations are {id, name, addr}; caseworkers & supervisors are names (supervisors should be a subset of caseworkers).
gmiau-cases menu key script filename (resolved under scriptsDir). camelCase keys; the CLI loader maps them to snake_case.
A generated naming._reference block (if present in the imported file) is preserved verbatim, it is not edited here and is regenerated by the CLI from naming.yaml.
Each stage holds CODE|Description entries, one per line.
Used by the GMIAU Tools launcher. pinned is an ordered list of tool ids (slug minus .html), one per line.
Per-bundle-type tramline title (auto-uppercased on export). Blank = fall back to the shipped default (shown as placeholder).
Per-bundle-type index Sections + auto-routing keywords. This is a nested structure normally maintained from Bundle Builder's sidebar (βSave as defaultβ); edit the raw JSON here only if you know the shape. Must be a valid JSON object, blank removes it.
Configuration Editor is a visual editor for the GMIAU βOne Fileβ: Tools-GMIAU-RB.conf at ~/Work/Config/. That single JSON file is the shared source of truth every GMIAU tool reads: the CLI letter / statement / gmiau-cases scripts and the browser webtools all pull caseworker name, paths, fonts, bundle settings and the org register from it.
Tools-GMIAU-RB.conf. The whole file loads into the form.Importing keeps the entire parsed file in memory; Export starts from that copy and only overwrites the fields the form manages. Anything the form doesn't show, the generated cases.naming._reference catalogue, any future additive keys: rides along untouched. Unknown keys are preserved per the spec's merge-with-spread rule.
New resets the form to sensible GMIAU defaults so you can build a config from scratch (e.g. for a second caseworker, change the Save as filename to Tools-GMIAU-XY.conf). If you click Export without importing first, the tool warns you, because writing then would lack any CLI-only sections already on disk.
Entirely in your browser: nothing is sent anywhere. The file picker reads and writes a local file you choose. The imported config is also cached for other GMIAU tools opened on the same hostname.
# CONFIG-SPEC.md
**Status:** v1.0: drafted 2026-05-09 in the One File consolidation session.
**Companion:** `~/Notes/03 Resources/Reference/Letter Reference.md` (CLI side); `~/Notes/00 Inbox/GMIAU config consolidation - migration plan.md` (provenance).
## 1. Purpose
Single source of truth for cross-tool GMIAU configuration. Replaces the pre-2026-05-09 multi-file layout (per-tool `.conf` / `.json` files in `~/Config/gmiau/` and elsewhere). Every GMIAU tool that needs a shared fact, caseworker name, output PDF font, paths to active cases or signatures, bundle titles, party-label style, reads it from this one file.
## 2. File
| Property | Value |
|---|---|
| Location | `~/Work/Config/GMIAU-{tool-or-tools}-{user-init-slug}.conf` |
| Canonical filename for the user-of-record | `Tools-GMIAU-RB.conf` |
| Format | JSON (despite the `.conf` extension, the extension groups the file visually with the rest of the GMIAU `.conf`-suffixed assets in the Config dir) |
| Encoding | UTF-8, no BOM |
| Newlines | `\n` |
| Stored in | Nextcloud, survives reinstalls; syncs across machines |
Future multi-user support: the `-RB` slug is generated from the caseworker's initials (or chosen short id). Two caseworkers on the same workstation would each carry `Tools-GMIAU-RB.conf` and `GMIAU-tools-XY.conf`. Tools resolve which file to load by combining the directory with the running user's slug, currently hardcoded to `RB`.
## 3. Schema versioning
```jsonc
{ "schema": "gmiau-config/1", β¦ }
```
- **`gmiau-config/1`** is current. The 2026-05-09 consolidation went *additive within /1* rather than bumping to `/2`, because the legacy webtool import check is strict equality (`parsed.schema !== 'gmiau-config/1'` rejects). Adding new top-level sections (`user`, `paths`, `cases`) was tolerated by the existing `{...defaults, ...parsed}` merge, they ride along as extras.
- **Bumping to `/2`** is deferred until all webtools accept both versions in their import path. When ready: writers emit `/2`, readers accept either, after one stable cycle the `/1` codepath retires.
- **Tools MUST reject unknown major versions** (anything outside `/1` and `/2` once promoted). Refusing > silently misreading.
## 4. Top-level structure
```jsonc
{
"schema": "gmiau-config/1", // Β§3, required
//: Identity: "user": { // Β§5: caseworker
"name": "Ryan Bestford",
"jobTitle": "Solicitor",
"telephone": "0161 769 8821",
"email": "ryan@gmiau.org",
"signatureImagePath": "$HOME/Work/Signatures/Ryan_Bestford_Signature.png",
"signatureWidthCm": 4.0,
"signatureHeightCm": 1.5
},
"caseworkerName": "Ryan Bestford", // Β§5, back-compat mirror of user.name
//: Filesystem: "paths": { // Β§6: every shared filesystem location
"gmiauRoot": "~/GMIAU",
"openCasesDir": "$HOME/Work/00 Open Cases",
"filingSubdir": "Filing",
"caseSummariesDir": "$HOME/Work/00 Open Cases/Case Summaries",
"signaturesDir": "$HOME/Work/Signatures",
"downloadsDir": "~/Downloads",
"scriptsDir": "~/Scripts/tools"
},
//, gmiau-cases CLI state: "cases": { // Β§7
"scripts": { /* menu key script filename (camelCase keys) */ },
"settings": { "gapDays": 80, "closingDays": 120 },
"naming": { /* see Β§7.3 */ },
"review": { /* see Β§7.4 */ }
},
//, Email-signature defaults (Staff Setup Email signature), "signature": { // Β§5A
"pronouns": "he/him",
"sraId": "386471",
"sraUrl": "https://solicitors.lawsociety.org.uk/person/386471",
"iaaNo": "N201000004",
"pgp": "9C6B44DAD0BD56249DFA8D82C598432B19A829B9",
"instagram": "@gmiaunit",
"bluesky": "@gmiau.bsky.social",
"workNote": "",
"workingHours": {
"start": "9:30 am",
"end": "5:30 pm",
"days": ["Mon", "Tue", "Wed", "Thu", "Fri"]
},
"sectionsInclude": [
"secure", "referrals", "newsletter", "social",
"announcement", "important", "badges", "disclaimer"
]
},
//, Webtool brand / output settings: "preparerDefault": "GMIAU", // Β§8.1
"preparedByFooter": true, // Β§8.2
"bundleNumbering": "continuous", // Β§8.3
"partyConjunction": "v", // Β§8.4
"pdfFont": "Helvetica", // Β§8.5
"bundleTitles": { /* Β§8.6 */ },
"bundleProforma": { /* Β§8.7 */ },
"partyLabelStyle": { "bold": true, "underline": true } // Β§8.8
}
```
Both `~` and `$HOME`/`$USER` env-var expansion are supported in path-shaped strings. Loaders MUST expand both (`os.path.expandvars(os.path.expanduser(s))` in Python; equivalent JS).
## 5. `user` section + `caseworkerName` mirror
| Key | Type | Purpose |
|---|---|---|
| `user.name` | string | Caseworker's display name. SoT for who-you-are. Fed into PDF Author metadata, email signatures, letter sign-off, gmiau-cases prompts. |
| `user.jobTitle` | string | "Solicitor", "Senior Caseworker", etc. Used in letter sign-off. |
| `user.telephone` | string | Direct line. Letter sign-off. |
| `user.email` | string | Caseworker email. Letter sign-off. |
| `user.signatureImagePath` | string (path) | PNG / JPG / SVG signature for letter merge. The on-disk source of truth. |
| `user.signatureImageData` | string (`data:` URI) | **Embedded copy** of the signature image (base64 `data:image/png;base64,β¦`). Added 2026-06-24 so the shared config carries the picture itself, a filesystem path is meaningless to the webtools (they can't read the caseworker's disk). Written by `cms config signature` / `cms config update`, which inline the bytes at `signatureImagePath`. Webtool signing tools read this in preference to the path. Capped at 2 MB inline. Empty/omitted = fall back to `signatureImagePath`. |
| `user.signatureWidthCm` | number | Optional override (default 4.0). |
| `user.signatureHeightCm` | number | Optional override (default 1.5; deprecated, derive from image aspect ratio). |
| `caseworkerName` | string | **Back-compat mirror of `user.name`.** Top-level for legacy webtool readers. Writers MUST keep it in sync with `user.name` until the schema is bumped. |
**Single SoT rule:** any tool displaying or writing the caseworker's name MUST read it from `user.name` (preferred) or `caseworkerName` (fallback). Tools MUST NOT carry their own copy of the name in localStorage or another file.
## 5A. `signature` section
Static identity fields used by Staff Setup Email signature. Personal
details that already live in `user.*` (name, jobTitle, telephone, email)
are NOT duplicated here, the email-signature pane reads them from there.
| Key | Type | Purpose |
|---|---|---|
| `signature.pronouns` | string | Pronouns. Either one of `"he/him"`, `"she/her"`, `"they/them"`, or any custom string. Empty/omitted = "(none)". |
| `signature.sraId` | string | SRA ID for solicitors. Empty/omitted for non-solicitors. |
| `signature.sraUrl` | string (URL) | Public Law Society profile URL keyed by `sraId`, used for both the SRA ID hyperlink in the footer and the personal accreditation badge link. |
| `signature.iaaNo` | string | IAA registration number. Defaults `"N201000004"` (org-level) when omitted. |
| `signature.pgp` | string | PGP fingerprint (40 hex) or 16-char key ID. |
| `signature.instagram` | string | IG handle, `@`-prefixed (e.g. `"@gmiaunit"`). |
| `signature.bluesky` | string | Bluesky handle (e.g. `"@gmiau.bsky.social"`). |
| `signature.workNote` | string | Free-form additional note (e.g. project shout-out). Pre-fills the pane's "Additional note" textarea. |
| `signature.workingHours.start` | string | Display time, free-form (e.g. `"9:30 am"`). |
| `signature.workingHours.end` | string | Display time, free-form. |
| `signature.workingHours.days` | string[] | Subset of `["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]`. |
| `signature.sectionsInclude` | string[] | Section keys in display order. Membership = enabled. Missing keys are disabled. Always-on sections (e.g. `badges`) MUST appear in the array. Valid keys: `secure`, `referrals`, `newsletter`, `social`, `announcement`, `important`, `badges`, `disclaimer`. |
**Read protocol**, Staff Setup's email-signature pane pre-fills these
fields from the conf on first load (no localStorage profile active).
Once a user saves a profile via the pane's `Save profileβ¦` button, the
profile takes precedence and the conf defaults are no longer applied.
**Write protocol**, Staff Setup's Config tab is the only writer. Other
tools MUST NOT mutate `signature.*`. Per-session/dynamic fields stay
out of conf: announcement title + body, accreditation badge
order/selection + custom badge images, logo image, signature mode
(Standard / Short).
`sectionsInclude` is read live from the Email-signature pane at export
time (drag-order + toggle states). The Config tab does not duplicate
the editor, edit in the pane; export from Config.
## 5B. `representative` section (documented 2026-06-24)
Drafting solicitor / representative biography. Read by the draft Statement
tool's `-rep` / reps variants to populate the representative's own witness
statement biography. All fields are strings; empty/omitted = the tool falls
back to its baked default or omits the line.
| Key | Purpose |
|---|---|
| `representative.name` | Short name. |
| `representative.fullName` | Full legal name. |
| `representative.firm` / `firmShort` | Firm name (+ short form). |
| `representative.address` | Firm address (multi-line). |
| `representative.dateOfBirth` / `placeOfBirth` / `nationality` | Personal details for the statement preamble. |
| `representative.role` | e.g. `solicitor`. |
| `representative.admittedDate` / `employedSince` | Dates for the experience paragraph. |
| `representative.regulator` / `regulatedBy` / `sraId` / `oiscNumber` | Regulatory identifiers (SRA / IAA). |
| `representative.orgType` | Organisation type. |
## 5C. `team` section (documented 2026-06-24)
Supervision tree, read by `cms contacts` (supervisee vCard export) and any tool
that needs the caseworker's supervisees.
```jsonc
"team": { "supervisees": [ { "name": "β¦", "email": "β¦" }, β¦ ] }
```
## 5D. `firm_history` section (documented 2026-06-24)
Time-versioned record of the firm's identity (name / address / registration
numbers) so documents dated to an earlier period render the firm details that
were correct *then*. Each `history` entry carries an effective-from date
(`valid_from`, null = "from the beginning") and the full firm block.
```jsonc
"firm_history": {
"schema": "β¦",
"history": [
{ "valid_from": null, "label": "β¦", "firm_name": "β¦", "address": "β¦",
"tel": "β¦", "fax": "β¦", "email": "β¦", "web": "β¦",
"company_no": "β¦", "charity_no": "β¦",
"registration_label": "β¦", "registration_no": "β¦" }
]
}
```
`team` and `firm_history` are object-shaped and edited in the webtools' Config
tab (or by hand); the `cms config` CLI editor preserves them verbatim and points
at the web editor rather than offering a flat field editor for them.
## 6. `paths` section
| Key | Default | Used by |
|---|---|---|
| `gmiauRoot` | `~/GMIAU` | gmiau-cases (root for case-management scans) |
| `openCasesDir` | `~/Work/00 Open Cases` | gmiau-cases, letter (output routing), tribunal (output routing) |
| `filingSubdir` | `Filing` | letter, tribunal, joined onto `openCasesDir` to form the shared filing inbox. **Subdir name only**, not a full path. |
| `caseSummariesDir` | `~/Work/00 Open Cases/Case Summaries` | letter, tribunal, sole source for case-summary JSON discovery (per `ref_ifyi_case_summaries_dir_sot`) |
| `signaturesDir` | `~/Work/Signatures` | letter (`--init-profile` signature path auto-suggest); webtools that pick a signature image |
| `downloadsDir` | `~/Downloads` | gmiau-cases (loose-file router source) |
| `scriptsDir` | `~/Scripts/tools` | gmiau-cases (resolve `cases.scripts` references to absolute paths) |
| `indexDb` | `~/.cache/gmiau/index.db` | gmiau-cases, SQLite case index. Point it at a Nextcloud-resident path if you want it to survive a machine wipe (otherwise it's a regenerable cache). Env override: `IFYI_INDEX_DB` (test harness uses it). Resolver: `gmiau.index_db_path()`. |
| `auditLog` | `~/.cache/gmiau/audit.jsonl` | gmiau-cases, cross-case audit log (per-case logs always live at `<case>/Archive/.audit.jsonl`). Move it onto Nextcloud to keep cross-case history across reinstalls. Env override: `IFYI_AUDIT_LOG`. Resolver: `gmiau.audit_log_path()`. |
| `reportsDir` | `~/Work/00 Open Cases/Reports` | gmiau-cases, output directory for consolidated compliance reports (HTML / ODS). |
| `database` | _unset_ | **immigrationfyi-tools launcher**, absolute path to the working `gmiau-database.gmiau-db.html` (e.g. `/var/home/rb/Work/Database/gmiau-database.gmiau-db.html`). When set, the launcher re-targets the Database tile to a `file://` URL pointing at this path instead of its built-in `offline/database-offline.html` shell. When unset, the tile uses the shell. Added 2026-05-13. |
`indexDb` / `auditLog` / `reportsDir` were added 2026-05-11. `database` was added 2026-05-13. They are deliberately the only state/cache/working-file paths exposed as knobs; `~/.config/gmiau/bank-holidays.txt` (bank-holidays cache), `<openCasesDir>/.trash/` and `<openCasesDir>/+Archive/` are fixed (derived or regenerable).
## 6A. `launcher` section (immigrationfyi-tools launcher only)
Added 2026-05-13. Used by the GMIAU Tools launcher (`gmiau-apps/immigrationfyi-tools/index.html`) only.
```jsonc
"launcher": {
"pinned": ["database", "filer", "bundle-builder", "case-summary"]
}
```
| Key | Type | Default | What it does |
|---|---|---|---|
| `pinned` | string[] | `[]` | Ordered list of tool **ids** (slug from `file:` minus `.html`, e.g. `filer`, `bundle-builder`). The launcher renders a ` Pinned` section at the top of the Tools tab containing these tiles, in the listed order. Tiles also appear in their normal section below (pinning is a "promote to top", not a "move out of section"). Unknown ids are silently ignored. |
The launcher uses a plain `<input type="file">` picker, Chromium intentionally disables `window.showOpenFilePicker` on `file://` origins, and the launcher runs as a `file://` Brave SSB. The picked file's JSON *text* is cached in `localStorage` (`ifyi_launcher_conf_text` + `ifyi_launcher_conf_name`); on every reload the launcher re-parses + re-applies it without prompting. To pick up edits to the conf file, the user re-runs ** Pick conf fileβ¦** on the launcher's Config tab. ** Forget** clears the localStorage cache and reverts to the built-in catalogue + the hard-coded `offline/database-offline.html` shell.
## 6B. `bookingLocations` + `offices` (shared reference lists)
Agreed GMIAU locations, used by tools that need a venue / office picker (e.g. the
draft Note tool's Venue field, interpreter bookings). Each entry is
`{ "name": β¦, "address": β¦ }`. Source: `260618-GMIAU-Booking-Locations.csv` /
`260618-GMIAU-Offices.csv`.
```jsonc
"bookingLocations": [
{ "name": "GMIAU", "address": "1 Delaunays Rd, Manchester, M8 4QS" },
{ "name": "Manchester IAC", "address": "Piccadilly Exchange, Manchester, M1 4AH" },
{ "name": "Remote (Signal/Teams/Zoom)", "address": "" }
// β¦ courts, hearing centres, HMP/YOI, etc.
],
"offices": [
{ "name": "GMIAU", "address": "1 Delaunays Rd, Manchester, M8 4QS" }
]
```
Tools read names via `ifyiConfigGet('bookingLocations')` (fall back to a baked
list if the config is absent, so the picker is always usable).
## 7. `cases` section (gmiau-cases-only)
This section was the legacy `~/Projects/gmiau-apps/gmiau-cases/work.conf` (INI). It moved into the One File 2026-05-09. The `gmiau.py:load_config()` JSON-aware loader translates camelCase keys back to snake_case for the existing `ConfigParser` API.
### 7.1 `cases.scripts`
```jsonc
"scripts": {
"rename": "rename-work-files.py",
"caseSetup": "case-setup.py",
"caseStage": "case-stage.py",
"compliance": "case-compliance.py",
"fixer": "case-fixer.py",
"laa": "laa-compliance.py",
"contact": "3m-compliance.py",
"filing": "filing.py",
"sequencer": "file-sequencer.py",
"ccms": "ccms-renamer.py",
"duplicates": "find-duplicates.py",
"taskBuilder": "task-builder.py"
}
```
Menu key script filename. The script lives under `paths.scriptsDir`. Loader maps camelCase JSON keys to snake_case INI keys (`caseSetup` `case_setup`).
### 7.2 `cases.settings`
```jsonc
"settings": { "gapDays": 80, "closingDays": 120 }
```
`gapDays`, contact-gap warning threshold (days). `closingDays`, closing-deadline threshold (days).
### 7.3 `cases.naming`
```jsonc
"naming": {
"fileSep": "space", // separator in document filenames
"caseSep": "space", // separator in case-folder names
"stageSep": "space", // separator in stage-folder names
"dateFormat": "YYMMDD", // YYMMDD or DDMMYY
"descriptionCase": "title", // title / upper / lower / preserve
"reports": true, // true = reports follow GMIAU naming
"capsWords": ["CCL", "OAL", "ILR", β¦], // tokens kept upper-case in title-case rendering
"_reference": { β¦ } // GENERATED, see below; do not hand-edit
}
```
`fileSep`/`caseSep`/`stageSep` accept the literal tokens `"space"` / `"underscore"` / `"hyphen"` plus the actual character. `gmiau.load_naming()` normalises.
**`cases.naming._reference`** (added 2026-05-11) is a generated, read-only mirror of the by-folder convention catalogue, for each folder type (case folder, stage folder, `LAA/`, `Documents/`, the required subfolders, the skipped folders) it lists every convention available and the recommended (`preferred`) one. Canonical source: `gmiau-specs/naming.yaml` `folders:` (enriched with human slot labels from `laa-slots.yaml`). Built by `gmiau.naming_reference()`; `gmiau.config_ensure_reference()` re-writes it whenever it drifts (called on `work.py` launch), so it can't get stale. It's a documentation block, not a setting, `gmiau.py`'s JSONConfigParser translation skips any `cases.naming` key beginning `_`. Tools iterating `cases.naming` for *settings* must do likewise.
### 7.4 `cases.review`
```jsonc
"review": {
"daysBasis": "working", // working | calendar
"gapWarn": 65, // amber threshold (days)
"gapOverdue": 75, // red threshold (days)
"contactTokens": ["Email to", "Letter to", "RTC", β¦],
"invoicePatterns": ["Interpreter", "Expert", "Translation"],
"keyDocs": { // per-stage required-document lists
"lh": ["CCL|Client Care Letter", "OAL|Opening Advice Letter"],
"clr": ["CCL|Client Care Letter", "OAL|Opening Advice Letter"],
"mcc": ["CCL|Client Care Letter"],
"ho": ["CCL|Client Care Letter"]
}
}
```
Each `keyDocs.<stage>` entry is a list of `<CODE>|<Description>` strings. Loader flattens to `[review] key_docs_<stage> = "CODE|Desc, CODE|Desc"` for the legacy ConfigParser API.
## 8. Top-level webtool settings
### 8.1 `preparerDefault` (string, default `"GMIAU"`)
Fallback "Prepared By" value when a fresh case has no preparer set. Bundle covers, document footers.
### 8.2 `preparedByFooter` (boolean, default `true`)
Show the "Prepared By" line in bundle footers.
### 8.3 `bundleNumbering` (string, enum: `"continuous"` | `"per-section"`, default `"continuous"`)
`continuous`, page numbers run 1, 2, 3 across all sections. `per-section`, each section restarts numbering.
### 8.4 `partyConjunction` (string, enum: `"AND"` | `"v"`, default `"AND"`)
Word drawn between Appellant and Respondent on bundle cover pages. `AND` = tribunal house style; `v` = court house style.
### 8.5 `pdfFont` (string, enum: `"Helvetica"` | `"TimesRoman"` | `"EBGaramond"`, default `"Helvetica"`)
Font for PDF body text in tool-generated bundles. Promoted from per-browser localStorage to the One File 2026-05-09 (house style, not per-machine ergonomics). PDF body text size is hardcoded at 12 pt, not configurable.
The runtime cache `localStorage["ifyi_font_family"]` is a SECONDARY mirror, readers update it from the file at boot; writers update both on user changes.
### 8.6 `bundleTitles` (object, key string)
Per-bundle-type tramline title. Auto-uppercased. Empty string means "fall back to the shipped default". Keys match the registry populated by each tab module:
```jsonc
"bundleTitles": {
"appeal_ftt": "APPELLANT BUNDLE",
"appeal_ut": "COMPOSITE BUNDLE",
"application_further_submissions": "FURTHER SUBMISSIONS",
"application_fee_waiver": "FEE WAIVER REQUEST",
"application_flr": "FURTHER LEAVE TO REMAIN APPLICATION",
"application_dv": "DOMESTIC VIOLENCE APPLICATION",
"application_expert": "INSTRUCTIONS TO EXPERT",
"application_expert_appeal": "INSTRUCTIONS TO EXPERT",
"application_facebook_dyi": "FACEBOOK DOWNLOAD YOUR INFORMATION FILE",
"application_facebook_dyi_appeal": "FACEBOOK DOWNLOAD YOUR INFORMATION FILE",
"jr_permission": "PERMISSION BUNDLE",
"jr_hearing": "HEARING BUNDLE"
}
```
The PDF filename is derived from the title, `CORE BUNDLE INDEX` `Core-Bundle-<ref>.pdf`.
### 8.7 `bundleProforma` (object)
Per-bundle-type Index Sections + auto-routing keywords. Empty default means "use shipped". Shape:
```jsonc
"bundleProforma": {
"<bundle_key>": {
"sections": [{ "name": "Witness Evidence", "colour": "#79c0ff" }, β¦],
"keywords": [{ "sname": "Witness Evidence", "kw": ["statement", "witness"] }, β¦]
}
}
```
Per-case sidebar overrides still trump the defaults; the "Save as default" button promotes a per-case layout into here.
### 8.8 `partyLabelStyle` (object: `{ bold, underline }`, both booleans)
Cosmetic style for right-aligned party labels (Appellant / Respondent / Claimant / Defendant) on bundle covers. Independent toggles, `bold` uses the bold font; `underline` draws a thin line under the baseline. Both `false` = current default (regular weight, no underline).
## 9. Read protocol
1. **Resolve the path.** Tool reads `~/Work/Config/Tools-GMIAU-RB.conf` (the slug currently hardcoded; future: derived from running user).
- **CLI self-heal (gmiau-cases, 2026-05-11).** If the file is absent *and* `~/Nextcloud` exists, `gmiau.load_config()` generates a fresh `gmiau-config/1` file from shipped defaults, seeded from a legacy `gmiau-cases/work.conf` when present, and with the generated `cases.naming._reference` block, then proceeds. This makes a freshly-wiped machine config-resident on first run. If `~/Nextcloud` is *not* present yet, the CLI does **not** create a stray local `~/Nextcloud` (the sync client would have to reconcile it); it falls back to `work.conf` with a one-line warning. `IFYI_CONF` (test override) bypasses self-heal entirely. Webtools do not self-heal, they keep the "fall back to defaults" behaviour of step 2 (a webtool can't safely guess what the CLI sections should contain).
2. **Parse JSON.** On parse error: log a warning, fall back to the tool's hardcoded defaults, continue. Tools MUST NOT crash on a missing or malformed file.
3. **Schema check.** If `schema` is missing or doesn't start with the family `gmiau-config/`, treat as malformed (per step 2). If `schema` is a known version (`/1` or `/2` once promoted), proceed.
4. **Merge against defaults.** `effectiveConfig = { ...DEFAULTS, ...parsed }`. **Tools MUST preserve unknown keys** (use the spread, don't whitelist). This is what allows additive evolution within `/1`.
5. **Mirror to runtime caches** as needed:
- `pdfFont` `localStorage.setItem('ifyi_font_family', value)` so existing per-tab readers see the file value.
- `cases.naming.capsWords` flattened to a CSV string in the `ConfigParser` view returned by `gmiau.load_config()`.
6. **Expose to consumers.** Webtools assign `window.ifyiConfig = effectiveConfig`. CLI tools return the merged dict from their loader.
## 10. Write protocol
### 10.1 In-tab cache writes (no disk write)
Per user interaction with a Config-tab control:
```
[user changes a value]
ifyiConfig.<key> = newValue
saveConfig() // writes localStorage (per-browser cache; per-tool key)
if the key has a runtime mirror (e.g. pdfFont FONT_KEY), update that too
```
`saveConfig` is a localStorage write only. It does NOT touch the disk file. The disk file is canonical, localStorage is the in-flight cache.
### 10.2 Disk writes (explicit user action)
Disk writes happen ONLY when the user clicks **Export** in a Config tab. Writers MUST:
1. **Cold-start guard.** If `ifyiConfig` doesn't carry `user`/`paths`/`cases` (typical sign of a fresh browser profile or wiped localStorage where the user hasn't Imported yet), prompt before writing. Default to "stop and import first", losing CLI sections is harder to recover from than re-clicking Export.
2. **Filename.** `immigrationfyi-tools-{caseworker-slug}.conf`, where slug is `caseworkerName` lowercased and `[^a-z0-9]+` collapsed to `-`.
3. **Format.** `JSON.stringify(ifyiConfig, null, 2)`, pretty-printed, two-space indent, trailing newline.
4. **Persistence.** Prefer File System Access API `showSaveFilePicker` when available (lets the user pick where; remembers the handle if FSA-IDB is wired). Fall back to anchor-tag download.
5. **Status feedback.** Tool surfaces ` Exported {filename}` or the appropriate failure mode.
### 10.3 Import (read disk into the in-flight cache)
When the user clicks **Import** and picks a file:
1. **Schema check.** If `parsed.schema !== 'gmiau-config/1'` (or `/2` once promoted), reject with ` Not a gmiau-config/1 file.`.
2. **Merge.** `ifyiConfig = { ...DEFAULTS, ...parsed }`, preserves unknown keys.
3. **Sub-key clamping.** `bundleTitles`, `bundleProforma`, `partyLabelStyle` are object-shaped, clamp each to its expected shape (defensive: if junk, fall back to default).
4. **Runtime cache mirroring.** Sync `parsed.pdfFont` `localStorage[FONT_KEY]`; trigger any UI re-render (e.g. `setPdfFontActive(parsed.pdfFont)`).
5. **`saveConfig()`** to persist to the per-tool localStorage cache.
6. **Re-render** the Config tab UI from the merged config.
## 11. Cold-start guard (export side, normative)
```
if (!ifyiConfig.user && !ifyiConfig.paths && !ifyiConfig.cases) {
// Almost certainly a fresh session that hasn't imported the live file.
// Exporting now would write a file with no CLI sections, overwriting
// the user/paths/cases the file on disk does have.
confirm with user; default action = stop.
}
```
This is the primary defence against a webtool silently clobbering CLI-side sections.
## 12. CLI participation
| Tool | Reads | Writes |
|---|---|---|
| `letter` (`merge-letter.py`) | `user.*`, `paths.*` (via `_ifyi_merge.load_profile()`/`load_letter_config()`); top-level `caseworkerName` as last-resort name source | Yes, `letter --config` writes `paths.*`; `letter --init-profile` writes `user.*` |
| `tribunal` (`merge-tribunal.py`) | Same, shares the engine | No (no interactive setup; uses letter's) |
| `gmiau-cases` (`gmiau.py:load_config()`) | `user.name`, `paths.*` (incl. `indexDb` / `auditLog` / `reportsDir`), `cases.*` (translated to ConfigParser shape; `cases.naming._reference` is skipped) | Yes, self-heals the file when missing (Β§9.1); `work.py` settings menu writes `paths.openCasesDir`; `gmiau.config_ensure_reference()` keeps `cases.naming._reference` current |
| `cms config` (`cases/config-cli.py` + `config_tool.py`) | The whole file. The CLI counterpart of the webtools' Config tab: interactive per-field editor over every section, plus `update` (reconcile, add any option the current tools read, non-destructively, preserving unknown keys + flagging shape clashes) and `signature` (inline the image into `user.signatureImageData`). | Yes, explicit Save / `--apply` only. Keeps `caseworkerName` in lockstep with `user.name`; never overwrites an existing value. Added 2026-06-24. |
| Legacy `GMIAU-letter-config-RB.conf` | Read-only fallback for `paths.*` not in the One File | No |
| Legacy `GMIAU-user-RB.conf` | Read-only fallback for `user.*` not in the One File | No |
| Legacy `gmiau-cases/work.conf` | Read-only fallback when the One File is absent | No |
## 13. Webtool participation matrix
| Tool | Status | What it should read |
|---|---|---|
| `bundle-builder.html` | **Implements full read/write** (Config tab) | All sections + top-level webtool keys |
| `evidence-exhibitor.html` | **Implements full read/write** (Config tab) | Same |
| `filer.html` | **Implements full read/write** (Config tab) | Same |
| `staff-setup.html` | **Implements full read/write** (Config tab, generates / imports / edits the One File) | All sections + top-level webtool keys; the Email signature / Avatar / Virtual background / Wallpaper panes also pre-fill empty inputs from `user.*` |
| `bundle-bookmarker.html`, `bundle-compliance-tool.html`, `bundle-country-background-information.html` | Should read | `pdfFont`, `caseworkerName`, `paths.openCasesDir` |
| `document-signer.html`, `signer.html`, `passage-picker.html` | Should read | `caseworkerName` (PDF Author metadata), `pdfFont`, `user.signatureImagePath`, `paths.signaturesDir` |
| `pdf-compare.html`, `pdf-compress.html`, `pdf-encrypt.html`, `document-scanner.html`, `document-extractor.html` | Should read | `caseworkerName` (PDF Author metadata) |
| `image-a4.html`, `image-annotator.html`, `signature-capture.html`, `todo-list-generator.html` | Should read | `caseworkerName` (PDF Author metadata) |
| `chronology-drafter.html`, `client-fee-waiver-assessment.html`, `expert-register.html`, `interpreter-travel-calculator.html`, `laa-self-grant.html`, `photo-statement.html`, `cw-form-completion.html` | Should read | `caseworkerName`, `paths.*` as relevant |
| `asylum-interview-tagger.html`, `asylum-interview-reviewer.html` | Should read | `caseworkerName`, `paths.openCasesDir` |
| `precedents.html`, `advice-templates.html`, `templates.html`, `howto-guide.html`, `case-tools.html`, `case-todo-generator.html`, `case-summary.html` | Should read | `caseworkerName` (only for surfacing in metadata where applicable) |
| `converter.html`, `image-ocr.html`, `zip-extractor.html`, `zip-unlocker.html`, `metadata-cleaner.html`, `markdown-notepad.html`, `video-merger.html`, `video-review.html`, `photo-compare.html`, `document-notepad.html`, `evidence-matrix.html`, `file-renamer.html` | **Don't participate**, generic utilities, no GMIAU coupling |: |
Tools marked "Should read" don't currently read the One File. Implementing this is a multi-session task; it can land tool-by-tool over time.
## 14. Shared loader
Built 2026-05-09 at `immigrationfyi-tools/scripts/ifyi-config.js`. Inlined at the `<!-- @@IFYI_CONFIG@@ -->` build sentinel by `scripts/build-all-offline.py`, same pattern as `@@IFYI_THEME@@`.
### 14.1 Surface
```js
window.ifyiConfigRead() // returns the cached config object, or null
window.ifyiConfigGet(path, fallback) // dotted-path lookup with fallback
// ifyiConfigGet('user.name', 'Caseworker')
// ifyiConfigGet('paths.openCasesDir', '~/Work/00 Open Cases')
window.ifyiConfigOnReady(fn) // callback fires once the cache is populated;
// re-fires after a successful ifyiConfigImport().
window.ifyiConfigImport() // async, opens picker, validates, populates the
// shared cache. Returns the parsed config or null
// (cancel). Throws on schema/parse failure.
```
### 14.2 Cross-tool sync
The loader uses a single localStorage key `ifyi_config_shared` that all participating tools read from. The three matter-aware tools (bundle-builder / evidence-exhibitor / filer) write to this key from their existing `importConfig` handlers, so importing in any of them propagates to every other participating tool **served from the same origin**.
**Origin caveat (important):** localStorage is per-origin. Tools served from the same hostname (e.g. all opened via `immigrationfyi-tools.local`) share the cache. Tools opened directly via `file://` URLs each get a per-file origin in modern Chromium, the cache will NOT cross between `bundle-builder-offline.html` and `pdf-compress-offline.html`. Either run via the local hostname or `ifyiConfigImport()` separately in each tool.
### 14.3 Adoption pattern
Two changes per tool:
1. Drop `<!-- @@IFYI_CONFIG@@ -->` somewhere in `<head>` (typically just before `</head>`).
2. Read what the tool needs via `ifyiConfigGet(path, fallback)`. Identity tools pre-fill empty inputs; PDF-output tools use the value as a metadata fallback; etc.
The loader is plumbing, each tool decides which keys to consume per the participation matrix in Β§13.
### 14.4 Reference adopters (2026-05-09)
| Tool | Reads | Behaviour |
|---|---|---|
| `staff-setup.html` | full `user.*` (read) + every config section (read/write via the Config tab) | Config tab imports / generates / edits the One File. The Avatar pane pre-fills `#av-name`; the Virtual background / Wallpaper panes pre-fill `#bg-*` / `#wp-*` (name / role / email); the Email signature pane pre-fills `#sig-*`, each only when the input is empty. Consolidated 2026-05-09 from the now-retired `avatar-generator` / `virtual-background` / `wallpaper-generator` / `email-signature` tools. |
| `pdf-compress.html` | `user.name` | If the source PDF has no `Author` metadata, the compressed output's `Author` is set to `user.name`. |
These adopters establish the pattern; remaining participation-matrix tools can be wired session-by-session using the same shape.
## 15. App-appearance + App-font sharing, `<!-- @@IFYI_SETTINGS@@ -->`
The 3 matter-aware tools (Bundle Builder / Evidence Exhibitor / Filer) own the **Settings tab UI** that writes these localStorage keys:
| Key | Values |
|---|---|
| `ifyi_mode_pref` | `'system'` \| `'light'` \| `'dark'` |
| `ifyi_dark_family` | `'gmiau'` (only family; legacy values are coerced) |
| `ifyi_app_font_family` | `'system'` \| `'helvetica'` \| `'times'` \| `'mono'` |
| `ifyi_app_font_size` | `'12'`..`'18'` (string, px) |
Other tools that should *match the look* (without re-implementing the UI) drop the `<!-- @@IFYI_SETTINGS@@ -->` sentinel in their `<head>`, after `<!-- @@IFYI_THEME@@ -->`. The shared `immigrationfyi-tools/scripts/ifyi-settings.js` applier:
- Resolves Mode effective theme id (`ifyi-light` / `ifyi-dark`) and calls `ifyiTheme.apply()`. Mirrors Bundle Builder's `_effectiveThemeId()` logic.
- Sets `--app-font` and `--app-font-size` CSS variables on `:root` for tools that reference them, and also applies `style.fontFamily` / `style.fontSize` directly on `<html>` so tools that *don't* reference the variables pick the values up via inheritance.
- Listens to the `storage` event so changes made in Bundle Builder's Settings tab propagate live into other open tabs (when same-origin).
- Listens to `prefers-color-scheme` so `mode='system'` tracks OS dark/light flips.
- Exposes `window.ifyiSettingsApply()` for callers that mutate the localStorage keys themselves and need a manual re-apply.
### 15.1 Sentinel order in `<head>`
The settings applier depends on `ifyiTheme.apply()`. Drop the sentinels in this order:
```html
<!-- @@IFYI_THEME@@ -->
<!-- @@IFYI_SETTINGS@@ -->
<!-- @@IFYI_CONFIG@@ -->
```
If `@@IFYI_THEME@@` lands later, the applier defers via `DOMContentLoaded`, works either way, but boot-order is cleaner the right way round.
### 15.2 Reference adopters (2026-05-09)
`staff-setup.html` and `pdf-compress.html` (which also adopt `@@IFYI_CONFIG@@`) adopt `@@IFYI_SETTINGS@@` too. They mirror Bundle Builder's Mode / theme / app-font / size on launch and live-react when those settings change in another open tab. Staff Setup additionally owns its own **Settings tab** (like the matter-aware tools) and hides its `#themeBtn`, so the shared dialog rebind in Β§15.3 is a no-op there.
### 15.3 Per-tool Settings dialog
The applier also exposes a **shared Settings dialog** that any participating tool gets for free. Why: Chromium gives each `file://` HTML its own origin, so localStorage doesn't cross between offline tools opened by double-click. Without per-tool controls, a user double-clicking `pdf-compress-offline.html` couldn't change Mode or Font without first opening Bundle Builder via a hostname.
Mechanics:
- The applier defines `window.ifyiSettingsOpen()`, pops a modal with the same 4 toggle controls (App font Family + Size, App appearance Mode + Theme).
- On launch, the applier rebinds any **visible** `#themeBtn` in the DOM to call `ifyiSettingsOpen()` instead of `ifyiTheme.cycle()`. The 3 matter-aware tools hide their `#themeBtn` (`display:none`, `aria-hidden="true"`) so the rebind is a no-op there, they keep using their full Settings tab.
- Each control writes the corresponding localStorage key on click and re-applies immediately. No Save button needed.
The same set of localStorage keys feeds Bundle Builder's Settings tab, so when tools share an origin (`immigrationfyi-tools.local`) the Bundle-Builder UI and the per-tool dialogs stay in lockstep.
### 15.4 Per-machine, per-tool
These keys are intentionally NOT in `Tools-GMIAU-RB.conf`. Mode and font preferences are per-machine ergonomics, different on a desktop vs. laptop, possibly different per browser. The Settings dialog provides per-tool control on file:// where origin sharing breaks.
If a future iteration decides these *should* sync across machines, promoting them into the One File (alongside `pdfFont`'s 2026-05-09 promotion) would be a small change: extend the schema with `appearance` and `appFont` sections, have Bundle Builder + the dialog write both file and localStorage on change, and the applier reads file-first with localStorage fallback.
## 15A. Encrypted database sharing, `<!-- @@DATABASE@@ -->`
> **2026-05-22, register split.** The *organisational* register, > `offices`, `locations`, `caseworkers`, `supervisors`, moved **out** of the
> encrypted database and into this One File under a top-level `register`
> section (`register.offices` / `.locations` / `.caseworkers` / `.supervisors`).
> The Database tool reads them via `ifyiConfigGet('register.*')`, shows them
> read-only in its Config tab, and writes empty arrays into the encrypted
> payload on save. The encrypted database remains the source of truth for the
> *contact* register below (interpreters / experts / barristers / contacts /
> bookings).
Tools that need to read the GMIAU contact register (interpreters, experts,
barristers, contacts) drop
`<!-- @@DATABASE@@ -->` in `<head>` and the offline builder inlines
the envelope crypto module + EFF mnemonic wordlist + the shared loader
from `immigrationfyi-tools/scripts/database.js`. See
`gmiau-specs/DATABASE-SPEC.md` for the full envelope contract; this
section is just the loader-side surface.
API:
```js
window.databaseRead() // -> {db,contacts,config,bookings} | null
window.databaseGet(path, fallback) // dotted lookup, e.g. 'config.caseworkers'
window.databaseOnReady(fn) // fires when cache populated; re-fires on Import
window.databaseImport() // file picker password prompt caches
window.databaseQueueAddition(kind, value) // suggest a new entry without writing
```
Cache: `localStorage.database_shared` holds the *cleartext* payload
for the session (cleared on browser-data wipe). `localStorage.database_pending`
holds suggestions queued by consumer tools, surfaced by the database tool
on next open.
Read-only contract: consumer tools MUST NOT mutate the cleartext cache or
the pending queue out-of-band; suggestions go via `databaseQueueAddition`.
## 15. Migration history
- **Pre-2026-05-09**, multi-file split: `~/Config/gmiau/letter-config.json`, `~/Config/gmiau/user.json`, `~/Projects/gmiau-apps/gmiau-cases/work.conf`, `~/Work/Config/Tools-GMIAU-RB.conf`. Each tool owned its own file. Caseworker name appeared in 3 of them.
- **2026-05-08 (morning)**, letter config files moved to `~/Work/Config/GMIAU-{letter-config,user}-rb.json`; `caseworkerName` inheritance from `Tools-GMIAU-RB.conf` added to the letter loader.
- **2026-05-09 (morning)**, letter config files renamed to `-RB.conf` for naming consistency; `case_owner_name` removed from user profile (pure inheritance from `caseworkerName`).
- **2026-05-09 (afternoon)**, One File consolidation (this document). All configs collapsed into `Tools-GMIAU-RB.conf` via the `user` / `paths` / `cases` sections. Letter / tribunal / gmiau-cases loaders rewritten. Webtool export guards added. Schema kept at `/1` (additive).
- **2026-05-09 (evening)**, `pdfFont` promoted from per-browser localStorage into the file (this spec written).
- **2026-06-24**, added the `cms config` CLI (`cases/config-cli.py` + `config_tool.py`): interactive editor + non-destructive `update` reconcile + `signature` embed. Added `user.signatureImageData` (embedded base64 signature, Β§5). Documented the sections that had grown ahead of this spec, `representative` (Β§5B), `team` (Β§5C), `firm_history` (Β§5D). No schema bump (additive within `/1`).
- **2026-05-11**, gmiau-cases: One File made the enforced SoT: `gmiau.load_config()` self-heals a missing file (Β§9.1); added `paths.indexDb` / `paths.auditLog` / `paths.reportsDir` knobs (Β§6) so cache/state dirs can be Nextcloud-resident; added the generated `cases.naming._reference` by-folder convention catalogue (Β§7.3), mirrored from `naming.yaml` v1.3's new `folders:` block.
Future:
- Spread the loader to the participating webtools listed in Β§13.
- Bump schema to `/2` once all webtools' import path accepts both.
- Retire the legacy fallback files (after a stable use cycle).
## 16. Conformance checklist
A tool participating in this spec MUST:
- [ ] Read from the canonical path on launch (no hardcoded duplicates of the values).
- [ ] Tolerate missing file / missing keys / parse failures by falling back to defaults.
- [ ] Use the merge-with-spread pattern on import (`{...defaults, ...parsed}`) so unknown keys are preserved.
- [ ] Mirror runtime caches from the file at boot (e.g. `pdfFont` `localStorage.ifyi_font_family`).
- [ ] On user change: write to BOTH the runtime cache AND `ifyiConfig.<key>`, then `saveConfig()`.
- [ ] Implement the cold-start guard before exporting.
- [ ] Reject schema versions outside the supported set.
- [ ] Keep `caseworkerName` and `user.name` in lockstep until the schema bump retires the mirror.
- [ ] Treat any object key beginning `_` (e.g. `cases.naming._reference`) as a generated documentation block, not a setting, skip it when iterating a section for settings, and preserve it verbatim on round-trip.
A tool participating in this spec MUST NOT:
- [ ] Carry its own copy of `caseworkerName` or any cross-tool fact in localStorage as the SoT.
- [ ] Whitelist keys on import (which would drop additive sections).
- [ ] Write to disk silently, disk writes are an explicit Export action only (the gmiau-cases CLI self-heal of a *missing* file is the sole exception, Β§9.1).
- [ ] Hand-edit `cases.naming._reference`, it's regenerated from `gmiau-specs/naming.yaml`.
## 17. References
- Plan: `~/Notes/00 Inbox/GMIAU config consolidation - migration plan.md`
- Reference doc: `~/Notes/03 Resources/Reference/Letter Reference.md`
- CLI implementation: `~/Config/Scripts/tools/_ifyi_merge.py`, `~/Projects/gmiau-apps/gmiau-cases/gmiau.py`
- Webtool implementations: `immigrationfyi-tools/source/{bundle-builder,evidence-exhibitor,filer}.html`
- Snapshot: `~/Projects/gmiau-apps/gmiau-backup/config-consolidation-2026-05-09/`
Free and open source, hosted in the EU by Codeberg. Designed for immigration and asylum casework. Use at your own risk., Nothing leaves your browser. The file on disk is canonical; this tool just edits it.
This is exactly what Export writes: your form values merged over the imported file, so preserved sections appear too.
No config loaded yet: go to Editor and Import the One File (or New for a fresh template).
Syncs across every GMIAU Shell tool via ifyi_hide_guide_tab + ifyi_hide_config_tab.