wiki: auto-compile 2026-05-15 (1 log(s), 254 articles)

This commit is contained in:
Vadym Samoilenko 2026-05-15 10:15:54 +01:00
parent 7c2d50931e
commit a34ce64f00
16 changed files with 648 additions and 2 deletions

View file

@ -10,3 +10,7 @@ tags: [daily]
- 10:10 (<1min) session ended | `memory-compiler`
- 10:11 — session ended | `memory-compiler`
- 10:12 (<1min) session ended | `memory-compiler`
- 10:14 (<1min) session ended | `memory-compiler`
- 10:14 (<1min) session ended | `memory-compiler`
- 10:15 (<1min) session ended | `memory-compiler`
- 10:15 (<1min) session ended | `memory-compiler`

View file

@ -23,7 +23,7 @@ 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, Redis/Celery, cost-tracker, OMG API | 29 |
| [[wiki/architecture/_index\|architecture/]] | Cross-cutting architectural patterns: Docker Compose, multi-agent AI, GCP timeout, RAG, hotfolder, optical-dev deploy, cost-tracker, new-project checklist, troubleshooting playbooks, ADR log, Cloud Run Jobs | 11 |
| [[wiki/client-knowledge/_index\|client-knowledge/]] | Per-client notes for Ford, H&M, L'Oréal, Barclays, Ferrero, 3M, BAIC | 7 |
| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 196 |
| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 197 |
| [[wiki/connections/_index\|connections/]] | Cross-cutting insights linking 2+ concepts: FastAPI+Azure AD+Docker trinity, AI→cost-tracker, Apache+Vite basePath, GCP→REST polling, Box+hotfolder, Docker DNS+AdGuard, Celery prefork×faster_whisper memory stacking | 10 |
| [[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, HP Elitedesk G3, Homarr API + Apps + Boards + Certificates + Integrations + Settings + Tasks + AdGuard + Clock + Docker Stats + Docker Integration + Download Client + Firewall + Proxmox Integration + Radarr + Readarr + Sonarr + Bookmarks + Calendar + Icons + App Widget + Weather + GitHub + Nextcloud + qBittorrent + RSS Feed + Speedtest Tracker + System Health Monitoring + System Resources + Services Map + Media Stack | 43 |
@ -31,7 +31,7 @@ This 3-hop pattern works for hundreds of articles without vector search.
| [[wiki/dotfiles/_index\|dotfiles/]] | Linux terminal ricing: Kitty, Fish, WezTerm CLI, modern Rust CLI tools, LazyVim, unified themes, Tabby, Aerospace WM | 22 |
| [[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/]] | LLM model catalogs — OpenAI, Claude/Anthropic, and Google Gemini models, IDs, context, pricing | 3 |
| [[wiki/claude-code/_index\|claude-code/]] | Claude Code product docs — install, capabilities, surfaces, MCP, hooks, scheduling, multi-agent, plugins, skills, channels, error recovery, LM Studio local, Figma MCP | 33 |
| [[wiki/claude-code/_index\|claude-code/]] | Claude Code product docs — install, capabilities, surfaces, MCP, hooks, scheduling, multi-agent, plugins, skills, channels, error recovery, LM Studio local, Figma MCP, global rules, local config | 36 |
| [[wiki/reports/_index\|reports/]] | Weekly and monthly summaries — generate: `uv run python scripts/report-generator.py --weekly` | 1 |
| [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 11 |

View file

@ -47,3 +47,6 @@ Claude Code is Anthropic's agentic coding assistant. Works across terminal, IDE,
| [[wiki/claude-code/claude-skills-guide\|claude-skills-guide]] | Complete guide to building Claude Skills: SKILL.md structure, YAML frontmatter, progressive disclosure, MCP integration, testing, distribution, 5 workflow patterns | raw/file_EE003DA9-270B-4159-A329-C89962F85415.pdf | 2026-05-06 |
| [[wiki/claude-code/figma-mcp-custom-rules\|figma-mcp-custom-rules]] | Project-level CLAUDE.md rules for Figma MCP: required tool flow (get_design_context → get_screenshot → implement → validate), asset rules, auto-generate rules prompt | raw/Add custom rules and instructions.md | 2026-05-15 |
| [[wiki/claude-code/figma-mcp-avoid-large-frames\|figma-mcp-avoid-large-frames]] | Select components/chunks, not full screens — large selections slow tools, cause errors, or truncate output; how to debug output quality | raw/Avoid selecting large, heavy frames.md | 2026-05-15 |
| [[wiki/claude-code/global-rules-claude-md\|global-rules-claude-md]] | Global CLAUDE.md rules: core principles, workflow (plan-first, subagents, verify), escalation, change limits, skills/MCP routing, git, code reuse, secrets, server ops | raw/Claude Code Global Rules (CLAUDE.md).md | 2026-05-15 |
| [[wiki/claude-code/local-machine-config\|local-machine-config]] | CLAUDE.local.md: Obsidian vault path + folder structure, Mailgun domain gotcha (mg.oliver.solutions), Oliver VPN Tunnelblick check snippet | raw/Claude Code Local Config (CLAUDE.local.md).md | 2026-05-15 |
| [[wiki/claude-code/figma-mcp-setup\|figma-mcp-setup]] | Figma MCP server setup in Claude Code: remote (plugin install) vs desktop (Dev Mode + local URL); auth flow, what tools are unlocked | raw/Claude Code and Figma Set up the MCP server.md | 2026-05-15 |

View file

@ -0,0 +1,76 @@
---
title: "Figma MCP Server — Setup in Claude Code"
aliases: [figma-mcp-setup, figma-claude-code-setup]
tags: [figma, mcp, claude-code, setup, design]
sources: [raw/Claude Code and Figma Set up the MCP server.md]
created: 2026-05-15
updated: 2026-05-15
---
# Figma MCP Server — Setup in Claude Code
Gives Claude Code structured access to Figma: read components/variables/layout, generate code from frames, use Code Connect, and write native content back to the canvas.
## Two Versions
| Version | When to use |
|---------|-------------|
| **Remote** (recommended) | Most users — broadest feature set |
| **Desktop** | Specific org/enterprise use cases only |
## Remote Setup (Preferred)
Install the Figma plugin (includes MCP server + Agent Skills):
```bash
claude plugin install figma@claude-plugins-official
```
Then:
1. Restart Claude Code
2. Run `/plugin` → navigate to **Installed** tab
3. Select `figma` → press Enter → authenticate via browser
4. Click **Allow access**
5. Run `/plugin` again — `figma` should show as **connected**
## Desktop Setup
Only if remote is unavailable (org/enterprise).
**In Figma desktop app:**
1. Open a Design file → switch to Dev Mode (toolbar toggle)
2. Enable MCP server in right sidebar → copy the URL
**In Claude Code terminal:**
```bash
claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp
```
Restart Claude Code, then verify with `/mcp`.
## What the MCP Server Provides
- Read: components, variables, layout data, FigJam content, Make resources
- Generate code from selected frames
- Code Connect — keeps generated code aligned with real components
- Write to canvas — create/update native Figma content
- Send live web interfaces to Figma as editable layers
## Key Takeaways
- Use the **remote** version unless you have an explicit org/enterprise reason for desktop
- Install via `claude plugin install figma@claude-plugins-official` — this is the recommended path
- After install, authenticate once via browser OAuth; stays connected across sessions
- Desktop version requires Figma desktop app + Dev Mode + manually copying the local URL
- After setup, test with `/mcp` (desktop) or `/plugin` (remote) to confirm connection
## Related Articles
- [[wiki/claude-code/mcp-integration|MCP Integration in Claude Code]]
- [[wiki/claude-code/figma-mcp-custom-rules|Figma MCP Custom Rules (CLAUDE.md)]]
- [[wiki/claude-code/figma-mcp-avoid-large-frames|Avoid Selecting Large Figma Frames]]
- [[wiki/claude-code/plugin-marketplaces|Plugin Marketplaces]]
## Sources
- `raw/Claude Code and Figma Set up the MCP server.md`
- Official: https://help.figma.com/hc/en-us/articles/39888612464151

View file

@ -0,0 +1,253 @@
---
title: "Global Claude Code Rules (CLAUDE.md)"
aliases: [claude-md-rules, global-rules, claude-global-config]
tags: [claude-code, configuration, workflow, rules, skills, mcp]
sources: [raw/Claude Code Global Rules (CLAUDE.md).md]
created: 2026-05-15
updated: 2026-05-15
---
# Global Claude Code Rules (CLAUDE.md)
The global `CLAUDE.md` file sets the operating contract for Claude Code across all projects. It enforces engineering discipline, context hygiene, and consistent conventions.
---
## Core Principles
- **Simplicity first** — minimum code that solves the task; no speculative abstractions.
- **No laziness** — fix root cause, not symptoms; senior-dev standard.
- **Minimal blast radius** — change only what's necessary; smaller diff = fewer regressions.
---
## Workflow Rules
| Rule | Detail |
|------|--------|
| **Plan-first** | 3+ steps or architecture → plan mode. Exceptions: typos, renames, one-liners. |
| **Subagents over main context** | Research/exploration/parallel analysis → Agent tool. Non-trivial impl → `isolation: "worktree"`. |
| **Verify before done** | Backend: `pytest` + type check. Frontend: `npm run build` + lint. DB: `alembic upgrade head` + rollback. Docker: build + up + healthcheck. |
| **Demand elegance** | Pause before finalizing: "is there a more elegant solution?" Skip for hotfixes/one-liners/config changes. |
| **Autonomous bug fixing** | Log/failed test → read trace, fix, re-run immediately. Never ask "how to fix." |
| **Explain non-trivial changes** | 13 lines high-level after any non-trivial edit. |
---
## Escalation Thresholds
- **3 failed fix attempts** → STOP, show what was tried, ask for direction.
- **Breaking change** (public API / schema migration / breaking rename) → confirm before applying.
- **Uncertain root cause** → show 23 hypotheses, ask.
---
## Change Limits
- **>5 files or >200 lines diff** → pause, show plan, confirm.
- **Unrequested refactoring** → don't do it; offer as a separate task.
---
## Token Discipline
- **20+ messages OR noticeable slowdown** → suggest `/compact`.
- **Topic switch** (different project/feature) → new session, not `/compact`.
- **Subagent returned >2000 words** → summarize to 200 words before inserting into context.
---
## Self-Improvement Loop
```
PreCompact/SessionEnd hooks → flush.py → daily/YYYY-MM-DD.md
→ compile.py (21:00) → wiki/concepts/ in Obsidian
→ SessionStart injects back
```
**Guaranteed lesson persistence** — write in final reply:
```
Lesson: <rule>
Reason: <reason>
Scope: global | project:<name> | skill:<name> | shared:<pattern>
```
`compile.py` routes `Scope: shared:*``wiki/shared-patterns/`.
- **Session tasks:** `TaskCreate` / `TodoWrite`. Never create `tasks/todo.md`.
- **Session logs** are written by Stop/SessionEnd hooks — don't duplicate manually.
---
## Language Policy
- Respond to user in **Russian**.
- Code, comments, file names, commits, variables, docs — strictly **English**.
---
## Don'ts
- No `try/except` without specific error handling.
- No files created "for the future" (empty modules, placeholder configs).
- No formatting/style changes in files unrelated to the task.
- No logic duplication — find existing util/helper first.
- Never say "I can't" — find a workaround, then report the limitation.
---
## Skills Routing
### Always Active (Obsidian)
| Skill | When |
|-------|------|
| `obsidian:defuddle` | Instead of WebFetch for any URL (articles, docs). Skip: `.md` URLs, raw APIs. |
| `obsidian:obsidian-markdown` | Creating/editing `.md` in Obsidian vault. |
| `obsidian:obsidian-cli` | Reading/writing/searching vault notes via CLI. |
| `obsidian:obsidian-bases` | `.base` files (database views). |
| `obsidian:json-canvas` | `.canvas` files. |
### By Trigger
| Trigger | Skill |
|---------|-------|
| `.py`, script, module | `fullstack-dev-skills:python-pro` |
| FastAPI routes, Pydantic | `fullstack-dev-skills:fastapi-expert` |
| Auth, JWT, Azure AD, OWASP | `fullstack-dev-skills:security-reviewer` |
| React, `.tsx`, Vite | `fullstack-dev-skills:react-expert` |
| TypeScript types, generics | `fullstack-dev-skills:typescript-pro` |
| UI/UX decisions, layout, design | `frontend-design:frontend-design` |
| Dockerfile, docker-compose, GCP, nginx | `fullstack-dev-skills:devops-engineer` |
| SQL, schema, migrations, PostgreSQL | `fullstack-dev-skills:postgres-pro` |
| Tests, mocking | `fullstack-dev-skills:test-master` |
| E2E, Playwright | `fullstack-dev-skills:playwright-expert` |
| RAG, vector DB, embeddings | `fullstack-dev-skills:rag-architect` |
| Prompt design, Claude/OpenAI API | `fullstack-dev-skills:prompt-engineer` |
| Full docs from scratch | `documentation-pipeline:ln-100-documents-pipeline` |
| Code review (file/diff) | `fullstack-dev-skills:code-reviewer` |
| PR review | `pr-review-toolkit:review-pr` |
| Stack trace, root cause | `fullstack-dev-skills:debugging-wizard` |
**Conflict resolution:** multiple skill match → primary = main action; secondary = consult only.
---
## MCP Routing
| MCP | When |
|-----|------|
| `context7` | Docs for FastAPI, Next.js, React, etc. |
| `basic-memory` | Save solution mid-session / recall past-session context |
| `postgres` | Direct SQL queries to project DB without writing scripts |
| `redis` | Redis operations (cc-dashboard, ai-cost-tracker, video-accessibility) |
| `sequential-thinking` | Complex multi-step architectural tasks |
Built into harness (no config needed): Playwright, Figma MCP, Magic.
---
## Second Brain — Obsidian Vault
**Before asking user — search vault first.**
**Lookup order:**
1. Session-start context → do NOT re-read (already in window).
2. `Read 01 Projects/<project>/*.md` → only if task is project-specific.
3. `Read wiki/_master-index.md` → only if pattern/architecture needed.
4. Ask user → last resort if info truly absent.
---
## Git Rules
- **Commit format:** `type(scope): description` (conventional commits)
- **Types:** `feat`, `fix`, `refactor`, `docs`, `chore`, `test`
- **Branch naming:** `feature/<short-desc>`, `fix/<short-desc>`
- **Atomic commits:** one concern = one commit
- **Worktrees:** multiple branches + new task → offer worktree; Agent for non-trivial impl → `isolation: "worktree"`
- **Agent conflicts:** two agents must NOT edit the same file; if unavoidable — sequential, not parallel
---
## Dependencies Policy
- New package → check: existing analogue in project; size; license; last commit.
- Prefer stdlib / already-installed packages.
- Adding dependency → explicitly notify user.
---
## Secrets Policy
- **NEVER** hardcode secrets in code, commits, logs.
- Before commit → check diff for keys/passwords/tokens.
- `.env` files → always in `.gitignore`. Create `.env.example` with placeholders.
- Found leaked secret → immediately notify user.
---
## Server Operations
- **NEVER** run commands on remote servers without explicit user instruction.
- **NEVER** destructive commands (`rm -rf`, `DROP TABLE`, `TRUNCATE`, `git push --force`) without explicit confirm.
- **Production DB**`SELECT` only. Any write → confirm + remind about backup.
---
## Code Reuse
### Search Before Write
1. Search current project → `grep`/`rg` by functionality.
2. Search vault `wiki/shared-patterns/` → already-solved patterns.
3. Search shared-libs → ready package.
4. Search neighboring projects (subagent) → similar implementation.
5. Only if 14 empty → write from scratch.
### Standard Library Choices
| Task | Standard | Avoid |
|------|----------|-------|
| HTTP client (Python) | `httpx` | requests, aiohttp |
| HTTP client (TS) | `fetch` / `ky` | axios |
| Validation (Python) | `pydantic` | marshmallow, attrs |
| Date handling (TS) | `date-fns` | moment, dayjs |
| Env config (Python) | `pydantic-settings` | python-dotenv directly |
| Logging (Python) | `structlog` | print, stdlib logging |
| Task queue | `celery` / `arq` | custom implementations |
| DB migrations | `alembic` (Python) | raw SQL scripts |
### Anti-Duplication
- Copy-paste spotted inside project → offer extract (don't do silently).
- Pattern repeated in 3+ projects → flag: "worth moving to shared".
- Never sync copies manually — divergence = signal to extract shared package.
---
## Key Takeaways
- All Claude Code sessions operate under these rules globally via `~/.claude/CLAUDE.md`.
- Plan mode is mandatory for 3+ step tasks; subagents protect main context.
- Verification is non-negotiable before marking work done.
- Skills are invoked by trigger (file type, task type) — always before starting work.
- Git uses conventional commits + atomic changes; worktrees for parallel agent work.
- Code reuse: search 4 places before writing from scratch; standard library table is authoritative.
- Session self-improvement loop: hooks → flush.py → compile.py → Obsidian wiki → re-injected next session.
---
## Related
- [[wiki/claude-code/dot-claude-folder|dot-claude-folder]] — `.claude/` folder structure (CLAUDE.md location, hooks, skills, agents)
- [[wiki/claude-code/skills|skills]] — Skills system: creation, storage scopes, invocation
- [[wiki/claude-code/custom-subagents|custom-subagents]] — Subagent patterns referenced in workflow rules
- [[wiki/claude-code/oliver-skills-config|oliver-skills-config]] — Oliver Agency skills selection rationale
- [[wiki/claude-code/scheduled-tasks|scheduled-tasks]] — `/loop`, `CronCreate` tools for session scheduling
- [[wiki/architecture/new-project-checklist|new-project-checklist]] — New project stub note procedure
---
## Sources
- `raw/Claude Code Global Rules (CLAUDE.md).md`

View file

@ -0,0 +1,70 @@
---
title: "Claude Code Local Machine Config (CLAUDE.local.md)"
aliases: [claude-local-config, claude-local-md, local-config]
tags: [claude-code, config, obsidian, mailgun, vpn, oliver]
sources: [raw/Claude Code Local Config (CLAUDE.local.md).md]
created: 2026-05-15
updated: 2026-05-15
---
# Claude Code Local Machine Config (CLAUDE.local.md)
Local overrides for the global [[wiki/claude-code/global-rules-claude-md|CLAUDE.md]] — machine-specific paths, credentials, and infrastructure details that must not be committed.
---
## Obsidian Vault
**Path:** `~/Library/Mobile Documents/iCloud~md~obsidian/Documents/VadymSamoilenko/`
| Folder | Contents |
|--------|----------|
| `01 Projects/<name>/` | Tech stack, server, deploy command, URL, session notes |
| `wiki/` | Knowledge base: tech patterns, architecture, concepts |
| `02 Areas/` | Pending commands, homelab config |
| `99 Daily/` | Session logs |
See [[wiki/obsidian-rag/_index|obsidian-rag/]] for how Claude Code navigates the vault.
---
## Email — Mailgun
- **Credentials:** `source ~/.secrets/mailgun.env`
- **Base URL:** `https://api.mailgun.net/v3`
- Always add to `.env` / `.env.example` automatically; `.env` always in `.gitignore`
> **Gotcha:** domain must be `mg.oliver.solutions`, **not** `oliver.solutions`.
> Wrong domain → Mailgun returns 404/401 → Python wrapper silently returns `False` → generic 500 error upstream.
---
## Oliver VPN
Must be connected before any SSH or deploy to Oliver servers:
- `optical-dev`, `optical-web-1`, `baic`, `box-cli`, `10.220.168.*`
**Check + auto-connect (Tunnelblick):**
```bash
VPN_STATE=$(osascript -e 'tell application "Tunnelblick" to get state of first configuration where name contains "Oliver"' 2>/dev/null)
[ "$VPN_STATE" != "CONNECTED" ] && osascript -e 'tell application "Tunnelblick" to connect "Oliver VPN"' && sleep 5
```
Run this before any SSH/deploy command targeting Oliver infrastructure. See [[wiki/infrastructure/_index|infrastructure/]] for the full server inventory.
---
## Key Takeaways
- `CLAUDE.local.md` holds machine-specific config that overrides global `CLAUDE.md` — never commit it
- Obsidian vault lives in iCloud at a known path; folder structure is: Projects / wiki / Areas / Daily
- Mailgun domain must be `mg.oliver.solutions` — wrong domain causes silent 404/401 → 500
- Always check Oliver VPN (Tunnelblick) before SSH/deploy to any Oliver server
- The osascript snippet auto-connects Tunnelblick if not already connected
---
## Sources
- `raw/Claude Code Local Config (CLAUDE.local.md).md`

View file

@ -57,6 +57,9 @@ ssh baic "sudo systemctl status baic_dashboard.service"
| SSE needs `?token=` | `EventSource` can't set custom headers | [[wiki/concepts/sse-jwt-query-param]] |
| SPA index.html must be no-cache | Old hashes cause blank screen after rebuild | [[wiki/concepts/spa-index-html-cache-control]] |
| MS SSO IDs are not UUIDs | `ms-4n0T2x-...` format breaks UUID validators | [[wiki/concepts/microsoft-sso-non-uuid-ids]] |
| Mailgun sending domain | Must use `mg.oliver.solutions`, NOT `oliver.solutions` — wrong subdomain causes silent failure (wrapper returns `False`, endpoint returns 500) | [[wiki/concepts/mailgun-sending-domain-oliver]] |
| FastAPI lifespan service access | Services initialised in `lifespan()` must be accessed via `request.app.state.service_name`, not imported from `app.main` | [[wiki/concepts/fastapi-lifespan-locals-app-state]] |
| Multi-layer rename (agent/role) | Renaming any AI agent or modcomms role requires simultaneous update: frontend label + TS union type + Python class attr + backend dict key + DB seed row | — |
## Features

View file

@ -241,5 +241,9 @@
| [[wiki/concepts/docker-compose-force-recreate]] | `docker compose up -d` after build keeps old container running — `--force-recreate` required to apply new image | daily/2026-05-13.md | 2026-05-13 |
| [[wiki/concepts/next-app-router-favicon]] | Next.js App Router favicon requires both `public/favicon.ico` AND `src/app/icon.png` (512×512) — one file alone is insufficient | daily/2026-05-13.md | 2026-05-13 |
| [[wiki/concepts/pil-photo-compression-pipeline]] | PIL photo pipeline: `thumbnail((1200,1200), LANCZOS)` + JPEG 82 progressive; re-encoding already-compressed JPEG gives no size benefit | daily/2026-05-13.md | 2026-05-13 |
| [[wiki/concepts/fastapi-lifespan-locals-app-state]] | `lifespan()` local vars are NOT module exports — access services via `request.app.state.service_name`, never `from app.main import service` | daily/2026-05-14.md | 2026-05-15 |
| [[wiki/concepts/apache-redirect-suffix-preservation]] | `RedirectMatch permanent ^/path(/.*)?$ https://dest/path$1` — suffix-preserving 301 for service deactivation; replaces ProxyPass+Alias+Directory block | daily/2026-05-14.md | 2026-05-15 |
| [[wiki/concepts/cloudflare-cdn-cache-corruption]] | HTTP 200 + `img.naturalHeight === 0` = CDN serving corrupt cached stub; replacing file on disk has no effect without cache purge | daily/2026-05-14.md | 2026-05-15 |
| [[wiki/concepts/html5-video-source-order-ios]] | iOS Safari requires `playsInline` + `muted` for inline autoplay; `<source>` order matters — MP4 must be listed before WebM or Safari stalls | daily/2026-05-14.md | 2026-05-15 |
<!-- Articles added automatically by compile.py -->
<!-- Format: | [[concepts/slug]] | One-line summary | daily/YYYY-MM-DD.md | date | -->

View file

@ -0,0 +1,61 @@
---
title: "Apache RedirectMatch — Suffix-Preserving 301 for Service Deactivation"
tags: [apache, redirect, regex, devops, oliver]
source: daily/2026-05-14.md
created: 2026-05-15
---
# Apache RedirectMatch — Suffix-Preserving 301 for Service Deactivation
## Pattern
When deactivating a path served by `ProxyPass` (e.g., moving a service off a subdomain), replace the entire `ProxyPass + Alias + Directory` block with a single `RedirectMatch`:
```apache
# BEFORE (active service)
ProxyPass /video-accessibility http://localhost:3001/
ProxyPass /video-accessibility/ http://localhost:3001/
Alias /video-accessibility /var/vhosts/.../video-accessibility
<Directory /var/vhosts/.../video-accessibility>
...
</Directory>
# AFTER (service moved to optical-dev)
RedirectMatch permanent ^/video-accessibility(/.*)?$ https://optical-dev.oliver.solutions/video-accessibility$1
```
## Key Details
| Part | Meaning |
|------|---------|
| `^/video-accessibility` | Anchor to exact path prefix |
| `(/.*)?` | Capture group 1: optional trailing slash + anything |
| `$` | End of URI |
| `$1` | Append captured suffix to destination URL |
## Why the capture group matters
Without `(/.*)?$1`, deep links break silently:
```
# Without suffix capture:
/video-accessibility/jobs/abc123 → https://optical-dev.../video-accessibility
# (suffix lost — 404 on destination)
# With suffix capture:
/video-accessibility/jobs/abc123 → https://optical-dev.../video-accessibility/jobs/abc123
```
## When to use
- Service relocated to a different server/subdomain, but old URLs must keep working
- Deactivating a vhost path without breaking bookmarks or SEO
- Simpler than maintaining a dead ProxyPass target
## Reload
```bash
sudo apachectl configtest && sudo systemctl reload apache2
```
Always run `configtest` first — a regex syntax error silently 500s the entire vhost.

View file

@ -0,0 +1,59 @@
---
title: "Cloudflare CDN Cache Corruption — naturalHeight:0 Diagnostic"
tags: [cloudflare, cdn, cache, debugging, media, payload-cms]
source: daily/2026-05-14.md
created: 2026-05-15
---
# Cloudflare CDN Cache Corruption — `naturalHeight: 0` Diagnostic
## Symptom
- File replaced on disk (or re-uploaded via CMS)
- HTTP response: `200 OK`, `content-type: image/jpeg` (or video/mp4)
- Browser still renders broken image / blank video
- Response headers include `cache-control: max-age=14400` and `cf-cache-status: HIT`
- File is 896 bytes (or similarly tiny stub) despite correct size on origin
## Root Cause
Cloudflare cached a corrupt or stub response (e.g., a partial upload, a 0-byte placeholder) and continues serving it with HTTP 200. Replacing the file on the origin disk has **zero effect** until the cached response is purged.
## Diagnostic — JavaScript
```javascript
// Run in browser console against any image URL suspected of CDN corruption
const img = new Image()
img.onload = () => console.log('naturalHeight:', img.naturalHeight)
img.onerror = () => console.log('load error')
img.src = 'https://example.com/media/video-thumbnail.jpg'
// naturalHeight === 0 with HTTP 200 → CDN serving corrupt cached stub
// naturalHeight > 0 → image is valid
```
## Fix Options
| Option | Command / Action | Notes |
|--------|-----------------|-------|
| **Purge via Cloudflare dashboard** | Zone → Caching → Purge Cache → Custom URLs | Requires Cloudflare account access |
| **Purge via API** | `curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache"` | Fastest; scriptable |
| **Delete CMS record** | Delete the media entry in Payload CMS (or equivalent) | Frontend falls back to static imports that bypass the poisoned URL |
| **Rename the file** | Upload with a new filename — new URL, no cached entry | Last resort; breaks existing links |
## Payload CMS Workaround
When CDN cache purge is not immediately possible:
1. Delete the media document in Payload CMS admin
2. Commit static asset to repo → frontend `import` uses bundled file (bypasses CDN URL entirely)
3. Re-upload media later once CDN entry expires or is purged
## Key Signal
> **HTTP 200 + `img.naturalHeight === 0`** = CDN serving corrupt data.
> HTTP status is NOT a reliable signal for media validity when CDN is in the path.
## See Also
- [[wiki/concepts/cloudflare-proxied-domain-npm-subpath]] — Cloudflare proxied domains + NPM routing

View file

@ -0,0 +1,56 @@
---
title: "FastAPI lifespan() Locals — Must Access via request.app.state"
tags: [fastapi, lifespan, app-state, python, gotcha]
source: daily/2026-05-14.md
created: 2026-05-15
---
# FastAPI lifespan() Locals — Must Access via `request.app.state`
## Rule
Variables declared inside `lifespan()` are **local to that function**. They are NOT module-level exports. Importing them from `app.main` causes `ImportError` or returns `None`.
## Wrong
```python
# app/main.py
@asynccontextmanager
async def lifespan(app: FastAPI):
knowledge_base_service = KnowledgeBaseService()
yield
# routes/some_router.py
from app.main import knowledge_base_service # ImportError — not exported
```
## Correct
```python
# app/main.py
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.knowledge_base_service = KnowledgeBaseService()
yield
# cleanup if needed
# routes/some_router.py
@router.get("/query")
async def query(request: Request):
svc = request.app.state.knowledge_base_service
# or safely:
svc = getattr(request.app.state, "knowledge_base_service", None)
```
## Why
`lifespan()` is a coroutine, not a module. Its local namespace is destroyed after `yield` returns. The canonical FastAPI pattern is `app.state` — a `State` object attached to the `FastAPI` instance that persists for the application lifetime.
## Diagnosis
The bug typically surfaces as HTTP 500 during the first request, with a `NameError` or `AttributeError` in the traceback. The lifespan itself runs fine (service initialises, logs look healthy), but route handlers can't reach the service.
## See Also
- FastAPI docs: [Lifespan Events](https://fastapi.tiangolo.com/advanced/events/)
- Related pattern in this project: `analysis_routes.py` uses `request.app.state.knowledge_base_service`

View file

@ -0,0 +1,46 @@
---
title: "HTML5 Video — playsInline + MP4-First Source Order for iOS Safari"
tags: [html, video, ios, safari, react, frontend]
source: daily/2026-05-14.md
created: 2026-05-15
---
# HTML5 Video — `playsInline` + MP4-First Source Order for iOS Safari
## Required Attributes for Inline Autoplay
```html
<!-- ALL four attributes required for silent autoplay on iOS Safari -->
<video playsInline autoPlay muted loop>
<source src="video.mp4" type="video/mp4" />
<source src="video.webm" type="video/webm" />
</video>
```
In React/JSX: use `playsInline` (camelCase), not `playsinline`.
## `<source>` Order Matters
**MP4 must be listed first.** Safari reads `<source>` tags in order and stops at the first it can decode. WebM is not supported by Safari — if WebM is listed first, Safari stalls and never attempts MP4.
| Order | Result |
|-------|--------|
| MP4 → WebM | Safari plays MP4 ✓ |
| WebM → MP4 | Safari stalls silently ✗ |
## Why `playsInline` Is Required
Without `playsInline`, iOS Safari forces the video into native fullscreen on play — breaking hero sections, background videos, and looping banners. The video appears to do nothing (silent refusal).
## Full Checklist for iOS Autoplay
- [x] `playsInline` (camelCase in JSX)
- [x] `autoPlay` (camelCase in JSX)
- [x] `muted` — iOS does NOT allow autoplay with audio
- [x] `loop` (if looping)
- [x] MP4 source listed before WebM
- [x] Video file served with correct `Content-Type: video/mp4`
## Diagnostic
Video plays on desktop Chrome/Firefox but not iOS Safari → check `playsInline` first. Video plays on iOS Safari but shows a full-screen takeover → `playsInline` missing.

View file

@ -1,6 +1,17 @@
# Build Log
## [2026-05-15T21:00:00+02:00] compile | daily/2026-05-14.md
- Source: daily/2026-05-14.md
- Sessions: BAIC modcomms (FastAPI lifespan + Mailgun), video-accessibility (Cloudflare CDN corruption + iOS video), Apache redirect (service deactivation)
- Articles created:
- [[wiki/concepts/fastapi-lifespan-locals-app-state]] — `lifespan()` local vars not exported; use `request.app.state`
- [[wiki/concepts/apache-redirect-suffix-preservation]] — `RedirectMatch` suffix-preserving 301 replaces ProxyPass+Alias+Directory
- [[wiki/concepts/cloudflare-cdn-cache-corruption]] — `naturalHeight:0` diagnostic; HTTP 200 ≠ valid media when CDN in path
- [[wiki/concepts/html5-video-source-order-ios]] — `playsInline` + MP4-first source order for iOS Safari inline autoplay
- Articles updated: [[wiki/client-knowledge/baic]] (Mailgun domain gotcha, FastAPI lifespan access pattern, multi-layer rename coupling)
- Index updates: [[wiki/concepts/_index]] (236→240, +4 entries); [[wiki/_master-index]] (concepts 196→240)
## [2026-05-14T00:00:00+02:00] compile | daily/2026-05-12.md
- Source: daily/2026-05-12.md
- Articles created: (none)