nexu-io/open-design · error · OverflowError

Cursor exceeded rail at '{label}': y={self.y} cap={self.cap}

Error message

Cursor exceeded rail at '{label}': y={self.y} cap={self.cap}; history={self.history}

What it means

This is a user-raised Python `OverflowError` from the `Cursor` class in the pptx-html-fidelity-audit skill's export script. `Cursor` walks down a slide allocating vertical space for each content block (kicker, headline, lead, body, grids, etc.); after every `take(h, gap, label)` call it advances `self.y += h + gap` and checks that the new position stays within the footer rail (`CONTENT_MAX_Y`, 6.70"). If a block — plus its gap — would cross that rail, the cursor refuses to render and throws, because content past the rail would overlap the pinned footer at presentation time. The error message includes the offending `label`, the current `y`, the `cap`, and the full allocation `history`, so you can see exactly which block tipped it over.

Source

Thrown at skills/pptx-html-fidelity-audit/references/layout-discipline.md:66

```

## 2. The Cursor primitive

Used on all non-hero slides. The cursor advances down the slide and refuses to cross `CONTENT_MAX_Y`.

```python
class Cursor:
    def __init__(self, y_start=CONTENT_TOP, cap=CONTENT_MAX_Y):
        self.y = y_start
        self.cap = cap
        self.history = []   # list of (top, height, label) for debugging

    def take(self, h, gap=Inches(0.12), label=""):
        top = self.y
        self.y = top + h + gap
        self.history.append((top, h, label))
        if self.y > self.cap:
            raise OverflowError(
                f"Cursor exceeded rail at '{label}': "
                f"y={self.y} cap={self.cap}; "
                f"history={self.history}"
            )
        return top

    def remaining(self):
        return self.cap - self.y
```

Usage:

```python
c = Cursor()
add_kicker(slide, top=c.take(Inches(0.18), label="kicker"))
add_h_xl(slide,   top=c.take(Inches(1.0),  label="h-xl"))
add_lead(slide,   top=c.take(Inches(0.8),  label="lead"))
add_pipeline(slide, top=c.take(Inches(2.6), label="pipeline"))

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the last tuple in the error's `history` — that `(top, h, label)` is the block that overflowed. Compare `h` against the actual rendered text height (`n_lines * line_height_pt / 72 + ~0.05"`); if the box was oversized, tighten `take(h)` to the real height and rerun.
  2. Shrink the inter-block gap for the offending region: pass a smaller `gap=` to `take()` (e.g. trim from `Inches(0.18)` to `Inches(0.10)`), or override the `Cursor` default `gap` at instantiation for CJK / Thai / Khmer decks that need MORE gap while trimming Latin blocks that need less.
  3. Call `c.remaining()` between blocks during layout debugging to find which early block is eating the budget; rebalance heights across kicker / headline / lead so no single section is over-allocated.
  4. If the content genuinely does not fit on one slide, split it into two slides — the doc is explicit that this is a design problem, not a layout problem. Do NOT raise `CONTENT_MAX_Y` to silence the error; the rail protects the footer and overriding it produces visible overlap in fullscreen presentation.

Example fix

// before
c = Cursor()
add_kicker(slide, top=c.take(Inches(0.18), label="kicker"), text=kicker)
add_h_xl(slide,   top=c.take(Inches(1.20), label="h-xl"), text=headline)  # oversized
add_lead(slide,   top=c.take(Inches(0.80), label="lead"), text=intro)
add_pipeline(slide, top=c.take(Inches(3.10), label="pipeline"), ...)  # OverflowError here

// after — tighten the headline to real text height and trim gaps
c = Cursor()
add_kicker(slide, top=c.take(Inches(0.18), gap=Inches(0.10), label="kicker"), text=kicker)
add_h_xl(slide,   top=c.take(Inches(0.75), gap=Inches(0.10), label="h-xl"), text=headline)
add_lead(slide,   top=c.take(Inches(0.55), gap=Inches(0.10), label="lead"), text=intro)
add_pipeline(slide, top=c.take(c.remaining(), label="pipeline"), ...)
Defensive patterns

Strategy: validation

Validate before calling

def safe_take(cursor, h, gap=None, label=""):
    gap = gap if gap is not None else Inches(0.12)
    if cursor.y + h + gap > cursor.cap:
        raise ValueError(
            f"Block '{label}' (h={h}, gap={gap}) would overflow the rail: "
            f"projected y={cursor.y + h + gap} > cap={cursor.cap}; "
            f"remaining={cursor.remaining()}. "
            f"Reduce h, reduce gap, or split the slide."
        )
    return cursor.take(h, gap=gap, label=label)

# Before committing a block, also pre-check the whole stack fits:
def fits(blocks, cursor, default_gap=Inches(0.12)):
    total = cursor.y
    for h, g, _ in blocks:
        total += h + (g if g is not None else default_gap)
    return total <= cursor.cap

Type guard

# python-pptx returns Emu (int subclass); validate the args before take()
from pptx.util import Emu
def is_valid_take_args(h, gap):
    return (
        isinstance(h, (int, Emu)) and h >= 0
        and isinstance(gap, (int, Emu)) and gap >= 0
    )
# Use: assert is_valid_take_args(h, gap), f"bad take args: h={h} gap={gap}"

Try / catch

try:
    top = c.take(h, gap=gap, label=label)
except OverflowError as e:
    # This is a layout-design bug, not a transient failure. Surface it
    # with the slide id so the export can be re-planned, do NOT silently
    # raise CONTENT_MAX_Y or skip the block.
    raise RuntimeError(f"Slide {slide_idx}: layout overflow — {e}") from e

Prevention

When it happens

Trigger: Calling `Cursor.take(h, gap=..., label=...)` when the sum of all previously taken blocks plus `h + gap` exceeds `cap` (default `CONTENT_MAX_Y`). Concretely: a content slide whose kicker + h-xl + lead + pipeline/cards sum to more than ~6.20" (CONTENT_MAX_Y minus CONTENT_TOP); a hero slide where `hero_layout(blocks)` computed `y_start` but the block stack still overruns `CANVAS_H - FOOTER_H - 0.2"`; or a single oversized `take(Inches(2.6), label="pipeline")` call that pushes past the rail after prior blocks already consumed most of the budget. The check fires after the offending block is appended to `history`, so the last entry in `history` is the culprit.

Common situations: The export script was translated from an HTML deck whose slide had more body copy / cards than fit on one 16:9 slide (genuine over-content, needs a split). A copy/pasted block height like `Inches(1.0)` for a headline that actually renders at 0.6" wastes budget and starves later blocks. The default `gap=Inches(0.12)` was left in place for a CJK / Devanagari / Thai / Khmer deck, where stacked tone marks inflate line height and the blocks collide sooner than the gap assumes. A grid recipe (3x2 observation cards) was given `row_h=Inches(1.10)` plus `Inches(0.20)` inter-row gap that no longer fits after a taller-than-planned intro. Someone tried to "fix" an earlier overflow by lowering `CONTENT_TOP` or raising `CONTENT_MAX_Y` instead of resizing content, masking the real overflow until a later block trips it.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/67cd286e3a0f0fe6. Report an issue: GitHub.