From 26a5c0ca3c83af28675a091bf17bd863d87f0b56 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Tue, 5 May 2026 11:17:44 +0100 Subject: [PATCH] vault backup: 2026-05-05 11:17:44 --- 99 Daily/2026-05-05.md | 6 + wiki/_master-index.md | 8 +- wiki/concepts/_index.md | 5 + .../docker-compose-restart-no-code-reload.md | 89 ++++++++++++++ ...stapi-response-model-silent-field-strip.md | 90 ++++++++++++++ ...ngodb-unwind-preservenullandemptyarrays.md | 86 +++++++++++++ wiki/concepts/native-track-blob-url.md | 39 +++++- .../react-useref-event-handler-state.md | 116 ++++++++++++++++++ wiki/log.md | 4 + wiki/tech-patterns/_index.md | 1 + 10 files changed, 439 insertions(+), 5 deletions(-) create mode 100644 wiki/concepts/docker-compose-restart-no-code-reload.md create mode 100644 wiki/concepts/fastapi-response-model-silent-field-strip.md create mode 100644 wiki/concepts/mongodb-unwind-preservenullandemptyarrays.md create mode 100644 wiki/concepts/react-useref-event-handler-state.md diff --git a/99 Daily/2026-05-05.md b/99 Daily/2026-05-05.md index acc4655..e14b121 100644 --- a/99 Daily/2026-05-05.md +++ b/99 Daily/2026-05-05.md @@ -59,3 +59,9 @@ tags: [daily] - 11:12 (<1min) | `3m-portal` - **Asked:** Set up a global communication rule to respond in Russian to users and English for everything else, and document git worktrees usage in Obsidian. - **Done:** Established language preference rule and created documentation for git worktrees workflow with Claude integration for multi-branch repositories. +- 11:14 | `3m-portal` + - **Asked:** Set global communication rules with Russian for user and English for everything else | Added communication rules to system prompt, documented git worktrees usage in Obsidian | system_prompt.md, Obsidian_Git_Workflow.md + - **Done:** | Global communication rules + git worktrees documentation | Updated system prompt for language rules, created Obsidian guide for git worktrees and multi-branch workflows | system_prompt.md, Obsidian_Git_Workflow.md +- 11:16 | `obsidian-vault` + - **Asked:** Create a global communication rule to respond in Russian to users but use English for everything else, and stop occasionally responding in Ukrainian. + - **Done:** Documented git worktrees workflow for running multiple Claude sessions in parallel and configured Claude to suggest this approach when a repository has multiple branches. diff --git a/wiki/_master-index.md b/wiki/_master-index.md index 30baa94..fb06f14 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -20,20 +20,20 @@ This 3-hop pattern works for hundreds of articles without vector search. |-------|-------------|----------| | [[wiki/obsidian-rag/_index\|obsidian-rag/]] | Karpathy's LLM wiki method — Obsidian RAG, setup, vs true RAG | 3 | | [[wiki/projects-overview/_index\|projects-overview/]] | All 42 Oliver Agency projects — grouped by server (optical-web-1, optical-dev, baic, box-cli) | 1 | -| [[wiki/tech-patterns/_index\|tech-patterns/]] | Recurring tech stacks: FastAPI, React/Vite, Next.js, Azure AD, AI, Box, One2Edit, Redis/Celery, cost-tracker | 21 | +| [[wiki/tech-patterns/_index\|tech-patterns/]] | Recurring tech stacks: FastAPI, React/Vite, Next.js, Azure AD, AI, Box, One2Edit, Redis/Celery, cost-tracker | 22 | | [[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 | 6 | -| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 93 | +| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 98 | | [[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 | 42 | +| [[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 | | [[wiki/web-agency/_index\|web-agency/]] | AI-assisted website building & selling: Claude Code, Nanobanana 2, Kling, LaunchPath MCP | 9 | | [[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/]] | 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, LM Studio local | 30 | | [[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 | 10 | +| [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 11 | | [[wiki/testing/_index\|testing/]] | Web app testing: functional, performance, security, UI types; TDD/BDD/Agile methodologies; Selenium/Cypress/Playwright/JMeter/OWASP ZAP tools | 1 | diff --git a/wiki/concepts/_index.md b/wiki/concepts/_index.md index ca8b9dd..c73fe33 100644 --- a/wiki/concepts/_index.md +++ b/wiki/concepts/_index.md @@ -110,5 +110,10 @@ | [[wiki/concepts/mongodb-schema-validator-migration-verification]] | MongoDB `collMod` migration marked "applied" without actually running — verify with `getCollectionInfos`; fix with direct `collMod` + `validationLevel: moderate` | daily/2026-04-30.md | 2026-04-30 | | [[wiki/concepts/git-worktrees-parallel-claude]] | Git worktrees for parallel Claude Code sessions — isolate branches, exact commands, when to suggest, Claude Code `isolation: "worktree"` | manual | 2026-05-05 | +| [[wiki/concepts/docker-compose-restart-no-code-reload]] | `docker compose restart` does not rebuild the image — must run `build && up -d` after any .py change; service name typos fail silently | daily/2026-05-01.md | 2026-05-01 | +| [[wiki/concepts/mongodb-unwind-preservenullandemptyarrays]] | Typo `preserveNullAndEmpty` (wrong) vs `preserveNullAndEmptyArrays` (correct); MongoDB 7.0 rejects unknown `$unwind` options with error 28811; older versions silently ignored them | daily/2026-05-01.md | 2026-05-01 | +| [[wiki/concepts/fastapi-response-model-silent-field-strip]] | FastAPI `response_model` silently strips fields not declared in the Pydantic schema — data exists in DB but never reaches the JSON response | daily/2026-05-01.md | 2026-05-01 | +| [[wiki/concepts/react-useref-event-handler-state]] | React `useState` setters are async — `pointerMove` sees stale state set in `pointerDown`; fix with `useRef` for drag flags; also: `act()` in RTL tests, `useMemo` declaration order | daily/2026-05-01.md | 2026-05-01 | + diff --git a/wiki/concepts/docker-compose-restart-no-code-reload.md b/wiki/concepts/docker-compose-restart-no-code-reload.md new file mode 100644 index 0000000..65faf8c --- /dev/null +++ b/wiki/concepts/docker-compose-restart-no-code-reload.md @@ -0,0 +1,89 @@ +--- +title: "docker compose restart Does Not Reload Code in Built Images" +aliases: [docker-restart-stale-code, docker-compose-build-required, docker-restart-vs-build] +tags: [docker, docker-compose, python, deployment, gotcha] +sources: + - "daily/2026-05-01.md" +created: 2026-05-01 +updated: 2026-05-01 +--- + +# `docker compose restart` Does Not Reload Code in Built Images + +When a Docker Compose service uses `build:` context (image is baked from source at build time), `docker compose restart ` restarts the existing container from the cached image — it does **not** rebuild. Any Python code changes since the last `docker compose build` are completely invisible to the running container. + +## Key Points + +- `docker compose restart` only recycles the container process; the image layer is unchanged +- After any `.py` file change, the full sequence is: `docker compose build && docker compose up -d ` +- Common symptom: bug fix applied and restart done, but the bug persists — the stale image is still running +- Service name precision matters: `docker compose restart backend` fails silently if the actual service is named `api` +- `docker compose logs ` will still show the old code running with no error +- Hot-reload (Uvicorn `--reload`) only works if the source directory is **volume-mounted** into the container, not just baked in + +## Details + +### The Wrong Pattern + +```bash +# ❌ Code change deployed, restart done — but stale image still runs +vim app/routes/users.py +docker compose restart api +# Bug still present. No error. Container appears healthy. +``` + +### The Correct Pattern + +```bash +# ✅ Rebuild the image, then recreate the container +docker compose build api && docker compose up -d api +``` + +### Diagnosing Stale Image + +To confirm whether the running container has the latest code: + +```bash +# Check when the image was last built +docker images | grep + +# Exec into the container and inspect the file directly +docker compose exec api cat /app/routes/users.py +``` + +### Volume-Mount Alternative (Dev Only) + +For development, mount the source directory so Uvicorn's `--reload` works without rebuilding: + +```yaml +services: + api: + build: . + command: uvicorn app.main:app --reload --host 0.0.0.0 + volumes: + - ./app:/app/app # live mount; changes are visible immediately +``` + +> [!warning] Not for Production +> Volume mounts bypass the baked image in production — secrets and build artifacts may differ between environments. + +### Service Name Gotcha + +```bash +# ❌ Silently does nothing if the service is named "api" not "backend" +docker compose restart backend + +# ✅ Verify service names first +docker compose ps +docker compose restart api +``` + +## Related Concepts + +- [[wiki/tech-patterns/fastapi-python-docker]] — FastAPI + Docker Compose deployment pattern this applies to +- [[wiki/architecture/optical-dev-server-deploy]] — optical-dev deploy workflow where build-before-up is the standard +- [[wiki/concepts/python-service-deployment-dotenv]] — full Python service deploy checklist (includes rebuild step) + +## Sources + +- [[daily/2026-05-01.md]] — Sessions 12:09 and 19:07: code fix applied, `docker compose restart` run, bug persisted; root cause traced to stale baked image diff --git a/wiki/concepts/fastapi-response-model-silent-field-strip.md b/wiki/concepts/fastapi-response-model-silent-field-strip.md new file mode 100644 index 0000000..045606f --- /dev/null +++ b/wiki/concepts/fastapi-response-model-silent-field-strip.md @@ -0,0 +1,90 @@ +--- +title: "FastAPI Response Model Silently Strips Fields Not in Schema" +aliases: [fastapi-field-strip, pydantic-response-model-missing-field, fastapi-silent-omit] +tags: [fastapi, pydantic, api, gotcha, debugging] +sources: + - "daily/2026-05-01.md" +created: 2026-05-01 +updated: 2026-05-01 +--- + +# FastAPI Response Model Silently Strips Fields Not in Schema + +When a FastAPI route has a `response_model`, Pydantic serializes the return value using **only** the fields declared in that model. Any extra fields — even if they exist in the MongoDB document and are present in the Python object — are silently dropped from the JSON response. No warning is raised, no error is logged. + +## Key Points + +- FastAPI's `response_model` is a whitelist: fields not declared are silently excluded from the response +- The data exists in the database and in the Python layer — it never reaches the HTTP response +- Frontend receives `undefined` for the missing field and typically falls back to a default/null state, masking the bug +- **Diagnostic first step**: when API data appears "missing", check the Pydantic response model schema before investigating the database or service layer +- Fix: add the missing field to the response model, or use `response_model=None` to disable filtering (not recommended in production) +- `response_model_exclude_none=True` does not cause this — that only removes `None` values that ARE in the schema + +## Details + +### Minimal Reproduction + +```python +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + +class TaskResponse(BaseModel): + id: str + title: str + # ← 'failure' and 'error' fields are NOT declared here + +@app.get("/tasks/{task_id}", response_model=TaskResponse) +async def get_task(task_id: str): + # DB returns: {"id": "abc", "title": "Export", "failure": "timeout", "error": "Connection refused"} + doc = await db.tasks.find_one({"_id": task_id}) + return doc +# Response JSON: {"id": "abc", "title": "Export"} +# 'failure' and 'error' are silently stripped +``` + +### The Fix + +```python +class TaskResponse(BaseModel): + id: str + title: str + failure: str | None = None # ← add missing fields + error: str | None = None +``` + +### Debugging Workflow + +``` +Bug: frontend shows no error message for failed tasks + ↓ +1. Check Pydantic response model — is 'failure' field declared? ← START HERE + → No → add it → bug fixed +2. If field IS in model, check service layer returns it +3. If service returns it, check MongoDB document structure +4. If MongoDB has it, check field name spelling (camelCase vs snake_case) +``` + +> [!warning] Silent Failure Pattern +> This is one of the most deceptive FastAPI bugs: the data pipeline is fully correct, but the response contract silently discards fields. Unlike missing DB documents (which raise errors), this produces valid 200 responses with incomplete data. + +### When to Use `response_model=None` + +```python +# Only for debugging or internal endpoints — disables all output validation +@app.get("/debug/tasks/{task_id}", response_model=None) +async def debug_task(task_id: str): + return await db.tasks.find_one({"_id": task_id}) +``` + +## Related Concepts + +- [[wiki/concepts/pydantic-v2-alias-id-gotcha]] — another Pydantic field serialization gotcha where field names differ from JSON keys +- [[wiki/concepts/pydantic-model-dict-interface]] — Pydantic model/dict boundary issues that cause silent data loss +- [[wiki/tech-patterns/fastapi-python-docker]] — FastAPI deployment context where these issues surface + +## Sources + +- [[daily/2026-05-01.md]] — Session 17:13: frontend showed `undefined` for task failure reason; root cause traced to `failure` and `error` fields missing from the Pydantic `TaskResponse` model diff --git a/wiki/concepts/mongodb-unwind-preservenullandemptyarrays.md b/wiki/concepts/mongodb-unwind-preservenullandemptyarrays.md new file mode 100644 index 0000000..00afac6 --- /dev/null +++ b/wiki/concepts/mongodb-unwind-preservenullandemptyarrays.md @@ -0,0 +1,86 @@ +--- +title: "MongoDB $unwind preserveNullAndEmptyArrays Typo Breaks on 7.0" +aliases: [mongodb-unwind-typo, preserveNullAndEmpty-wrong, mongodb-28811-error] +tags: [mongodb, aggregation, python, fastapi, gotcha, version-compatibility] +sources: + - "daily/2026-05-01.md" +created: 2026-05-01 +updated: 2026-05-01 +--- + +# MongoDB `$unwind` `preserveNullAndEmptyArrays` Typo Breaks on 7.0 + +The `$unwind` aggregation stage option is `preserveNullAndEmptyArrays` (full word). The common typo `preserveNullAndEmpty` (truncated) was silently ignored by older MongoDB versions but is **rejected with error code 28811** in MongoDB 7.0, which treats unknown `$unwind` options as fatal errors. + +## Key Points + +- Correct spelling: `preserveNullAndEmptyArrays` — not `preserveNullAndEmpty` +- MongoDB 7.0 raises `OperationFailure` code **28811** for unknown `$unwind` options; older versions silently ignored them +- A pipeline that worked in development (older Mongo) breaks in production after a MongoDB upgrade +- FastAPI wraps the root exception in `anyio.EndOfStream` — the real `OperationFailure` is in the second "During handling of the above exception" block in the traceback +- Stack path to root cause: HTTP route → `service.aggregate(pipeline)` → `OperationFailure` code 28811 + +## Details + +### The Typo + +```python +# ❌ WRONG — silently accepted by MongoDB < 7.0, fatal on 7.0+ +pipeline = [ + { + "$unwind": { + "path": "$items", + "preserveNullAndEmpty": True # truncated — unknown option + } + } +] +``` + +```python +# ✅ CORRECT +pipeline = [ + { + "$unwind": { + "path": "$items", + "preserveNullAndEmptyArrays": True + } + } +] +``` + +### Reading the FastAPI / anyio Traceback + +When this error occurs through FastAPI, the traceback is misleading: + +``` +anyio.EndOfStream: ... +During handling of the above exception, another exception occurred: + ... + pymongo.errors.OperationFailure: Unrecognized option to $unwind: preserveNullAndEmpty, full error: {'code': 28811, ...} +``` + +> [!tip] Diagnosis Rule +> When you see `anyio.EndOfStream` in a FastAPI route, always scroll to the **second** exception block — the first is just the ASGI transport wrapper. The real error code is in `OperationFailure`. + +### Why It Went Unnoticed + +| Environment | MongoDB Version | Behaviour | +|---|---|---| +| Local dev | 5.x / 6.x | Unknown options ignored — pipeline runs | +| Production | 7.0 | Unknown options → fatal error code 28811 | + +The error only surfaced after a server-side MongoDB upgrade, making it appear to be an unrelated regression. + +### Defensive Practice + +When writing aggregation pipelines with multiple options, cross-reference against the [official `$unwind` docs](https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/) rather than relying on runtime silence. MongoDB 7.0's stricter validation is the correct behaviour. + +## Related Concepts + +- [[wiki/concepts/fastapi-mongodb-role-migration]] — FastAPI + MongoDB aggregation pipeline patterns +- [[wiki/concepts/mongodb-schema-validator-migration-verification]] — MongoDB 7.0 stricter validation affects schema validators too +- [[wiki/tech-patterns/fastapi-python-docker]] — FastAPI + Docker environment where version mismatches surface + +## Sources + +- [[daily/2026-05-01.md]] — Sessions 18:57 and 19:07: `OperationFailure` code 28811 on production after MongoDB 7.0 upgrade; local dev on older Mongo never raised the error diff --git a/wiki/concepts/native-track-blob-url.md b/wiki/concepts/native-track-blob-url.md index 69ee61b..fe5f387 100644 --- a/wiki/concepts/native-track-blob-url.md +++ b/wiki/concepts/native-track-blob-url.md @@ -5,7 +5,7 @@ tags: [browser, html, vtt, accessibility, react, javascript] sources: - "daily/2026-04-29.md" created: 2026-04-29 -updated: 2026-04-29 +updated: 2026-05-01 --- # Native `` Element Requires Blob URL, Not `data:` URI @@ -117,6 +117,43 @@ And this is the second line. The string must start with `WEBVTT` on the first line (no BOM, no extra whitespace before it). +## Gotchas + +### `` + Custom JS Overlay = Double Captions + +The `default` attribute on `` tells the browser to **auto-activate** the text track, causing the browser's native caption renderer to display cues. If a custom JS overlay is also rendering captions from the same track, both renderers fire simultaneously and captions appear doubled. + +```jsx +// ❌ BROKEN — native renderer + JS overlay both show captions + + +// ✅ CORRECT — remove 'default'; JS overlay is the sole renderer + +``` + +When using a custom caption overlay, always omit the `default` attribute and activate/read the track programmatically via the `TextTrack` API. + +### VTT Cue Settings Are Not Auto-Applied to Custom Overlays + +WebVTT cue settings (`line:`, `position:`, `size:`, `align:`) are instructions for the **browser's native renderer**. A custom JS overlay that reads `cue.text` does not automatically honour these settings — it receives the raw text string only. + +```javascript +// cue.text = "Hello world" — no position data here +// cue.line, cue.position, cue.align ARE accessible, but the overlay +// must explicitly read them and apply CSS positioning + +track.addEventListener("cuechange", () => { + const cues = track.activeCues; + for (const cue of cues) { + // Must read cue.line / cue.position and apply manually: + overlayEl.style.top = cue.line !== "auto" ? `${cue.line}%` : "90%"; + overlayEl.textContent = cue.text; + } +}); +``` + +Silently ignoring cue settings while not stripping them causes subtle positioning bugs — text appears at the default position regardless of VTT metadata. + ## Related Concepts - [[wiki/tech-patterns/python-ai-agents]] — VTT is often generated by AI models (Gemini, Whisper) in accessibility pipelines diff --git a/wiki/concepts/react-useref-event-handler-state.md b/wiki/concepts/react-useref-event-handler-state.md new file mode 100644 index 0000000..2bde17c --- /dev/null +++ b/wiki/concepts/react-useref-event-handler-state.md @@ -0,0 +1,116 @@ +--- +title: "React useRef for Event Handler State — Avoid Stale useState in Pointer Events" +aliases: [react-useref-drag-state, pointer-event-stale-state, useref-event-handler] +tags: [react, hooks, useref, usestate, events, drag, testing] +sources: + - "daily/2026-05-01.md" +created: 2026-05-01 +updated: 2026-05-01 +--- + +# React `useRef` for Event Handler State — Avoid Stale `useState` in Pointer Events + +React 18 `useState` setters are asynchronous — state updates are batched and do not take effect until the next render. Event handlers registered on the same element fire in the same tick, which means a `pointerMove` handler that runs immediately after `pointerDown` will read the **pre-update** (stale) state value set in `pointerDown`. The solution is to use `useRef` for flags that event handlers must read synchronously. + +## Key Points + +- `useState` setters are async: `setDraggingIndex(i)` in `pointerDown` is still `null` when `pointerMove` fires in the same event tick +- `useRef` holds a mutable `.current` value that is visible synchronously to all code in the same render cycle +- Use `useRef` for drag-in-progress flags, selected index tracking, and any state that event handlers must read without waiting for a re-render +- React Testing Library `fireEvent` does **not** flush state synchronously — wrap state-dependent assertions in `act()` +- `useMemo` hooks must be declared **after** all `useState` variables they reference (TypeScript TS2448: "used before declaration") + +## Details + +### The Stale State Bug + +```tsx +// ❌ BROKEN — pointerMove sees stale null because useState is async +function DraggableList({ cues }) { + const [draggingCueIndex, setDraggingCueIndex] = useState(null); + + const handlePointerDown = (index: number) => (e: React.PointerEvent) => { + setDraggingCueIndex(index); // schedules update — NOT yet applied + }; + + const handlePointerMove = (e: React.PointerEvent) => { + if (draggingCueIndex === null) return; // ← always null! fires in same tick + // drag logic never executes + }; +} +``` + +### The Fix: useRef for Synchronous Access + +```tsx +// ✅ CORRECT — ref is readable immediately in the same event tick +function DraggableList({ cues }) { + const draggingCueIndexRef = useRef(null); + const [draggingCueIndex, setDraggingCueIndex] = useState(null); + // ↑ useState still used for re-render trigger (visual feedback) + + const handlePointerDown = (index: number) => (e: React.PointerEvent) => { + draggingCueIndexRef.current = index; // synchronous, readable immediately + setDraggingCueIndex(index); // triggers re-render for UI update + e.currentTarget.setPointerCapture(e.pointerId); + }; + + const handlePointerMove = (e: React.PointerEvent) => { + if (draggingCueIndexRef.current === null) return; // ✓ correct value + // drag logic executes correctly + }; + + const handlePointerUp = () => { + draggingCueIndexRef.current = null; + setDraggingCueIndex(null); + }; +} +``` + +### Testing: act() Requirement + +```tsx +// ❌ BROKEN — assertion runs before state flush +fireEvent.pointerDown(element, { pointerId: 1 }); +expect(screen.getByTestId("dragging-indicator")).toBeInTheDocument(); +// Fails: state not yet applied + +// ✅ CORRECT — wrap in act() to flush state updates +import { act } from "@testing-library/react"; + +act(() => { + fireEvent.pointerDown(element, { pointerId: 1 }); +}); +expect(screen.getByTestId("dragging-indicator")).toBeInTheDocument(); +``` + +### useMemo Declaration Order (TS2448) + +```tsx +// ❌ TypeScript error TS2448: Block-scoped variable 'items' used before its declaration +const sortedItems = useMemo(() => [...items].sort(), [items]); // references items +const [items, setItems] = useState([]); // declared after + +// ✅ Always declare useState before useMemo that depends on it +const [items, setItems] = useState([]); +const sortedItems = useMemo(() => [...items].sort(), [items]); +``` + +### Decision Guide: useState vs useRef + +| Use case | Correct hook | +|---|---| +| Value that triggers a re-render (visual state) | `useState` | +| Value read synchronously in event handlers | `useRef` | +| Value that needs both (drag index) | Both — `useRef` for sync read, `useState` for render | +| DOM node reference | `useRef` | + +## Related Concepts + +- [[wiki/concepts/zustand-async-hydration]] — another async state access pattern where data is not available synchronously on first render +- [[wiki/tech-patterns/react-vite-typescript]] — React + Vite + TypeScript stack where this pattern applies +- [[wiki/concepts/websocket-react-token-guard]] — React `useEffect` + async state dependency management + +## Sources + +- [[daily/2026-05-01.md]] — Sessions 15:31 and 19:07: drag-to-reorder feature broke because `pointerMove` saw stale `draggingCueIndex`; fixed by introducing `draggingCueIndexRef`; also discovered `useMemo` ordering TS2448 and `act()` requirement in tests diff --git a/wiki/log.md b/wiki/log.md index beb9a6e..98072c8 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -1,6 +1,10 @@ # Build Log +## [2026-05-05T12:00:00+01:00] compile | 2026-05-01.md +- Articles created: [[wiki/concepts/docker-compose-restart-no-code-reload]], [[wiki/concepts/mongodb-unwind-preservenullandemptyarrays]], [[wiki/concepts/fastapi-response-model-silent-field-strip]], [[wiki/concepts/react-useref-event-handler-state]] +- Articles updated: [[wiki/concepts/native-track-blob-url]] + ## [2026-04-30T23:59:00+01:00] compile | 2026-04-30.md (pass 3) - Source: daily/2026-04-30.md - Articles created: [[wiki/concepts/react-query-enabled-falsy-value]], [[wiki/concepts/mongodb-cross-collection-id-confusion]], [[wiki/concepts/browser-sequential-download-blocking]], [[wiki/concepts/mongodb-schema-validator-migration-verification]] diff --git a/wiki/tech-patterns/_index.md b/wiki/tech-patterns/_index.md index f3f4e2f..6215956 100644 --- a/wiki/tech-patterns/_index.md +++ b/wiki/tech-patterns/_index.md @@ -35,6 +35,7 @@ Recurring technology stacks used across Oliver Agency projects. Each article cov | [[wiki/tech-patterns/websocket-keepalive-terminal-close\|WebSocket Keepalive + Terminal Close Codes]] | Bidirectional 20s keepalive + terminal close codes (4001/4003/4004/4403) to prevent reconnect storms through Apache mod_proxy_wstunnel | video-accessibility, mod-comms | | [[wiki/tech-patterns/pydantic-empty-string-coercion\|Pydantic Empty String → None Coercion]] | field_validator with mode='before' to treat "" as absent optional field — prevents 400 on CC-only or AD-only payloads | video-accessibility | | [[wiki/tech-patterns/vtt-descriptive-transcript-regeneration\|VTT Edit → Descriptive Transcript Regeneration]] | Pattern for keeping descriptive_transcript.txt in sync when captions or AD VTTs are edited via PATCH /vtt | video-accessibility | +| [[wiki/tech-patterns/git-worktrees-parallel-claude-sessions\|Using Git Worktrees for Parallel Claude Development Sessions]] | Use git worktrees when you need to run multiple independent Claude sessions simultaneously on differ | — | ## Quick Decision Guide