diff --git a/01 Projects/ford_qc/Ford QC System.md b/01 Projects/ford_qc/Ford QC System.md
index 5f3a6b5..2ea8f4c 100644
--- a/01 Projects/ford_qc/Ford QC System.md
+++ b/01 Projects/ford_qc/Ford QC System.md
@@ -9,8 +9,8 @@ url:
server: Production server with systemd
tags: [ford, qc, quality-control, box, bnp, systemd, hotfolder]
created: 2026-04-14
-last_commit: 2026-04-16
-commits: 19
+last_commit: 2026-04-17
+commits: 20
---
## Overview
@@ -68,10 +68,27 @@ sudo systemctl status ford-qc-hotfolder.service
## Timeline / Git History
| Date | Change |
|------|--------|
+| 2026-04-17 | Add asset count summary to QC HTML reports |
| 2026-04-16 | Add zip filename check for GPAS naming convention |
| 2026-03-16 | Make Ranger ptvl pattern configurable via profile |
## Sessions
+### 2026-04-17 – Where can we find the report
+**Asked:** Where can we find the report and why isn't the previously done task working?
+**Done:** Generated report opened in browser using linkingrecord.json and QC results, added Asset Count card showing link and asset counts by section.
+
+### 2026-04-17 – Asked | Done | Log
+**Asked:** Asked | Done | Log
+**Done:** Feature deployment | Pushed changes and restarted dev service | ford-qc-hotfolder.service
+
+### 2026-04-17 – Where is the report and why
+**Asked:** Where is the report and why isn't the implementation working?
+**Done:** Updated implementation to display total linking records and unique assets for overall and per-section views by modifying the call site to pass four values.
+
+### 2026-04-17 – Asked | Where can the new
+**Asked:** Asked | Where can the new report be found after implementation? | Completed
+**Done:** Done | Added two new static methods before `_build_head` and verified changes | Code updated with new static methods
+
### 2026-04-16 – Changed zip file naming convention from
**Asked:** Changed zip file naming convention from "_image.zip" to "_GPAS.zip" for image pack validation.
**Done:** Updated image pack naming format and added validation check to ensure all packs end with "_GPAS.zip" suffix.
@@ -135,6 +152,10 @@ sudo systemctl status ford-qc-hotfolder.service
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
+| 2026-04-17 | Report generation and deployment | Generated report with Asset Count card, linked QC results to summary | linkingrecord.json, test_reports, box-cli dev deployment |
+| 2026-04-17 | Report location | Code pushed to git and deployed to box-cli dev folder | dev deployment |
+| 2026-04-17 | Report display implementation | Import validation, function signature updated to accept four parameters | call site, implementation file |
+| 2026-04-17 | New static methods | Added two static methods before _build_head, verified implementation | Source file |
| 2026-04-16 | Image pack naming | Zip suffix changed from _image to _GPAS, validation check added | .gitignore, validation script |
| 2026-04-16 | Zip naming validation | zip_filename_check implementation, validation logic, error messaging | .gitignore, validation module files |
| 2026-04-16 | Zip naming update | Validation logic for _GPAS.zip suffix, .env configuration setup | .gitignore, utils/config.py, .env.example |
diff --git a/99 Daily/2026-04-17.md b/99 Daily/2026-04-17.md
index 44ab995..bd5c42d 100644
--- a/99 Daily/2026-04-17.md
+++ b/99 Daily/2026-04-17.md
@@ -347,3 +347,48 @@ tags: [daily]
- 14:14 | `Barclays-banner-builder`
- **Asked:** Create an idempotent deployment script for Ubuntu that builds Docker containers, pulls code, initializes database, and runs migrations.
- **Done:** Analyzed project requirements and provided deployment script foundation with Docker build caching, database initialization, and migration execution strategy.
+- 15:37 (2min) | `ford_qc`
+ - **Asked:** Asked | Where can the new report be found after implementation? | Completed
+ - **Done:** Done | Added two new static methods before `_build_head` and verified changes | Code updated with new static methods
+- 15:40 | `ford_qc`
+ - **Asked:** Where is the report and why isn't the implementation working?
+ - **Done:** Updated implementation to display total linking records and unique assets for overall and per-section views by modifying the call site to pass four values.
+- 15:41 | `ford_qc`
+ - **Asked:** Asked | Done | Log
+ - **Done:** Feature deployment | Pushed changes and restarted dev service | ford-qc-hotfolder.service
+- 15:44 | `ford_qc`
+ - **Asked:** Where can we find the report and why isn't the previously done task working?
+ - **Done:** Generated report opened in browser using linkingrecord.json and QC results, added Asset Count card showing link and asset counts by section.
+- 16:02 (2min) | `memory-compiler`
+ - **Asked:** Developer requested review of added terminal and Claude Code documentation in Obsidian wiki and identification of system improvements needed.
+ - **Done:** Reviewed documentation and identified additional quality checks and empty sections to refine in the system.
+- 16:06 (3min) | `memory-compiler`
+ - **Asked:** Compile knowledge from daily conversation logs into structured wiki articles and update the master index.
+ - **Done:** Created 4 new concept articles documenting LibreChat OpenID auth, MSAL.js PKCE implementation, and updated the master index with new concepts and connections.
+- 16:08 (1min) | `memory-compiler`
+ - **Asked:** Review added terminal and Claude Code documentation in Obsidian wiki and identify needed system improvements.
+ - **Done:** Verified compile.py pipeline, fixed bug, successfully generated 4 concept and 1 connection articles from logs.
+- 16:11 | `memory-compiler`
+ - **Asked:** Review added wiki content about terminals and Claude Code, then identify what needs improvement in the system.
+ - **Done:** Fixed f-string bugs in flush.py/compile.py, merged master-index table, confirmed compile process working, and gathered configuration details.
+- 16:22 | `memory-compiler`
+ - **Asked:** Review added terminal and Claude code content in Obsidian wiki and identify needed system improvements.
+ - **Done:** Reviewed Obsidian CLI documentation and identified that GitHub MCP package name was incorrect in implementation.
+- 16:47 | `memory-compiler`
+ - **Asked:** Review the Obsidian wiki content about terminals and Claude Code, then identify what needs improvement in the system.
+ - **Done:** Identified conflict between forgit function aliases and custom abbreviations, and updated the cheat sheet accordingly.
+- 16:52 | `memory-compiler`
+ - **Asked:** Review Obsidian wiki additions about terminals and Claude Code to identify missing features.
+ - **Done:** Documented Obsidian CLI command syntax and parameters for vault interaction.
+- 17:01 (2min) | `aimpress`
+ - **Asked:** Asked | accomplished something
+ - **Done:** Audit Claude config and research top GitHub plugins for dev/design | Reviewed configuration settings and identified top-starred plugins for development and design workflows | .claude/config.json, plugin-recommendations.md
+- 17:03 | `aimpress`
+ - **Asked:** Audit Claude Code configuration and install top-starred GitHub plugins for development and design.
+ - **Done:** Reviewed hooks and official plugins, then installed three official and two community plugins.
+- 17:27 | `aimpress`
+ - **Asked:** Set up Claude Code with top-starred GitHub plugins for development and design tasks.
+ - **Done:** Installed three official Anthropic plugins and two community plugins with automatic context-based activation.
+- 17:29 | `aimpress`
+ - **Asked:** Reviewed Claude configuration for development tasks | Analyzed GitHub top-starred skills and plugins for development/design, installed 5 selected tools (3 official + 2 community), confirmed session load and token usage are optimized | Configuration files, skill definitions
+ - **Done:** —
diff --git a/wiki/_master-index.md b/wiki/_master-index.md
index 7c4e946..7504c95 100644
--- a/wiki/_master-index.md
+++ b/wiki/_master-index.md
@@ -23,15 +23,14 @@ This 3-hop pattern works for hundreds of articles without vector search.
| [[wiki/tech-patterns/_index\|tech-patterns/]] | Recurring tech stacks: FastAPI, React/Vite, Next.js, Azure AD, AI, Box, One2Edit | 9 |
| [[wiki/architecture/_index\|architecture/]] | Cross-cutting architectural patterns: Docker Compose, multi-agent AI, GCP timeout, RAG, hotfolder | 5 |
| [[wiki/client-knowledge/_index\|client-knowledge/]] | Per-client notes for Ford, H&M, L'Oréal (2+ projects each) | 3 |
-| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 0 |
-| [[wiki/connections/_index\|connections/]] | Cross-cutting insights linking 2+ concepts | 0 |
+| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 4 |
+| [[wiki/connections/_index\|connections/]] | Cross-cutting insights linking 2+ concepts | 1 |
| [[wiki/qa/_index\|qa/]] | Filed answers to queries (saved with `--file-back`) | 0 |
| [[wiki/homelab/_index\|homelab/]] | Self-hosted infra: Proxmox install, IOMMU/PCI passthrough, hypervisor setup, budget builds | 4 |
| [[wiki/web-agency/_index\|web-agency/]] | AI-assisted website building & selling: Claude Code, Nanobanana 2, Kling, LaunchPath MCP | 1 |
| [[wiki/dotfiles/_index\|dotfiles/]] | Linux terminal ricing: Kitty, Fish, WezTerm CLI, modern Rust CLI tools, LazyVim, unified themes, Tabby | 21 |
-
| [[wiki/agent-sdk/_index\|agent-sdk/]] | Claude Agent SDK (formerly Claude Code SDK) — build autonomous AI agents in Python and TypeScript | 30 |
-| [[wiki/llm-models/_index\|llm-models/]] | OpenAI model catalog — GPT-5.x, o-series reasoning, audio/realtime, embeddings, moderation | 1 |
+| [[wiki/llm-models/_index\|llm-models/]] | LLM model catalogs — OpenAI and Claude/Anthropic models, IDs, context, pricing | 2 |
| [[wiki/claude-code/_index\|claude-code/]] | Claude Code product docs — install, capabilities, surfaces, MCP, hooks, scheduling, multi-agent, plugins, skills, channels, error recovery | 12 |
diff --git a/wiki/concepts/_index.md b/wiki/concepts/_index.md
index 1d4694b..72f7782 100644
--- a/wiki/concepts/_index.md
+++ b/wiki/concepts/_index.md
@@ -5,6 +5,10 @@
| Article | Summary | Source | Updated |
|---------|---------|--------|---------|
+| [[wiki/concepts/librechat-openid-auth]] | LibreChat v0.8.4 OpenID/OAuth auth internals — ALLOW_SOCIAL_REGISTRATION, openidId requirement, state mismatch diagnosis | daily/2026-04-15.md | 2026-04-15 |
+| [[wiki/concepts/msal-vanilla-js-pkce]] | MSAL.js v5 UMD script-tag PKCE for SPAs without bundlers — Azure portal platform type, Node.js JWKS validation | daily/2026-04-15.md | 2026-04-15 |
+| [[wiki/concepts/shell-static-deploy-patterns]] | Shell deploy scripts for static frontends — cp -r vs glob, Apache reload requirement, setup vs deploy distinction | daily/2026-04-15.md | 2026-04-15 |
+| [[wiki/concepts/fish-shell-path-config]] | Fish shell PATH configuration — /usr/local/bin not in default PATH, fish_add_path, SSH aliases as Fish functions | daily/2026-04-15.md | 2026-04-15 |
diff --git a/wiki/concepts/fish-shell-path-config.md b/wiki/concepts/fish-shell-path-config.md
new file mode 100644
index 0000000..5447986
--- /dev/null
+++ b/wiki/concepts/fish-shell-path-config.md
@@ -0,0 +1,88 @@
+---
+title: "Fish Shell PATH Configuration"
+aliases: [fish-path, fish-user-paths, fish_add_path, fish-env]
+tags: [fish, shell, path, dotfiles, terminal]
+sources:
+ - "daily/2026-04-15.md"
+created: 2026-04-15
+updated: 2026-04-15
+---
+
+# Fish Shell PATH Configuration
+
+Fish shell does not automatically include all standard system directories in `PATH`. Directories like `/usr/local/bin` are commonly missing, causing commands that work in bash to fail in Fish with `Unknown command`.
+
+## Key Points
+
+- `/usr/local/bin` is **not** in Fish's default PATH — tools installed there (e.g., `kubectl`, Homebrew binaries on older macOS installs) must be added explicitly
+- Use `fish_add_path /usr/local/bin` to permanently add a directory — it modifies `fish_user_paths` universal variable and persists across sessions
+- `set -gx fish_user_paths /usr/local/bin $fish_user_paths` is the older manual equivalent; `fish_add_path` is idempotent and preferred
+- `spf --fix-config-file` (superfile config fix) requires a live TTY and cannot run from a non-interactive context like Claude Code agent
+- SSH connection aliases belong in Fish functions (`~/.config/fish/functions/`) that wrap the underlying `~/.ssh/config` entries — don't duplicate host/user/key info in Fish
+
+## Details
+
+### Adding Directories to Fish PATH
+
+```fish
+# Permanent (recommended) — idempotent, safe to run multiple times
+fish_add_path /usr/local/bin
+fish_add_path /opt/homebrew/bin # Apple Silicon Homebrew
+
+# One-time session only
+set -gx PATH /usr/local/bin $PATH
+
+# Manual permanent (older style)
+set -U fish_user_paths /usr/local/bin $fish_user_paths
+```
+
+`fish_add_path` checks for duplicates, so it's safe to call in `config.fish` on every startup without path bloat.
+
+### Diagnosing "Unknown command" Errors
+
+When a tool is installed but Fish can't find it:
+
+```fish
+# Find where the binary actually lives
+which kubectl # may fail
+command -v kubectl
+
+# Check current PATH
+echo $PATH
+
+# Find the binary manually
+find /usr /opt /home -name kubectl 2>/dev/null
+```
+
+Once found, add the parent directory with `fish_add_path`.
+
+### SSH Aliases as Fish Functions
+
+Wrap SSH connections in named Fish functions rather than duplicating config:
+
+```fish
+# ~/.config/fish/functions/ssh-optical.fish
+function ssh-optical
+ ssh optical-dev # matches a Host block in ~/.ssh/config
+end
+```
+
+`~/.ssh/config` holds the actual host, user, and identity file. The Fish function is just a convenience alias. This separation means updating the SSH config (e.g., changing a hostname) doesn't require updating Fish functions.
+
+### Dotfiles Context (2026-04-15 Setup)
+
+Terminal stack configured to match a specific video guide style:
+- **Terminal:** Kitty
+- **Shell:** Fish + tide prompt
+- **Editor:** Neovim with kanagawa-wave theme
+- **File manager:** superfile (`spf`)
+- **Font:** Hasklug Nerd Font Mono, size 15
+
+## Related Concepts
+
+- [[wiki/dotfiles/_index]] — full dotfiles topic index with Kitty, Fish, LazyVim, WezTerm articles
+- [[wiki/concepts/shell-static-deploy-patterns]] — other shell scripting patterns
+
+## Sources
+
+- [[daily/2026-04-15.md]] — Fish PATH fix for `kubectl` (`/usr/local/bin` not in PATH), SSH aliases setup, terminal dotfiles configuration
diff --git a/wiki/concepts/librechat-openid-auth.md b/wiki/concepts/librechat-openid-auth.md
new file mode 100644
index 0000000..5a78da6
--- /dev/null
+++ b/wiki/concepts/librechat-openid-auth.md
@@ -0,0 +1,55 @@
+---
+title: "LibreChat OpenID / Azure AD Auth Internals"
+aliases: [librechat-auth, librechat-openid, librechat-oauth]
+tags: [librechat, azure-ad, oauth, openid, debugging]
+sources:
+ - "daily/2026-04-15.md"
+created: 2026-04-15
+updated: 2026-04-15
+---
+
+# LibreChat OpenID / Azure AD Auth Internals
+
+LibreChat v0.8.4 has specific requirements for OpenID/OAuth users that are not obvious from configuration alone. The interaction between `ALLOW_SOCIAL_REGISTRATION`, MongoDB user records, and session cookies creates multiple silent failure points.
+
+## Key Points
+
+- `ALLOW_SOCIAL_REGISTRATION=false` means LibreChat checks for an existing user with `openidId` populated — a manually inserted MongoDB record without `openidId` is not recognized
+- `Unknown OAuth error` in LibreChat logs means `req.session?.messages` is empty — the real error happened earlier in the session/state layer, before the verify callback
+- OAuth state mismatch can occur client-side if the session cookie from a previous attempt is stale after a server restart
+- The Network tab flow for a failing auth: `302 → /oauth/openid → Microsoft OK → /oauth/callback → /oauth/error`
+- Always get a screenshot or Network tab capture from the user first — it identifies whether Microsoft auth succeeds before investing time in server logs
+
+## Details
+
+### ALLOW_SOCIAL_REGISTRATION and openidId
+
+When `ALLOW_SOCIAL_REGISTRATION=false`, LibreChat requires the user to already exist in MongoDB **with** the `openidId` field populated. Adding a user document manually (e.g., copying another user's record) does not work unless the `openidId` field contains the correct Azure AD object ID. The check order is: look up by `openidId` → if not found, registration is blocked → auth fails.
+
+Setting `ALLOW_SOCIAL_REGISTRATION=true` temporarily lets the user complete the flow and have `openidId` saved automatically. After the user registers, set it back to `false` and the user will be recognized on subsequent logins.
+
+### OAuth State Mismatch
+
+`Unknown OAuth error` in LibreChat server logs maps to the passport.js behavior where `req.session?.messages` is empty — this means state validation failed before the verify callback even ran. The likely causes:
+
+1. **Stale session cookie** — if the server restarted between when the user clicked "Login" and when Microsoft redirected back, the session store no longer has the original OAuth state. The callback `state` parameter doesn't match anything in the session → validation fails.
+2. **Browser privacy settings or proxies** — if the session cookie is not sent on the callback request (third-party cookie blocking, network proxy stripping cookies), the session lookup fails silently.
+
+The diagnosis path: confirm Microsoft authentication *succeeds* (user sees Microsoft login page and approves) → check if redirect goes to `/oauth/callback` or directly to `/oauth/error`. If it goes to `/oauth/error`, the issue is in LibreChat's session layer, not in Azure AD.
+
+### Diagnosis Order
+
+1. Ask user for a Network tab screenshot first — identifies where the 302 chain breaks
+2. Check server logs for `Unknown OAuth error` vs a specific passport error message
+3. Check MongoDB for the user document and whether `openidId` is present
+4. Try from a different browser / private window to rule out stale cookies
+
+## Related Concepts
+
+- [[wiki/tech-patterns/azure-ad-msal-auth]] — Azure AD app registration and PKCE configuration
+- [[wiki/concepts/msal-vanilla-js-pkce]] — SPA-specific Azure AD OAuth patterns
+- [[wiki/concepts/oauth-state-mismatch-debugging]] — broader OAuth state debugging patterns
+
+## Sources
+
+- [[daily/2026-04-15.md]] — LibreChat debugging session for user `santhosha.nayak@brandtech.plus` on `chat-sandbox.oliver.solutions`
diff --git a/wiki/concepts/msal-vanilla-js-pkce.md b/wiki/concepts/msal-vanilla-js-pkce.md
new file mode 100644
index 0000000..d6adaa3
--- /dev/null
+++ b/wiki/concepts/msal-vanilla-js-pkce.md
@@ -0,0 +1,92 @@
+---
+title: "MSAL.js v5 Vanilla JS PKCE (No Bundler)"
+aliases: [msal-umd, msal-script-tag, msal-spa-pkce, msal-vanilla]
+tags: [azure-ad, msal, pkce, spa, vanilla-js, auth]
+sources:
+ - "daily/2026-04-15.md"
+created: 2026-04-15
+updated: 2026-04-15
+---
+
+# MSAL.js v5 Vanilla JS PKCE (No Bundler)
+
+MSAL.js v5 can be used in plain HTML/JS projects via its UMD build without a bundler. The library exposes `window.msal` from a `
+
+```
+
+The `window.msal` namespace is the entry point — all MSAL classes are on it.
+
+### Azure Portal Configuration
+
+The app registration platform type is critical:
+- **"Single-page application"** → PKCE flow, no client secret, correct for browser-only apps
+- **"Web"** → authorization code flow, expects a client secret, wrong for pure SPAs
+
+If the platform type is "Web", Azure will expect a client secret on the token endpoint. The PKCE flow will fail because the browser-side MSAL request doesn't send a secret. Switch to "Single-page application" in Azure Portal → Authentication → Platform configurations.
+
+### Backend JWKS Validation (Node.js, No Extra Deps)
+
+```js
+// Fetch JWKS from Azure and cache for 24h
+const JWKS_URL = `https://login.microsoftonline.com/${TENANT_ID}/discovery/v2.0/keys`;
+let jwksCache = { keys: [], fetchedAt: 0 };
+
+async function getSigningKey(kid) {
+ if (Date.now() - jwksCache.fetchedAt > 86400000) {
+ const res = await fetch(JWKS_URL);
+ jwksCache = { keys: (await res.json()).keys, fetchedAt: Date.now() };
+ }
+ return jwksCache.keys.find(k => k.kid === kid);
+}
+
+// Validate with Node.js built-in crypto
+const [header64, payload64, sig64] = token.split(".");
+const key = await getSigningKey(JSON.parse(atob(header64)).kid);
+// ... construct public key from JWK and verify signature with crypto.verify()
+```
+
+This approach adds zero npm dependencies — useful in projects where keeping the dependency footprint small matters.
+
+### Deploy Pattern
+
+For this project (`social-reporting-tool`):
+- `setup.sh` — first-time only: creates directories, initial container setup
+- `deploy.sh` — all subsequent deploys: copies frontend files, rebuilds image
+- **`deploy.sh` does NOT restart the container with updated env vars** — after adding new env vars to `.env`, manually run: `docker compose down && docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
+
+## Related Concepts
+
+- [[wiki/tech-patterns/azure-ad-msal-auth]] — general MSAL patterns across Oliver projects
+- [[wiki/concepts/librechat-openid-auth]] — Azure AD app registration used in LibreChat context
+- [[wiki/concepts/shell-static-deploy-patterns]] — deploy.sh patterns for frontend projects
+
+## Sources
+
+- [[daily/2026-04-15.md]] — MSAL v5 SSO implementation for `optical-dev.oliver.solutions` social-reporting dashboard
diff --git a/wiki/concepts/shell-static-deploy-patterns.md b/wiki/concepts/shell-static-deploy-patterns.md
new file mode 100644
index 0000000..c9b13b7
--- /dev/null
+++ b/wiki/concepts/shell-static-deploy-patterns.md
@@ -0,0 +1,91 @@
+---
+title: "Shell Deploy Script Patterns for Static Frontends"
+aliases: [deploy-script, cp-recursive, frontend-deploy, apache-reload]
+tags: [shell, bash, deploy, apache, nginx, static-files]
+sources:
+ - "daily/2026-04-15.md"
+created: 2026-04-15
+updated: 2026-04-15
+---
+
+# Shell Deploy Script Patterns for Static Frontends
+
+Bash deploy scripts for static frontend projects have two common failure modes: incomplete file copying and serving stale files after deploy. Both are silent — the script exits 0 but the deployment is wrong.
+
+## Key Points
+
+- `cp frontend/*` glob does **not** recurse into subdirectories — use `cp -r frontend/. "$DEST/"` instead
+- `cp -r frontend/.` (with dot) copies all contents including hidden files and nested dirs; `cp -r frontend/` copies the directory itself into the dest
+- Always add `mkdir -p "$DEST"` before the copy as a safety net
+- After copying static files, reload the web server: `systemctl reload apache2` (or `nginx -s reload`) — otherwise the old cached version is served
+- Distinguish `setup.sh` (first-time init) from `deploy.sh` (all subsequent updates) — only `deploy.sh` runs on updates
+
+## Details
+
+### The cp Glob Pitfall
+
+```bash
+# WRONG — skips subdirectories silently
+cp frontend/* /var/www/html/
+
+# CORRECT — copies all contents recursively, including hidden files
+cp -r frontend/. /var/www/html/
+```
+
+The shell glob `frontend/*` expands to all non-hidden entries in `frontend/` but `cp` without `-r` will skip any entries that are directories (it prints a warning but exits 0). Using `frontend/.` instead of `frontend/` as the source avoids creating a nested directory inside the destination.
+
+### Apache / Nginx Reload Requirement
+
+Web servers cache configuration and sometimes static file handles. After overwriting files in the web root:
+
+```bash
+# Apache
+systemctl reload apache2
+
+# Nginx
+nginx -s reload
+# or
+systemctl reload nginx
+```
+
+`reload` is preferred over `restart` — it re-reads config and gracefully hands off connections without dropping active requests.
+
+### Recommended deploy.sh Structure
+
+```bash
+#!/bin/bash
+set -e
+
+FRONTEND_DIR="/var/www/html/myapp"
+BACKEND_DIR="/opt/myapp"
+
+# 1. Copy frontend files
+mkdir -p "$FRONTEND_DIR"
+cp -r frontend/. "$FRONTEND_DIR/"
+
+# 2. Rebuild and restart backend
+cd "$BACKEND_DIR"
+docker compose build
+docker compose up -d
+
+# 3. Reload web server
+systemctl reload apache2
+
+echo "Deploy complete"
+```
+
+### setup.sh vs deploy.sh Distinction
+
+- `setup.sh` — runs once at first install: creates directories, sets permissions, initializes the database, sets up systemd services
+- `deploy.sh` — runs on every update: copies code, rebuilds containers, reloads servers
+
+Never run `setup.sh` after initial setup — it may overwrite config files or re-initialize databases.
+
+## Related Concepts
+
+- [[wiki/concepts/msal-vanilla-js-pkce]] — deploy pattern for social-reporting-tool where this was encountered
+- [[wiki/architecture/docker-compose-patterns]] — container restart patterns that work alongside deploy scripts
+
+## Sources
+
+- [[daily/2026-04-15.md]] — Fixed broken deploy scripts in social listening project (commit `7a70283`)
diff --git a/wiki/connections/_index.md b/wiki/connections/_index.md
index abf5682..0f913a7 100644
--- a/wiki/connections/_index.md
+++ b/wiki/connections/_index.md
@@ -5,6 +5,7 @@
| Article | Connects | Source | Updated |
|---------|---------|--------|---------|
+| [[wiki/connections/oauth-state-mismatch-debugging]] | LibreChat OpenID ↔ MSAL SPA ↔ Azure AD — state mismatch root cause shared across implementations | daily/2026-04-15.md | 2026-04-15 |
diff --git a/wiki/connections/oauth-state-mismatch-debugging.md b/wiki/connections/oauth-state-mismatch-debugging.md
new file mode 100644
index 0000000..ca81277
--- /dev/null
+++ b/wiki/connections/oauth-state-mismatch-debugging.md
@@ -0,0 +1,43 @@
+---
+title: "Connection: OAuth State Mismatch — LibreChat vs MSAL SPA"
+connects:
+ - "concepts/librechat-openid-auth"
+ - "concepts/msal-vanilla-js-pkce"
+ - "tech-patterns/azure-ad-msal-auth"
+sources:
+ - "daily/2026-04-15.md"
+created: 2026-04-15
+updated: 2026-04-15
+---
+
+# Connection: OAuth State Mismatch — LibreChat vs MSAL SPA
+
+## The Connection
+
+Both LibreChat's built-in OpenID integration and custom MSAL.js SPA implementations use Azure AD OAuth with state validation. OAuth state mismatch — where the `state` parameter returned by Azure doesn't match what's in the session — manifests very differently in each system but has the same root causes.
+
+## Key Insight
+
+In LibreChat, a state mismatch surfaces as `Unknown OAuth error` (the session message array is empty). In a custom MSAL.js SPA, a state mismatch surfaces as a silent redirect to an error page or a blank callback. In both cases, the fix is at the **session layer** (clear stale cookies, try a different browser), not in Azure AD configuration. This is non-obvious because the error points away from the actual cause.
+
+The shared root cause: if the server restarts between OAuth initiation (`/oauth/openid`) and the callback (`/oauth/callback`), the session store loses the original state. Azure sends back the correct state, but the server-side session no longer has it to compare against — the check fails.
+
+## Evidence
+
+- **LibreChat (2026-04-15):** User `santhosha.nayak@brandtech.plus` could not log in. Microsoft auth succeeded (user saw Microsoft login, approved it). LibreChat showed `Unknown OAuth error`. Network tab showed: `302 → /oauth/openid → Microsoft OK → /oauth/callback → /oauth/error`. Root cause: stale session cookie from a previous attempt after server restart. Resolution path: try different browser / clear cookies.
+- **MSAL SPA (2026-04-15):** MSAL.js v5 UMD in a plain-HTML dashboard. MSAL handles state internally via `sessionStorage`. If `sessionStorage` is cleared between initiation and callback (e.g., navigation, redirect loops), MSAL throws a state mismatch error client-side. MSAL's internal handling is more transparent than LibreChat's.
+
+## Diagnostic Heuristic
+
+When OAuth fails after Microsoft auth succeeds:
+
+1. Check if this is a first attempt or a retry — retries are more likely to hit state mismatch
+2. Check if the server recently restarted
+3. Try from a different browser / incognito window
+4. Only then investigate Azure AD app registration, Conditional Access, or account status
+
+## Related Concepts
+
+- [[wiki/concepts/librechat-openid-auth]] — LibreChat-specific auth internals and ALLOW_SOCIAL_REGISTRATION
+- [[wiki/concepts/msal-vanilla-js-pkce]] — MSAL.js v5 UMD SPA implementation
+- [[wiki/tech-patterns/azure-ad-msal-auth]] — Azure AD patterns across Oliver projects
diff --git a/wiki/dotfiles/_index.md b/wiki/dotfiles/_index.md
index 3182bba..5229e6c 100644
--- a/wiki/dotfiles/_index.md
+++ b/wiki/dotfiles/_index.md
@@ -1,3 +1,11 @@
+---
+title: "Dotfiles & Terminal Setup Index"
+description: "Linux terminal customization, shell configs, CLI tool setups, and ricing guides"
+tags: [index, dotfiles, terminal, wezterm, fish, cli]
+created: 2026-04-15
+updated: 2026-04-17
+---
+
# Dotfiles & Terminal Setup
Linux terminal customization, shell configs, CLI tool setups, and ricing guides.
diff --git a/wiki/llm-models/_index.md b/wiki/llm-models/_index.md
index 6b4d2e1..96e28dc 100644
--- a/wiki/llm-models/_index.md
+++ b/wiki/llm-models/_index.md
@@ -5,3 +5,4 @@
| Article | Summary | Source | Updated |
|---------|---------|--------|---------|
| [[wiki/llm-models/openai-model-catalog\|OpenAI Model Catalog]] | All OpenAI API models: GPT-5.x, o-series reasoning, audio/realtime, embeddings, moderation | OpenAI API Docs | 2026-04-17 |
+| [[wiki/llm-models/claude-model-catalog\|Claude Model Catalog]] | Opus/Sonnet/Haiku 4.x — IDs, context windows, when to use each, API usage, Fast Mode | Claude Code system context | 2026-04-17 |
diff --git a/wiki/llm-models/claude-model-catalog.md b/wiki/llm-models/claude-model-catalog.md
new file mode 100644
index 0000000..c165dfb
--- /dev/null
+++ b/wiki/llm-models/claude-model-catalog.md
@@ -0,0 +1,66 @@
+---
+title: "Claude / Anthropic Model Catalog"
+aliases: [claude-models, anthropic-models, opus-sonnet-haiku]
+tags: [claude, anthropic, models, llm, ai]
+sources: [knowledge cutoff Aug 2025 + Claude Code system context]
+created: 2026-04-17
+updated: 2026-04-17
+---
+
+## Model Families
+
+| Family | Tier | Best for |
+|--------|------|----------|
+| Opus 4.x | Most capable | Complex reasoning, architecture, long-form writing |
+| Sonnet 4.x | Balanced | Coding, agentic tasks, daily work — best cost/performance |
+| Haiku 4.x | Fast & cheap | Logging, classification, summaries, bulk operations |
+
+## Current Model IDs (Claude 4.x)
+
+| Model | ID | Context | Notes |
+|-------|----|---------|-------|
+| **Opus 4.7** | `claude-opus-4-7` | 200K | Most powerful. Fast Mode available (same model, faster output) |
+| **Sonnet 4.6** | `claude-sonnet-4-6` | 200K | Default in Claude Code. Best everyday coding model |
+| **Haiku 4.5** | `claude-haiku-4-5-20251001` | 200K | Use for logging/notes in Obsidian vault (per CLAUDE.md) |
+
+## When to Use Each Model
+
+**Opus** — multi-step reasoning, large refactors, security reviews, novel architecture decisions
+
+**Sonnet** — default for all coding tasks; Claude Code uses this; agentic pipelines, tool use, RAG
+
+**Haiku** — Obsidian session logging, bulk text processing, classification, any "just write text" task; cheapest
+
+## Claude Code Context
+
+- Claude Code CLI currently runs on **Sonnet 4.6** by default
+- `/fast` toggles Fast Mode on Opus 4.6 (faster output, same quality)
+- Memory compiler `flush.py` / `compile.py` should use Sonnet; **Obsidian logging uses Haiku** (per vault CLAUDE.md)
+- Agent SDK: pass model ID in `ClaudeAgentOptions` → `model` field
+
+## API Usage
+
+```python
+from anthropic import Anthropic
+
+client = Anthropic()
+response = client.messages.create(
+ model="claude-sonnet-4-6", # or opus-4-7, haiku-4-5-20251001
+ max_tokens=1024,
+ messages=[{"role": "user", "content": "Hello"}]
+)
+```
+
+## Key Takeaways
+
+- Default to **Sonnet 4.6** for agentic/coding work — best balance
+- Use **Haiku** for any high-volume or low-stakes generation (logging, summaries) to cut costs
+- Use **Opus** only when the task genuinely requires maximum reasoning
+- Model IDs include version suffix — always use full ID in code to avoid silent upgrades
+- All 4.x models support 200K context window
+
+## Related
+
+- [[wiki/agent-sdk/overview|Agent SDK Overview]] — how to pass model to ClaudeAgentOptions
+- [[wiki/claude-code/overview|Claude Code Overview]] — default model and Fast Mode
+- [[wiki/llm-models/openai-model-catalog|OpenAI Model Catalog]] — comparison reference
diff --git a/wiki/log.md b/wiki/log.md
index 2ebfa11..bbd138f 100644
--- a/wiki/log.md
+++ b/wiki/log.md
@@ -12,3 +12,8 @@
- KNOWLEDGE_DIR relocated from ~/.claude/memory-compiler/knowledge/ to /wiki/
- All three hooks promoted to global (fire from any project directory)
- Compile time set to 21:00 (9 PM) BST
+
+## [2026-04-17T16:03:42+01:00] compile | 2026-04-15.md
+- Source: daily/2026-04-15.md
+- Articles created: [[wiki/concepts/librechat-openid-auth]], [[wiki/concepts/msal-vanilla-js-pkce]], [[wiki/concepts/shell-static-deploy-patterns]], [[wiki/concepts/fish-shell-path-config]], [[wiki/connections/oauth-state-mismatch-debugging]]
+- Articles updated: [[wiki/tech-patterns/azure-ad-msal-auth]] (added SPA platform type gotcha, OAuth state mismatch gotcha, daily/2026-04-15.md source)
diff --git a/wiki/tech-patterns/azure-ad-msal-auth.md b/wiki/tech-patterns/azure-ad-msal-auth.md
index bfad756..81ddd2d 100644
--- a/wiki/tech-patterns/azure-ad-msal-auth.md
+++ b/wiki/tech-patterns/azure-ad-msal-auth.md
@@ -2,7 +2,14 @@
title: "Azure AD / MSAL Authentication"
aliases: [azure-ad, msal, sso, pkce]
tags: [azure-ad, msal, auth, sso, pkce, security]
-sources: [01 Projects/gmal-scope-builder, 01 Projects/enterprise-ai-hub-nexus, 01 Projects/cinema-studio-pro, 01 Projects/modcomms, 01 Projects/baic_dashboard, 01 Projects/sandbox-notebookllamalm-nextjs]
+sources:
+ - "01 Projects/gmal-scope-builder"
+ - "01 Projects/enterprise-ai-hub-nexus"
+ - "01 Projects/cinema-studio-pro"
+ - "01 Projects/modcomms"
+ - "01 Projects/baic_dashboard"
+ - "01 Projects/sandbox-notebookllamalm-nextjs"
+ - "daily/2026-04-15.md"
created: 2026-04-15
updated: 2026-04-15
---
@@ -76,6 +83,8 @@ instance.clearCache()
- Azure App Registration must have the delegated scopes explicitly added for M365 features
- `offline_access` scope required for refresh token to work (Enterprise Nexus M365)
- Proactive token refresh needed for long-running Celery jobs (Enterprise Nexus SharePoint sync)
+- **SPA platform type is required for PKCE** — Azure Portal → Authentication → Platform configurations must be set to "Single-page application", NOT "Web". "Web" expects a client secret; PKCE SPAs don't have one → silent auth failure (2026-04-15)
+- **OAuth state mismatch** after server restart: session cookie from previous attempt doesn't match new state — always ask user to try different browser before deep-diving Azure config (2026-04-15)
## Related
- [[wiki/tech-patterns/fastapi-python-docker|fastapi-python-docker]] — backend receiving tokens