Back to Cocoindex

Pitfalls that cost iterations

dev/agent-skills/cocoindex-diagrams/references/pitfalls.md

1.0.77.7 KB
Original Source

Pitfalls that cost iterations

A record of mistakes encountered while building the current diagram set. Each entry: the symptom, the cause, and the fix.

All diagram boxes render as solid black

Symptom: Diagrams look like black rectangles in screenshots; text is dimly visible but all fills are black.

Cause: The docs site uses base: '/docs' in astro.config.mjs, so built HTML references CSS at /docs/_astro/*.css. A naïve python3 -m http.server from dist/ serves CSS at /_astro/... instead, returning 404. With no stylesheet, var(--cream), var(--coral), etc. are undefined; SVG fill falls back to black.

Fix: Serve from a parent dir containing a docs/ symlink or copy of dist/, so /docs/_astro/... resolves. The scripts/preview.sh script handles this automatically.

Rows/content faded at 35% opacity

Symptom: Multiple rows in a diagram all appear dim; only show at full opacity when the user hovers.

Cause: dg-step + dg-step-N classes. These are designed for progressive-reveal narratives (e.g., a 3-panel "step 1 → 2 → 3") where each step should fade in on hover with stagger. If applied to parallel rows that should all be visible statically, they fade by default.

Fix: Remove dg-step from the <g> wrapping the row. Only use dg-step when hover-driven reveal is intentional.

Labels overflow / clip inside narrow boxes

Symptom: "Split into chunks" sticks out past the box edges.

Cause: Early versions used SVG <text> which does not wrap. Manual line-splitting via lines={['Split into', 'chunks']} worked but was awkward.

Fix: All label-bearing primitives (DataBox, LogicBox, ProcessingComponent, TargetBullet) now use <foreignObject> with a flex-centered <div class="dg-fo-label">. The browser wraps against the box width. Just pass label="...".

Magic-number insets scatter across call sites

Symptom: <MemoMark x={PC_X + 14} y={row.pcY + 4} size={12} /> and <MemoMark x={embedX + 6} y={mid - EMBED_H / 2 - 2} size={10} /> — different offsets, different sizes, invisible coupling to container dimensions.

Cause: Inset values baked into call sites instead of the primitive.

Fix: The primitive owns its inset + size. Callers pass the container's reference corner (top-right for MemoMark). The primitive internally does translate(x - INSET_X - w, y). Same for ProcessingComponent's header and memo mark — the container primitive knows its own dimensions and handles all internal positioning.

Source vs target color accidentally diverged

Symptom: Drive Folder in palm-green, Vector Database in coral — suggests a distinction that doesn't exist in CocoIndex.

Cause: Early LogicBox had variant="source" / variant="target" modifiers with different fills. Kept around from homepage conventions that don't apply to docs diagrams.

Fix: One neutral cream + maroon fill for all non-container logic boxes. Shape (not color) carries the semantic distinction — TargetBullet is visually different from LogicBox because of its bullet shape, not because of color. Only App containers get the peach tint for visual grouping.

Arrows at different heights hit the wrong target

Symptom: vector1 at y=93 points to Vector Database, but the arrow line crosses over vector2 at y=147 because both converge to VDB center.

Cause: Drawing all arrows to the single y-center of the destination box.

Fix: Keep arrows horizontal — draw each to the destination's left edge at the source's y-coordinate. This only works when destinations are tall enough to accept multiple horizontal entries. See layout-patterns.md.

Arrowheads on binding lines

Symptom: Drive Folder → file and vector → Vector Database both have arrowheads and moving dashes, making them feel like causal flow.

Cause: Using FlowArrow everywhere instead of distinguishing flow from binding.

Fix: Connector (static, dashed, no arrowhead) for bindings; FlowArrow only for causal flow. A binding is "X is bound to Y" — Drive Folder → file (the file IS from the folder), vector → Vector Database (the vector IS written to the DB).

MemoMark fills solid black, looks too heavy

Symptom: Memo marks render as dark solid maroon ribbons that visually dominate small boxes.

Fix: Use coral outline + translucent coral fill (fill: color-mix(in oklab, var(--coral) 22%, transparent)), not a solid color. Reads over any background and matches the CocoIndex accent palette.

Asymmetric padding inside a container

Symptom: Content hugs the left side of an App/ProcessingComponent container with a big empty gap on the right. Or the first row hugs the top while the last row has a large gap below.

Cause: Hardcoding APP.w / APP.h to a round number and then placing content starting at a small APP_PAD. The content fits but leaves whatever remainder on the right/bottom.

Fix: Derive the container size from the content, not the other way around:

astro
const APP_PAD_X = 24;
const APP_W = FILE_W + GAP + PC_W + APP_PAD_X * 2;  // left pad == right pad
const APP = { x: 140, y: TOP_Y, w: APP_W, h: TOP_H };

Any downstream sibling (Target System, Drive Folder right) must then be positioned relative to APP.x + APP_W, not a hardcoded x. Same principle vertically: compute row y-centers so top-pad == bottom-pad.

See layout-patterns.md "Balanced padding on all sides" for the full idiom.

State class on a container cascades into its children

Symptom: A ProcessingComponent with pcState='updated' paints every nested .dg-box (chunks, embeds, vectors) gold — even the ones explicitly left at state='idle'.

Cause: The state CSS rules were written with the descendant combinator: .dg-state-updated .dg-box { stroke: gold; }. The container's state class matches, and its descendant .dg-box elements inherit the style.

Fix: Use the direct-child combinator > so the rule only applies to the box element on the same wrapper <g> that owns the state:

css
.dg-state-new > .dg-box    { ... }
.dg-state-updated > .dg-box { ... }
.dg-state-removed > .dg-box { ... }
.dg-state-changed > .dg-box { ... }

Each shape primitive (built on ShapeGroup) has its own wrapper <g> with its own state class and its own direct-child .dg-box. With >, states never cascade.

New state visuals that don't read against a stateful container

Symptom: A state="new" chunk inside a state="updated" PC looks identical to its sibling idle chunks — palm-green stroke is invisible against gold-tinted PC background.

Cause: Default stroke-width (1.4) makes colored state strokes too thin to pop against a tinted parent.

Fix: The dg-state-* rules in diagrams.css bump stroke-width to 2.2 for all delta states, and the hover dg-delta-pulse keyframes push it to 4.2 for extra visibility. If you add a new delta state, match the pattern.

Always-on animations distract on static pages

Symptom: A spinning icon (refresh badge) keeps spinning even when the user isn't looking at that diagram — makes the page feel busy.

Fix: Every animation in diagrams.css is idle by default, active only on .dg-root:hover. Includes dg-flow, dg-pulse, dg-delta-pulse, dg-spin (refresh badge), dg-check-draw (cache-ready check). When adding a new animation, gate it the same way — and register it in the @media (prefers-reduced-motion: reduce) override at the bottom of the stylesheet.

Reporting complete without looking

Symptom: "The build succeeded, diagram done." Then user screenshots show overlaps / black boxes / faded content.

Fix: Always run scripts/preview.sh and Read the PNG before reporting. A clean npm run build proves only that the Astro components compile.