Skip to content

Spikes: #63 scale (linear, refuted) + #64 monospace probe round two - #79

Merged
leogdion merged 4 commits into
v0.1.xfrom
64-monospace-probe
Aug 3, 2026
Merged

Spikes: #63 scale (linear, refuted) + #64 monospace probe round two#79
leogdion merged 4 commits into
v0.1.xfrom
64-monospace-probe

Conversation

@leogdion

@leogdion leogdion commented Aug 3, 2026

Copy link
Copy Markdown
Member

Prepares the #64 Keynote-bound spike so the human pass is a short scripted check rather than authoring probe slides by hand. Does not close #64 — the render result is still yours to record.

Branches off v0.1.x directly; no dependency on any other lane.

What's here

  • MonospaceProbeContent.swift — four family slides (Menlo, SF Mono, Courier New, Monaco) + an indentation slide
  • Registered as its own monospaceProbe group in AcceptanceDeck
  • Checklist bullets added to AcceptanceDecksCommand (the command prints the human pass, so a new deck has to add its own)
  • research/findings/monospace_probe.md — procedure + empty results table

How to run it

export DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer
xcrun swift run AcceptanceDecks /path/to/out
open /path/to/out/monospace_probe.key   # copy first — Keynote autosaves in place

Render checklist

Slides 1–4, one per family. Each shows iiii / MMMM / 1111 at 96pt.

  • Monospaced → all three lines the same width, right edges in a clean column
  • Substituted → MMMM visibly wider, ragged edges → reject that family

This is the whole test — no measuring. A silent substitution still renders, which is exactly why the signal needs to be this blunt.

Slide 5. Leading-space indentation in Menlo; nested lines' left edges must form clean columns. If Menlo was rejected on slide 1 this slide proves nothing — re-run against whichever family survived.

Then fill in the results table in research/findings/monospace_probe.md.

Verification done here

  • swift run AcceptanceDecks — writes and self-checks (every record decodes, both SIGTRAP invariants hold via UUIDMapVerifier); deck has the expected 5 slide members
  • swift test — all six targets pass
  • LINT_MODE=STRICT ./Scripts/lint.sh — exit 0

Structural green is not a render pass; nothing here proves a font resolves.

One judgment call

I registered this in the acceptance catalog, which means it prints in every future human pass. That's noise for something that is spike evidence rather than a feature gate — but running it through AcceptanceDecks is what gets it the structural self-check for free, and a throwaway harness wouldn't. The findings file records the cleanup: once the working family is pinned in the demo deck, drop the monospaceProbe group and delete the content file.

Refs #64

🤖 Generated with Claude Code

Prepares the #64 Keynote-bound spike so the human pass is a short scripted
check rather than authoring probe slides by hand.

`monospace_probe.key` is four family slides (Menlo, SF Mono, Courier New,
Monaco) plus an indentation slide. Each family slide renders `iiii` / `MMMM`
/ `1111` at 96pt: in a genuinely monospaced face those three lines are the
same width and their right edges form a clean column; under a proportional
substitute `MMMM` is dramatically wider. That signal is legible at a glance,
which matters because a silent substitution still renders — it just isn't
monospaced.

The indentation slide checks that leading-space columns hold, which is what
#66 actually depends on.

Registered as its own `monospaceProbe` group rather than folded into an
existing one, and the findings file records how to remove it: this is spike
evidence, not a permanent acceptance deck, and it should not add noise to
every future human pass once the working family is pinned.

Structurally green via `swift run AcceptanceDecks` (all records decode, both
SIGTRAP invariants hold); `swift test` and `LINT_MODE=STRICT ./Scripts/lint.sh`
pass. The render result is Leo's to record.

Refs #64

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 819a6dad-87c3-4c72-a664-e450b39e49bf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

## #63 — measured, and the concern is refuted

`Sources/ScaleSpike` times `Deck.write(to:)` across a ladder of slide counts.
A single 20-slide number cannot separate "quadratic but small" from "linear",
so it sweeps 3/5/10/15/20 and reports the growth exponent.

    slides  drawables  median(s)
         3         12     0.1049
        20         80     0.6558

    growth exponent 3->20: 0.97   (1 = linear, 2 = quadratic)

A 20-slide demo-shaped deck writes in 0.66s and per-slide cost is flat
(0.035s at 3 slides, 0.033s at 20). The quadratic SHAPE in `expandSlides` is
real — `SlideCatalog` is rebuilt every loop iteration and `locate` is a full
scan — but at demo scale the constant factors dominate. **#44 is not a
v0.1.0 blocker.**

The deck is demo-shaped rather than minimal (4 drawables/slide, transition,
build) because cost scales with records per member; bare slides would
understate it. Findings: `research/findings/scale_spike.md`.

Still needs a human: open the kept 20-slide deck and confirm it renders.

## #64 — round one failed, but not the way it looked

All four families rendered proportional AND at template default size —
including Courier New, which certainly exists and is certainly monospaced.
Both symptoms together rule out font substitution: a missing family would
still honor the authored 96pt.

Probing the archive showed KeynoteKit writes the authored values correctly —
`fontName=Menlo fontSize=96.0` in a `TSWP.ParagraphStyleArchive` variation,
identical for single- and multi-line boxes. So the bytes are right and
Keynote ignored them.

The one structural difference from `TextFormattingContent`, which does render
its authored font, is paragraph count: that deck is one paragraph, every
round-one probe box was three. Hypothesis: whole-item font is only honored on
single-paragraph items.

Round two isolates that variable — slides 1-3 hold paragraph count at one and
vary family; slides 4-5 hold family and vary paragraph count and styling
route. If 1-3 render and 4 does not, it is a KeynoteKit bug that blocks #66,
since code blocks are inherently multi-line.

Refs #63, #64

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@leogdion leogdion changed the title Monospace probe deck for the #64 spike Spikes: #63 scale (linear, refuted) + #64 monospace probe round two Aug 3, 2026
@leogdion

leogdion commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Author update — both spikes now have results, and one of them changes the picture.

#63 — measured, concern refuted

Added Sources/ScaleSpike. A single 20-slide timing can't separate "quadratic but small" from "linear", so it sweeps a ladder and reports the growth exponent:

slides drawables median write (s)
3 12 0.1049
5 20 0.1547
10 40 0.2965
15 60 0.4645
20 80 0.6558

Growth exponent 3→20: 0.97. Per-slide cost is flat (0.035s at 3, 0.033s at 20) — the signature of linear scaling.

The quadratic shape in expandSlides is real, but at demo scale constant factors dominate. #44 stays v0.1.1. Extrapolating, ~100 slides is where it would start to bite; worth re-measuring before promising large-deck support.

Still yours: open the kept 20-slide deck and confirm it renders.

#64 — round one failed, but not because of monospace

You reported the render failed. Looking at your exports: all four families rendered proportional and at template default size — including Courier New, which certainly exists and is certainly monospaced.

Those two symptoms together rule out font substitution. If Keynote had merely substituted an unavailable family, the authored 96pt would still have applied — you'd see large proportional text. You saw small proportional text, meaning the whole authored style was dropped.

So I probed what we actually write:

=== MULTI-LINE + .font(Menlo, 96) ===
  para VARIATION id=2653720 fontName=Menlo fontSize=96.0
=== SINGLE-LINE + .font(Menlo, 96) ===
  para VARIATION id=2653720 fontName=Menlo fontSize=96.0

The archive is correct. We write the right bytes and Keynote ignored them — which makes this a KeynoteKit correctness question, not a font question.

The one structural difference from TextFormattingContent (which does render its authored font, verified 2026-07-31) is paragraph count: that deck is one paragraph; every round-one probe box was three.

Round two isolates exactly that — slides 1–3 hold paragraph count at one and vary family; slides 4–5 hold family and vary paragraph count and styling route.

Deck is generated at /Users/leo/Downloads/probe-round2/monospace_probe.key.

If Then
1–3 render, 4 doesn't whole-item font ignored on multi-paragraph boxes — a real bug, and it blocks #66 since code blocks are inherently multi-line
4 fails, 5 renders per-span styling is the workaround; #66 should emit styled spans
nothing renders, incl. 1 whole-item font regressed generally; #37's render evidence is stale
everything renders round one was an export artifact

I've left .font() untouched pending that read — fixing it on an unconfirmed hypothesis would be guessing.

leogdion and others added 2 commits August 3, 2026 09:49
Round two settles it by render. Monospace was never the problem.

    slide 1  Menlo, 1 paragraph            monospaced at 96pt   OK
    slide 4  Menlo, 3 paragraphs, item     template default     FAIL
    slide 5  Menlo, 3 paragraphs, spans    1st + 3rd only       PARTIAL

Slides 1 and 4 differ only in paragraph count — same family, same size, same
deck, same run. That is #81 (one `tableParaStyle` entry written regardless of
paragraph count), now confirmed visually as well as structurally.

Slide 5 is the useful surprise: styling each paragraph's own `Text` span
fixes the FIRST and THIRD paragraphs and leaves the SECOND proportional. So
per-span is a different off-by-one, not the same defect reappearing — and
#81's fix has to cover both routes. Its acceptance test needs at least three
paragraphs; a two-paragraph case would pass and hide this.

Consequences:

- `Menlo` is the demo deck's family and #66's `CodeTheme` default
- #66 is BLOCKED on #81 — neither styling route renders multi-line code
  correctly today, so shipping it now would produce unstyled code slides

Refs #64, #81, #66

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The condensed middle line in round two looked like a second off-by-one in
the per-span route. It was not. An isolation probe:

    iiii / xxxx / 1111    interior line renders Menlo — fine
    MMMM / iiii / 1111    MMMM fails in FIRST position
    MMMM alone            still condensed, single paragraph

`MMMM` fails wherever it appears, including alone on a slide with no
multi-paragraph involvement. Any other interior line renders correctly. That
is Keynote substituting a condensed face for capital M in Menlo at 90pt —
its shrink-to-fit on a wide glyph run — not a KeynoteKit defect.

The ruler was badly chosen: I picked `MMMM` precisely BECAUSE it is the
widest glyph, which is what provokes the substitution. A width comparison
needs glyphs that do not trigger fallback.

Consequence: #81's fix is complete, and #66 is unblocked.

Refs #64, #81, #66

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@leogdion
leogdion merged commit 64ac8a1 into v0.1.x Aug 3, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant