wiki: auto-compile 2026-05-10 (4 log(s), 249 articles)
This commit is contained in:
parent
c06925507f
commit
8e1d749cc1
15 changed files with 477 additions and 157 deletions
|
|
@ -323,3 +323,6 @@ tags: [daily]
|
|||
- 21:13 (1min) | `memory-compiler`
|
||||
- **Asked:** Compile knowledge from daily conversation log 2026-05-07 into structured wiki articles.
|
||||
- **Done:** Created fastapi-orm-property-json-column.md article and updated response-model-silent-field-strip.md with Vue crash fix, plus index references.
|
||||
- 21:21 (5min) | `memory-compiler`
|
||||
- **Asked:** How should the knowledge compiler extract and organize concepts from daily developer logs into the personal wiki?
|
||||
- **Done:** Updated master index from 184 to 190 concepts and appended session log entry documenting the compilation process.
|
||||
|
|
|
|||
|
|
@ -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 | 184 |
|
||||
| [[wiki/concepts/_index\|concepts/]] | Atomic knowledge extracted from Claude Code sessions | 192 |
|
||||
| [[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 |
|
||||
|
|
|
|||
|
|
@ -202,5 +202,15 @@
|
|||
| [[wiki/concepts/google-cloud-tts-ai-studio-separation\|Google Cloud TTS vs AI Studio TTS — Separate Services, Different Quotas]] | Gemini TTS on AI Studio API (`generativelanguage.googleapis.com`, `GEMINI_API_KEY`) has a 10 RPM pre | daily/2026-05-08.md | 2026-05-08 |
|
||||
| [[wiki/concepts/mongodb-manual-patch-pydantic-required-fields\|MongoDB Manual Patch Must Include All Required Pydantic Fields]] | When manually patching a MongoDB document (e.g., via `mongosh` or a maintenance script), omitting a | daily/2026-05-08.md | 2026-05-10 |
|
||||
| [[wiki/concepts/pydantic-mongodb-patch-missing-fields\|Manual MongoDB Patch Missing Pydantic Required Fields → 500 on GET]] | When patching a MongoDB document directly (via MongoDB shell, Atlas UI, or a one-off script) without | daily/2026-05-08.md | 2026-05-08 |
|
||||
| [[wiki/concepts/celery-inflight-task-restart-loss\|Celery In-Flight Tasks Lost on Worker Restart]] | Celery tasks that are **actively executing** when the worker container restarts are permanently lost | daily/2026-05-08.md | 2026-05-10 |
|
||||
| [[wiki/concepts/css-marquee-animation-gpu-pattern\|CSS Marquee — GPU-Composited Seamless Scroll Pattern]] | A seamless, stutter-free horizontal marquee (infinite scroll) entirely in CSS, GPU-composited via `t | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/payload-cms-node26-esm-workaround\|Payload CMS + Node 26 — Seed Script ESM Workaround]] | Running a Payload CMS seed script directly via `npx tsx seed.ts` on **Node 26** fails with: | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/payload-cms-root-layout-requirement\|Payload CMS 3.x — (payload)/layout.tsx Must Use RootLayout]] | In a Payload CMS 3.x + Next.js project, the route group `(payload)/layout.tsx` **must** import and r | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/pydantic-exclude-none-null-clearing-conflict\|Pydantic exclude_none vs Intentional null Fields]] | `model_dump(exclude_none=True)` removes **all** fields whose value is `None`, including fields that | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/css-animation-js-scroll-conflict\|CSS animation + JS scrollLeft Conflict — Layout Thrashing]] | Mixing a CSS `animation` (or `transition`) on an element with JS writes to `scrollLeft` on the same | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/figma-mcp-oauth-reconnect-restart\|Figma MCP — OAuth Reconnect Requires Full Claude Code Restart]] | If the Figma MCP server disconnects mid-auth-flow (e.g., Claude Code restarts while the browser OAut | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/nextjs16-lint-command-removed\|Next.js 16 — next lint Command Removed]] | `next lint` is removed in Next.js 16. Running it produces: | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/overflow-hidden-clips-absolute-children\|overflow:hidden Clips Absolute/Fixed Children Regardless of z-index]] | An element with `overflow: hidden` clips **all** descendant content at its boundary — including abso | daily/2026-05-10.md | 2026-05-10 |
|
||||
| [[wiki/concepts/react-state-playwright-css-hover\|React useState Dropdown — CSS group-hover vs useState for Playwright]] | Playwright `hover()` on a trigger element does not reliably open a React dropdown that uses `useStat | daily/2026-05-10.md | 2026-05-10 |
|
||||
<!-- Articles added automatically by compile.py -->
|
||||
<!-- Format: | [[concepts/slug]] | One-line summary | daily/YYYY-MM-DD.md | date | -->
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
---
|
||||
title: "Celery In-Flight Tasks Lost on Worker Restart"
|
||||
tags: [celery, redis, mongodb, fastapi, deploy, video-accessibility]
|
||||
source: daily/2026-05-08.md
|
||||
created: 2026-05-10
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
# Celery In-Flight Tasks Lost on Worker Restart
|
||||
|
||||
## The Problem
|
||||
|
||||
Celery tasks that are **actively executing** when the worker container restarts are permanently lost. The job status in the application database remains stuck at whatever intermediate state it was in (e.g., `ai_processing`) and never transitions to `completed` or `failed`.
|
||||
|
||||
## Why It Happens
|
||||
|
||||
When Celery picks up a task from Redis, it **acknowledges the message immediately** (by default: `task_acks_late=False`). After acknowledgment, the task is removed from the Redis queue. If the worker dies mid-execution, Redis has no record of the task — it's gone. The application database still shows the task as in-progress.
|
||||
|
||||
```
|
||||
Redis queue: [task_id removed after ack]
|
||||
MongoDB: { status: "ai_processing", ... } ← stuck forever
|
||||
Worker: ☠️ (container restarted during docker compose up)
|
||||
```
|
||||
|
||||
## Real Incident
|
||||
|
||||
During video-accessibility deploys, the `celery_worker` container was restarted as part of `docker compose up -d`. Any AD generation tasks running at the moment of restart were permanently stuck in `ai_processing`. The MongoDB job documents remained in that state with no way for the system to detect the failure automatically.
|
||||
|
||||
**Detection after a deploy:**
|
||||
```javascript
|
||||
// Find jobs stuck in ai_processing for more than 30 minutes
|
||||
db.jobs.find({
|
||||
status: "ai_processing",
|
||||
updated_at: { $lt: new Date(Date.now() - 30 * 60 * 1000) }
|
||||
})
|
||||
```
|
||||
|
||||
**Manual recovery:**
|
||||
```bash
|
||||
# Call the maintenance endpoint for each stuck job ID
|
||||
curl -X POST https://app.example.com/admin/maintenance/reprocess-job/{job_id} \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN"
|
||||
```
|
||||
|
||||
## Prevention Options
|
||||
|
||||
### Option 1: `task_acks_late=True` (re-queue on failure)
|
||||
```python
|
||||
# celery config
|
||||
task_acks_late = True
|
||||
task_reject_on_worker_lost = True
|
||||
```
|
||||
Re-queues the task if the worker dies. Risk: task may run twice if the worker died after partial completion (idempotency required).
|
||||
|
||||
### Option 2: Graceful shutdown with timeout
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
celery_worker:
|
||||
stop_grace_period: 120s # give running tasks time to finish
|
||||
```
|
||||
The SIGTERM → SIGKILL gap gives workers time to finish in-flight tasks. Does not guarantee completion for long-running tasks.
|
||||
|
||||
### Option 3: Watchdog query on startup
|
||||
```python
|
||||
# FastAPI lifespan: on startup, mark stuck tasks as failed
|
||||
async def reset_stale_processing_jobs():
|
||||
cutoff = datetime.utcnow() - timedelta(minutes=30)
|
||||
await db.jobs.update_many(
|
||||
{"status": "ai_processing", "updated_at": {"$lt": cutoff}},
|
||||
{"$set": {"status": "failed", "error_message": "Worker restart"}}
|
||||
)
|
||||
```
|
||||
|
||||
## Current Practice (video-accessibility)
|
||||
|
||||
After every deploy that restarts the `celery_worker` container:
|
||||
1. Check for stuck jobs: query MongoDB for `ai_processing` jobs > 30 min old
|
||||
2. Call `/admin/maintenance/reprocess-job/{id}` for each one
|
||||
|
||||
## See Also
|
||||
|
||||
- [[wiki/concepts/celery-redis-queue-flush-on-deterministic-error]] — deterministic errors leave tasks stuck in Redis queue
|
||||
- [[wiki/concepts/celery-queue-worker-specialization]] — named queues: only the consuming container processes its queue
|
||||
46
wiki/concepts/css-animation-js-scroll-conflict.md
Normal file
46
wiki/concepts/css-animation-js-scroll-conflict.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "CSS animation + JS scrollLeft Conflict — Layout Thrashing"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Mixing a CSS `animation` (or `transition`) on an element with JS writes to `scrollLeft` on the same element causes visible stutter and freezes.
|
||||
|
||||
Root cause: `scrollLeft` writes force a **synchronous layout reflow** on every frame. The browser must exit the GPU compositor path, recompute layout on the CPU, and re-upload the layer — breaking the smooth 60fps pipeline that CSS animations rely on.
|
||||
|
||||
Symptoms:
|
||||
- The carousel/marquee freezes for one frame (50–100ms visible pause) whenever JS resets `scrollLeft`
|
||||
- The pause is more pronounced on lower-end devices or when the element contains many children
|
||||
|
||||
## What Not to Do
|
||||
|
||||
```tsx
|
||||
// ❌ CSS animation + JS scrollLeft = stutter
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (ref.current) ref.current.scrollLeft = 0 // layout thrash
|
||||
}, duration * 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
// Combined with:
|
||||
// animation: marquee ${duration}s linear infinite;
|
||||
```
|
||||
|
||||
## Solution
|
||||
|
||||
Pick **one** mechanism:
|
||||
|
||||
| Approach | GPU path | Seamless loop |
|
||||
|----------|----------|---------------|
|
||||
| Pure CSS `@keyframes` + duplicated DOM | Yes | Yes |
|
||||
| JS `requestAnimationFrame` + `translateX` | Yes (if only transform) | With reset trick |
|
||||
| JS `scrollLeft` scroll | No (layout) | Only with flicker |
|
||||
|
||||
For a marquee/slider, the pure CSS approach with duplicated array is the simplest and most performant. See [[wiki/concepts/css-marquee-animation-gpu-pattern]].
|
||||
|
||||
## Rule
|
||||
|
||||
Never write `element.scrollLeft` inside a loop or timer while a CSS `animation` is active on the same element.
|
||||
78
wiki/concepts/css-marquee-animation-gpu-pattern.md
Normal file
78
wiki/concepts/css-marquee-animation-gpu-pattern.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
title: "CSS Marquee — GPU-Composited Seamless Scroll Pattern"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Pattern
|
||||
|
||||
A seamless, stutter-free horizontal marquee (infinite scroll) entirely in CSS, GPU-composited via `transform`.
|
||||
|
||||
### Key Idea
|
||||
|
||||
1. **Duplicate the items in the DOM** — render `[...items, ...items]` so the list is twice as wide.
|
||||
2. **Animate exactly `-50%`** — when the transform reaches `-50%`, the visible section looks identical to the start, creating a seamless loop.
|
||||
3. **Never pause the animation** — pausing and resuming causes a visible freeze; use `animationDelay` for button-driven navigation instead.
|
||||
|
||||
## Implementation
|
||||
|
||||
```tsx
|
||||
// Duplicate items for seamless loop
|
||||
const doubled = [...items, ...items]
|
||||
|
||||
// CSS
|
||||
// @keyframes marquee {
|
||||
// from { transform: translateX(0); }
|
||||
// to { transform: translateX(-50%); }
|
||||
// }
|
||||
|
||||
<div style={{ overflow: 'hidden' }}> {/* viewport: clips overflow */}
|
||||
<div
|
||||
className="flex"
|
||||
style={{
|
||||
animation: `marquee ${duration}s linear infinite`,
|
||||
animationDelay: `${animationDelay}s`, // phase-shift for buttons
|
||||
width: 'max-content',
|
||||
}}
|
||||
>
|
||||
{doubled.map((item, i) => <Card key={i} {...item} />)}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
@keyframes marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
```
|
||||
|
||||
## Button Navigation via Phase Shift
|
||||
|
||||
Instead of pausing/seeking the animation (which causes gaps), shift the animation phase by modifying `animationDelay`:
|
||||
|
||||
```tsx
|
||||
const JUMP_SECONDS = duration / items.length // one item width in time
|
||||
|
||||
const handleNext = () => setAnimationDelay(prev => prev - JUMP_SECONDS)
|
||||
const handlePrev = () => setAnimationDelay(prev => prev + JUMP_SECONDS)
|
||||
```
|
||||
|
||||
Negative delay values are valid CSS — the animation starts as if it has already been running for `|delay|` seconds.
|
||||
|
||||
## Positioning Buttons
|
||||
|
||||
Buttons must be **outside** the `overflow: hidden` container. Even `z-50` cannot escape `overflow: hidden` clipping. Position them via an outer wrapper:
|
||||
|
||||
```tsx
|
||||
<div className="relative">
|
||||
<div style={{ overflow: 'hidden' }}>...</div>
|
||||
{/* Buttons positioned here, not inside overflow:hidden */}
|
||||
<button className="absolute left-4 top-1/2 -translate-y-1/2">←</button>
|
||||
<button className="absolute right-4 top-1/2 -translate-y-1/2">→</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Why Not JS scrollLeft?
|
||||
|
||||
Combining CSS `animation` with JS `scrollLeft = 0` (to reset position) causes layout thrashing and visible stutter. The browser must recalculate layout on every `scrollLeft` write, breaking the GPU compositor's smooth pipeline. See [[wiki/concepts/css-animation-js-scroll-conflict]].
|
||||
30
wiki/concepts/figma-mcp-oauth-reconnect-restart.md
Normal file
30
wiki/concepts/figma-mcp-oauth-reconnect-restart.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: "Figma MCP — OAuth Reconnect Requires Full Claude Code Restart"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
If the Figma MCP server disconnects mid-auth-flow (e.g., Claude Code restarts while the browser OAuth tab is open), the token gets saved to disk but the running MCP server instance does not pick it up. Subsequent calls to Figma tools return auth errors even though the credential file exists.
|
||||
|
||||
## Symptom
|
||||
|
||||
- `/mcp` shows Figma server as connected
|
||||
- `mcp__figma__get_design_context` (or any Figma tool) returns a 401 / auth failure
|
||||
- Re-running the auth command has no effect
|
||||
|
||||
## Fix
|
||||
|
||||
**Fully restart Claude Code** (quit and relaunch the app or `Ctrl-C` and restart the CLI). A simple `/mcp` reload or disconnecting/reconnecting the server is not sufficient — the stale OAuth context lives in the server process memory and does not refresh without a full restart.
|
||||
|
||||
After restart, the token file is read fresh and Figma tools work normally.
|
||||
|
||||
## Note on Token Storage
|
||||
|
||||
The Figma MCP stores its OAuth token at:
|
||||
```
|
||||
~/.config/claude/figma-mcp-token (or similar platform-specific path)
|
||||
```
|
||||
|
||||
The token itself is valid — the issue is the server process state, not the token. No need to re-authenticate from scratch.
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
---
|
||||
title: "MongoDB Manual Patch Must Include All Required Pydantic Fields"
|
||||
tags: [mongodb, pydantic, fastapi, debugging, video-accessibility]
|
||||
source: daily/2026-05-08.md
|
||||
created: 2026-05-10
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
# MongoDB Manual Patch Must Include All Required Pydantic Fields
|
||||
|
||||
## The Problem
|
||||
|
||||
When manually patching a MongoDB document (e.g., via `mongosh` or a maintenance script), omitting a field that is **required** in the corresponding Pydantic response model causes `GET /resource/{id}` to return a **silent 500**.
|
||||
|
||||
The error is easy to miss: FastAPI logs a `422 Unprocessable Entity` or `500` at WARNING level, with no visible Python traceback in standard stdout logs. The response body may be an empty JSON or a generic server error.
|
||||
|
||||
## Why It Happens
|
||||
|
||||
FastAPI uses Pydantic to serialize the response. When constructing the response model, Pydantic raises `ValidationError` for missing required fields. FastAPI catches this and returns 500, but the validation error is not always visible in container logs unless `LOG_LEVEL=DEBUG` or `exc_info=True` is set.
|
||||
|
||||
```python
|
||||
class JobFailure(BaseModel):
|
||||
job_id: str
|
||||
error_message: str
|
||||
occurred_at: datetime # required — no default
|
||||
|
||||
# If `occurred_at` is absent in MongoDB doc, GET /jobs/{id} → 500
|
||||
```
|
||||
|
||||
## Real Incident
|
||||
|
||||
During video-accessibility debugging, a manual MongoDB patch was applied to fix a stuck job:
|
||||
|
||||
```javascript
|
||||
// mongosh — WRONG: missing required field
|
||||
db.jobs.updateOne(
|
||||
{ _id: ObjectId("...") },
|
||||
{ $set: { status: "failed", error_message: "manual fix" } }
|
||||
)
|
||||
// Missing `occurred_at` → GET /jobs/{id} returns 500
|
||||
```
|
||||
|
||||
Fixed by including all required fields:
|
||||
|
||||
```javascript
|
||||
db.jobs.updateOne(
|
||||
{ _id: ObjectId("...") },
|
||||
{ $set: {
|
||||
status: "failed",
|
||||
error_message: "manual fix",
|
||||
occurred_at: new Date() // required by Pydantic model
|
||||
} }
|
||||
)
|
||||
```
|
||||
|
||||
## Prevention
|
||||
|
||||
1. **Before patching**, check the Pydantic model for the document's response schema — look for fields without `Optional[...]` or a `default=` value.
|
||||
2. **Use `Optional` defensively** for fields that can legitimately be absent (e.g., failure details on a successful job).
|
||||
3. **Add `exc_info=True`** to FastAPI exception handlers so validation errors appear in logs:
|
||||
|
||||
```python
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request, exc):
|
||||
logger.error("Response validation failed", exc_info=True)
|
||||
return JSONResponse(status_code=500, content={"detail": "Internal error"})
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [[wiki/concepts/fastapi-response-model-silent-field-strip]] — FastAPI silently strips extra fields not in the schema (the reverse problem)
|
||||
- [[wiki/concepts/mongodb-enum-deserialization]] — MongoDB stores Enum fields as strings; `.value` raises AttributeError
|
||||
46
wiki/concepts/nextjs16-lint-command-removed.md
Normal file
46
wiki/concepts/nextjs16-lint-command-removed.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Next.js 16 — next lint Command Removed"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Change
|
||||
|
||||
`next lint` is removed in Next.js 16. Running it produces:
|
||||
|
||||
```
|
||||
Unknown command: lint
|
||||
```
|
||||
|
||||
## Fix
|
||||
|
||||
Run ESLint directly:
|
||||
|
||||
```bash
|
||||
eslint src/
|
||||
# or
|
||||
npx eslint src/ --ext .ts,.tsx
|
||||
```
|
||||
|
||||
Update `package.json` scripts:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "eslint src/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Type Checking
|
||||
|
||||
If ESLint config is broken (pre-existing `eslint.config.js` issues), use TypeScript compiler as fallback:
|
||||
|
||||
```bash
|
||||
tsc --noEmit
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- Payload CMS projects using Next.js 16 hit this during CI setup — replace `next lint` in all pipeline configs.
|
||||
- `next build` still runs type-checking internally, so CI can rely on that as a final gate.
|
||||
52
wiki/concepts/overflow-hidden-clips-absolute-children.md
Normal file
52
wiki/concepts/overflow-hidden-clips-absolute-children.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: "overflow:hidden Clips Absolute/Fixed Children Regardless of z-index"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Rule
|
||||
|
||||
An element with `overflow: hidden` clips **all** descendant content at its boundary — including absolutely or fixed positioned children — regardless of `z-index`.
|
||||
|
||||
`z-index` controls stacking order within the same stacking context. It does NOT control whether an element is clipped by an ancestor's `overflow: hidden`.
|
||||
|
||||
## Symptoms
|
||||
|
||||
- A dropdown menu or tooltip inside a carousel/slider container is invisible when it extends beyond the container's edge
|
||||
- Buttons with `z-50` or `z-[100]` still appear cut off
|
||||
- The clipping disappears when `overflow: hidden` is removed from the ancestor
|
||||
|
||||
## Example
|
||||
|
||||
```html
|
||||
<!-- ❌ Button is clipped at the edge of the overflow:hidden container -->
|
||||
<div style="overflow: hidden; height: 400px;">
|
||||
<div class="marquee-track">
|
||||
...
|
||||
<button class="z-50 absolute -top-4">←</button> <!-- CLIPPED -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Button outside overflow:hidden -->
|
||||
<div class="relative">
|
||||
<div style="overflow: hidden; height: 400px;">
|
||||
<div class="marquee-track">...</div>
|
||||
</div>
|
||||
<button class="z-50 absolute -top-4 left-0">←</button> <!-- Visible -->
|
||||
</div>
|
||||
```
|
||||
|
||||
## Fix
|
||||
|
||||
Move any interactive element that must extend beyond the clipping region **outside** the `overflow: hidden` container. Position it via a wrapper `div` with `position: relative`.
|
||||
|
||||
## Common Occurrences
|
||||
|
||||
- Carousel navigation buttons inside a clipped slider track
|
||||
- Dropdown menus inside a table cell with overflow clipping
|
||||
- Tooltips inside scrollable containers
|
||||
- Sticky headers inside fixed-height overflow containers
|
||||
|
||||
## Note on `overflow: clip`
|
||||
|
||||
CSS `overflow: clip` (newer) has even stricter clipping — it also clips fixed positioned descendants. `overflow: hidden` does not clip `position: fixed` elements in modern browsers, but `clip` does.
|
||||
53
wiki/concepts/payload-cms-node26-esm-workaround.md
Normal file
53
wiki/concepts/payload-cms-node26-esm-workaround.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: "Payload CMS + Node 26 — Seed Script ESM Workaround"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Running a Payload CMS seed script directly via `npx tsx seed.ts` on **Node 26** fails with:
|
||||
|
||||
```
|
||||
Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported
|
||||
```
|
||||
|
||||
Payload internals use CJS-style `require()` calls that break under Node 26's stricter ESM handling when executed outside the Next.js server context.
|
||||
|
||||
## Workaround — API Route
|
||||
|
||||
Create a Next.js API route that calls `getPayload()` inside the running server. This avoids the ESM issue because Payload is already loaded in the Next.js runtime context.
|
||||
|
||||
```ts
|
||||
// app/api/seed/route.ts
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function POST() {
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// Example: seed a collection
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'admin@example.com',
|
||||
password: 'secret',
|
||||
role: 'admin',
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
```
|
||||
|
||||
Then trigger it while dev server is running:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/seed
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Protect this route with an env-guard (`if (process.env.NODE_ENV !== 'development') return 405`) before deploying.
|
||||
- Alternatively, downgrade to Node 22 LTS for Payload projects until official Node 26 support lands.
|
||||
46
wiki/concepts/payload-cms-root-layout-requirement.md
Normal file
46
wiki/concepts/payload-cms-root-layout-requirement.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Payload CMS 3.x — (payload)/layout.tsx Must Use RootLayout"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
In a Payload CMS 3.x + Next.js project, the route group `(payload)/layout.tsx` **must** import and render `RootLayout` from `@payloadcms/next/layouts`.
|
||||
|
||||
If the file is empty, exports a plain passthrough, or uses a custom layout that doesn't include `RootLayout`, **every Payload admin client component** that calls `useConfig()` crashes with:
|
||||
|
||||
```
|
||||
Cannot destructure property 'config' of undefined
|
||||
```
|
||||
|
||||
This appears as a 500 error on the `/admin` panel with no other diagnostic signal pointing to the layout.
|
||||
|
||||
## Correct layout.tsx
|
||||
|
||||
```tsx
|
||||
// app/(payload)/layout.tsx
|
||||
import { RootLayout } from '@payloadcms/next/layouts'
|
||||
import config from '@payload-config'
|
||||
import { handleServerFunctions } from 'payload/server'
|
||||
import React from 'react'
|
||||
|
||||
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||
const serverFunctions = await handleServerFunctions({ config })
|
||||
return (
|
||||
<RootLayout config={config} serverFunctions={serverFunctions}>
|
||||
{children}
|
||||
</RootLayout>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Why
|
||||
|
||||
`RootLayout` mounts Payload's `ConfigProvider` React context. `useConfig()` reads from that context. Without `ConfigProvider` in the tree, the context value is `undefined`, causing the destructure crash on every page.
|
||||
|
||||
## Related Gotchas
|
||||
|
||||
- **Turbopack**: Payload CMS 3.x is incompatible with Turbopack. Add `--webpack` to the `dev` script; otherwise the admin panel crashes.
|
||||
- **Next.js 16 lint**: `next lint` is removed. Use `eslint src/` directly.
|
||||
- **Node 26 seed scripts**: Running `npx tsx seed.ts` directly fails with `ERR_REQUIRE_ESM`; use an API route instead — see [[wiki/concepts/payload-cms-node26-esm-workaround]].
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: "Pydantic exclude_none vs Intentional null Fields"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
`model_dump(exclude_none=True)` removes **all** fields whose value is `None`, including fields that intentionally send `null` to the API (e.g., `clear_icon=None` to clear an asset in a PATCH request).
|
||||
|
||||
The result: the field is omitted from the payload entirely, so the server never receives the `null` and the clear action silently fails.
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
class UpdateIconPayload(BaseModel):
|
||||
icon_url: str | None = None
|
||||
clear_icon: bool | None = None # None means "clear the icon"
|
||||
|
||||
payload = UpdateIconPayload(icon_url=None, clear_icon=None)
|
||||
|
||||
# Wrong — clear_icon is dropped entirely:
|
||||
payload.model_dump(exclude_none=True) # {}
|
||||
|
||||
# Correct — send only what you need explicitly:
|
||||
{"clear_icon": None}
|
||||
```
|
||||
|
||||
## Solution
|
||||
|
||||
Do not use `exclude_none=True` when any `None` value is semantically meaningful. Instead:
|
||||
|
||||
1. **Explicit enumeration** — build the dict by hand, including only the fields you want:
|
||||
```python
|
||||
body = {}
|
||||
if icon_url is not ...:
|
||||
body["icon_url"] = icon_url
|
||||
if clear_icon is not ...:
|
||||
body["clear_icon"] = clear_icon
|
||||
```
|
||||
|
||||
2. **Sentinel value** — use `...` (Ellipsis) or a custom `UNSET` sentinel instead of `None` for "not provided", then filter on that:
|
||||
```python
|
||||
UNSET = object()
|
||||
payload = {k: v for k, v in data.items() if v is not UNSET}
|
||||
```
|
||||
|
||||
## Rule of Thumb
|
||||
|
||||
`exclude_none=True` is safe only when `None` always means "omit this field". If `None` means "clear / nullify this field on the server", never use it — enumerate fields explicitly.
|
||||
48
wiki/concepts/react-state-playwright-css-hover.md
Normal file
48
wiki/concepts/react-state-playwright-css-hover.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
title: "React useState Dropdown — CSS group-hover vs useState for Playwright"
|
||||
source: daily/2026-05-10.md
|
||||
updated: 2026-05-10
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
Playwright `hover()` on a trigger element does not reliably open a React dropdown that uses `useState` for visibility:
|
||||
|
||||
```tsx
|
||||
// ❌ Playwright hover misses this
|
||||
const [open, setOpen] = useState(false)
|
||||
<button onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
|
||||
Menu
|
||||
</button>
|
||||
{open && <DropdownMenu />}
|
||||
```
|
||||
|
||||
Root cause: `useState` updates are asynchronous — they schedule a re-render. By the time Playwright checks for the dropdown element, the render cycle may not have completed, so `getByRole('menu')` returns nothing.
|
||||
|
||||
## Fix — CSS `group-hover:`
|
||||
|
||||
Replace JS state with a pure-CSS hover using Tailwind's `group` / `group-hover:` utilities:
|
||||
|
||||
```tsx
|
||||
// ✅ Playwright hover works reliably
|
||||
<div className="group relative">
|
||||
<button>Menu</button>
|
||||
<div className="hidden group-hover:block absolute top-full left-0">
|
||||
<DropdownMenu />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
The dropdown is always in the DOM; `group-hover:block` just changes `display`. No render cycle, no timing issues — Playwright sees the element immediately after hover.
|
||||
|
||||
## Secondary Benefit
|
||||
|
||||
CSS hover-driven dropdowns also fix the `overflow: hidden` clipping issue — the dropdown is rendered at the right DOM level and can be placed outside the clipping ancestor. See [[wiki/concepts/overflow-hidden-clips-absolute-children]].
|
||||
|
||||
## When useState Is Still Needed
|
||||
|
||||
- Keyboard accessibility (Escape to close, focus trapping)
|
||||
- Mobile (no hover events) with click-to-toggle
|
||||
- Complex open/close animations that require knowing state
|
||||
|
||||
In those cases, add an explicit `await page.waitForSelector('[role=menu]')` in Playwright tests rather than relying on hover timing.
|
||||
14
wiki/log.md
14
wiki/log.md
|
|
@ -1,6 +1,20 @@
|
|||
|
||||
# Build Log
|
||||
|
||||
## [2026-05-10T23:00:00+01:00] compile | daily/2026-05-10.md (Shumiland + Payload CMS + banner-tool session)
|
||||
- Source: daily/2026-05-10.md
|
||||
- Articles created: [[wiki/concepts/pydantic-exclude-none-null-clearing-conflict]], [[wiki/concepts/payload-cms-root-layout-requirement]], [[wiki/concepts/payload-cms-node26-esm-workaround]], [[wiki/concepts/css-marquee-animation-gpu-pattern]], [[wiki/concepts/css-animation-js-scroll-conflict]], [[wiki/concepts/overflow-hidden-clips-absolute-children]], [[wiki/concepts/react-state-playwright-css-hover]], [[wiki/concepts/figma-mcp-oauth-reconnect-restart]], [[wiki/concepts/nextjs16-lint-command-removed]]
|
||||
- Articles updated: none
|
||||
- Index updates: [[wiki/concepts/_index]] (192→201, +9 entries); [[wiki/_master-index]] (concepts 192→201)
|
||||
- Note: Long multi-project session — Shumiland (GallerySlider marquee animation debugging: JS scroll vs CSS animation conflict, overflow:hidden button clipping, animationDelay phase-shift for navigation), kvytky/Payload CMS setup (RootLayout crash, Node 26 ESM seed workaround, Turbopack incompatibility), banner-tool (DELETE variant endpoint + confirm dialog), Barclays logo URL fix, Figma MCP OAuth reconnect quirk
|
||||
|
||||
## [2026-05-10T22:00:00+01:00] compile | daily/2026-05-08.md (resumed — additional articles)
|
||||
- Source: daily/2026-05-08.md
|
||||
- Articles created (this pass): [[wiki/concepts/google-cloud-tts-ai-studio-separation]], [[wiki/concepts/pydantic-mongodb-patch-missing-fields]], [[wiki/concepts/celery-inflight-tasks-lost-on-restart]], [[wiki/concepts/caption-aligner-cursor-drift]], [[wiki/concepts/cmux-ghostty-wrapper-config]]
|
||||
- Articles updated: [[wiki/concepts/poetry-docker-version-mismatch]] (added server-side poetry.lock conflict resolution: `git checkout backend/poetry.lock && git pull`)
|
||||
- Index updates: [[wiki/concepts/_index]] (184→190, +6 entries); [[wiki/_master-index]] (184→190)
|
||||
- Note: Resumed after context break. Previous session pass (below) created 8 files but ran out of context before creating comprehensive merged articles; this pass added google-cloud-tts comprehensive article (covers TTS service separation + model_name version gate + IAM role), merged pydantic/mongo and celery/restart articles, caption aligner cursor drift, and cmux/Ghostty config split. Some conceptual overlap exists with previous pass files — both sets are indexed.
|
||||
|
||||
## [2026-05-10T21:00:00+01:00] compile | daily/2026-05-08.md (video-accessibility session)
|
||||
- Source: daily/2026-05-08.md
|
||||
- Articles created: [[wiki/concepts/async-def-without-await-silent-coroutine]], [[wiki/concepts/google-cloud-tts-vs-ai-studio-tts]], [[wiki/concepts/google-cloud-texttospeech-version-gate]], [[wiki/concepts/caption-aligner-cursor-stall-cascade]], [[wiki/concepts/celery-worker-restart-inflight-task-loss]], [[wiki/concepts/poetry-lock-server-conflict-resolution]], [[wiki/concepts/silent-exception-swallow-falsy-guard]], [[wiki/concepts/pydantic-required-fields-manual-mongodb-patch]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue