Skip to content

Measure Pele render time on a phone and set the default diagram limits #522

Closed

Description

@HMarzban

Part of #521. Read the tracker first: its §Shared contract binds this issue. Work on main.

Summary

A throwaway measurement, outside the tracked tree, that sets the default limits for the new package (D10). It times Pele render plus DOMPurify sanitize across a size ladder. The maintainer then confirms on a real mid-range phone. The public API freezes only at this issue's checkpoint.

Rulings: 1, 10, 11, 12 (#521 §Maintainer rulings).

Files and interfaces

Files (all gitignored; H is REPO/Notes/local-docs/mermaid-measure)

  • Create: H/package.json, H/index.html, H/bench.ts, H/examples.md (a copy of https://pele.run/examples.md).
  • Create: the measure note, REPO/Notes/local-docs/<measure date>-mermaid-render-measure.md (<measure date> is this run's date, YYYY-MM-DD).

Interfaces

Measurement rules

  • One sample is performance.now() around one synchronous mount(el, source, OPTIONS) on a fresh connected div (width min(720px, 100%)). OPTIONS.sanitize runs inside mount, so a sample is render plus sanitize, like durationMs. Destroy and remove the element after each sample.
  • Before the first sample of a fixture, run detectType in try/catch and await load(type). Loading is never timed.
  • One warm-up sample, then 50 timed samples. Yield with setTimeout(r, 0) between samples, and keep the tab in the foreground.
  • Percentiles are nearest-rank. With 50 samples, p99 is the maximum.
  • OPTIONS: { limit: 20000, maxEdges: 1000, outputLimit: 1000000, links: false, images: false, zoom: matchMedia('(pointer: coarse)').matches ? false : 'auto', sanitize, now: Date.UTC(2026, 0, 1) }, where sanitize is the D8 config ([Tracking] Mermaid diagrams in docs.plus through a headless Pele extension #521 §Shared contract → Behaviour) with dompurify 3.4.15.
  • Ladder levels (characters/edges): 20000/1000, 15000/750, 10000/500, 5000/250.
  • Fixtures:
    • starter: the [Tracking] Mermaid diagrams in docs.plus through a headless Pele extension #521 §Commands starter.
    • Seven grown fixtures (sequence, class, state, er, gantt, gitGraph, mindmap). Each grows one valid line at a time to just under the level's characters. The builder writes the templates.
    • flowchart-edges: flowchart TD plus exactly edges distinct edges.
    • Every ```mermaid block in examples.md, plus the source info.
  • A grown fixture that does not reach ready at the top level is a harness defect. Fix its template. The one exception is error:limit(output), a real finding: the characters cap is above what outputLimit allows.
  • Line probe: L0 flowchart LR\n A --> B\n B --> (error on editor line 3); L1 the same after a 3-line --- frontmatter (editor line 6); L2 the same after %%{init: {}}%% (editor line 4). Record PeleError.line and column.
  • Sanitize probe: run mount with a capturing sanitize that keeps Pele's raw SVG string and the sanitized fragment. Use every examples.md block, info, and flowchart LR\n accTitle: Order flow\n accDescr: Two steps\n A --> B. In both trees, keep one count for each attribute name (id, style, class, role, aria-roledescription, aria-labelledby, aria-describedby), plus the <title> and <desc> element counts, and compare the root text. A mismatch is any single count that differs. It is a finding for the maintainer. Do not change the D8 config here.

Implementation steps

  • Step 1: Install the harness and prove the repo is untouched.

    • Run curl -s https://registry.npmjs.org/pele/latest | grep -o '"version":"[^"]*"'. If it is newer than 1.6.0, use that version everywhere ([Tracking] Mermaid diagrams in docs.plus through a headless Pele extension #521 §Shared contract → Pele).
    • H/package.json: { "name": "mermaid-measure", "private": true, "type": "module", "dependencies": { "dompurify": "3.4.15", "pele": "1.6.0" } }.
    • First save git -C REPO status --porcelain to <scratchpad>/status-before.txt.
    • Run mkdir -p REPO/Notes/local-docs/mermaid-measure && cd REPO/Notes/local-docs/mermaid-measure && bun install, then curl -fsS https://pele.run/examples.md -o examples.md.
    • Run git -C REPO status --porcelain and git -C REPO diff --stat bun.lock. Expected: the status matches status-before.txt, and the diff prints nothing. Otherwise stop: Bun treated H as a workspace member.
  • Step 2: Write H/index.html and H/bench.ts.

    • index.html holds <div id="host" style="width:min(720px,100%)">, <pre id="out"> and the module script ./bench.ts.
    • bench.ts imports only pele/lazy and dompurify, and examples.md as text: import examples from './examples.md' with { type: 'text' }. It has two modes: ?mode=time&label=<label> and ?mode=probe.
    • Time mode prints one line label=<label> zoom=<zoom> host=<px> viewport=<px>, then a table | fixture | level | chars | edges | first ms | p50 | p95 | p99 | state |, then DONE <label> <rows> rows. state is ready or error:<code>. Print error:limit(output) when the message starts with "Diagram output is longer".
    • Probe mode prints the line-probe table and the sanitize table (fixture | state | mismatches | text equal), then DONE probes.
  • Step 3: Run the laptop timing. Run bun ./index.html in H. Open ?mode=time&label=laptop in desktop Chrome, with no throttle and DevTools closed. Expected: DONE laptop and zoom=auto.

  • Step 4: Run the phone proxy. In DevTools, choose device mode Moto G Power and CPU 6× slowdown, then load ?mode=time&label=6x. Expected: DONE 6x, zoom=false, and a viewport near 412 px.

  • Step 5: Run the probes. Load ?mode=probe with no throttle. Expected: DONE probes, no sanitize mismatch, and every text equal true. Record the actual values either way.

  • Step 6: Apply the D10 rule.

    • A ladder row passes when its 6x p99 <= 200. A level passes when all its rows pass.
    • characters is the highest level where all seven grown fixtures pass. edges is the highest level where flowchart-edges passes.
    • A failing starter or example row is a finding. Lowering the limits does not fix it.
    • List every laptop p95 over 16 ms under "Spec gap: laptop gate scope". It never cuts the limits.
    • If no level passes, stop and ask the maintainer. D10 rules out a worker.
  • Step 7: Write the measure note and post its verdict. Sections: Setup (machine, Chrome and Bun versions, the rules), Results (the two tables), D10 verdict (DEFAULT_LIMITS = { characters: N, edges: M } and the starter row laptop p95), Probes, Findings, and "Phone confirmation: Pending maintainer." Give the maintainer the Setup, D10 verdict, Probes and Findings sections to post on this issue.

  • Step 8: Review checkpoint and maintainer gate.

    • Run git -C REPO status --porcelain. Expected: it matches status-before.txt.
    • The maintainer confirms on a real mid-range Android phone: serve with bun ./index.html, run adb reverse tcp:<port> tcp:<port>, and open http://localhost:<port>/?mode=time&label=phone in Chrome. Apply the Step 6 rule to the phone rows. If the phone gives lower limits, the phone wins.
    • Stop. Scaffold @docs.plus/extension-mermaid with its types and the insertMermaid command #523 starts after the maintainer confirms the limits and rules on the laptop gap (R1).

Decisions

  • R1. Do the phone numbers win when they give lower limits, and are laptop p95 rows over 16 ms findings only? Recommended: yes to both. The design names no consequence for the laptop number.

Acceptance criteria

  • The posted comment holds the Setup, D10 verdict and Findings sections, with N and M.
  • The maintainer has posted the phone confirmation and the R1 ruling on this issue.
  • git -C REPO status --porcelain matched status-before.txt at the checkpoint.

Activity

  1. HMarzban commented on Oct 19, 2026

    @HMarzban
    CollaboratorAuthor

    Maintainer ruling, 2026-10-19

    R1 is confirmed: phone numbers win when they give lower limits, and laptop p95 rows over 16 ms are findings only. The headless 6x CPU stand-in is accepted in place of Step 4. 2.2.0 does not ship until the Step 8 phone run confirms or lowers characters: 15000 and edges: 500.

  2. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    Step 8 phone run, 2026-10-21: the limits come down to 10000 characters and 250 edges

    The maintainer ran the harness on three devices over Wi-Fi (bun ./index.html --hostname=0.0.0.0), with pele 1.7.0, the version that ships. The harness now prints a Step 6 verdict box after DONE. Each device finished all 92 rows. The table shows the worst p99 for each level (rule: p99 ≤ 200 ms).

    Level Mac chars / edges iPhone (Safari) chars / edges Android (Chrome) chars / edges
    20000/1000 51.1 / 67.8 180.0 / 374.0 1945.7 (mindmap) / 1936.9
    15000/750 37.1 / 55.5 132.0 / 328.0 1096.8 (er) / 286.6
    10000/500 25.2 / 37.2 94.0 / 229.0 176.2 (state) / 202.4
    5000/250 15.3 / 20.7 51.0 / 119.0 105.0 (state) / 118.6
    starter + 59 examples 3.60 11.0 21.3
    Limits it allows 20000 / 1000 20000 / 250 10000 / 250

    Bold cells fail. Under R1, the phone wins when it gives lower limits, so:

    DEFAULT_LIMITS = { characters: 10000, edges: 250 }, down from the provisional 15000 / 500.

    • Edges: both phones fail at 500 edges. Android missed by 2.4 ms, and the iPhone by 29 ms, so this is not a one-device result.
    • Characters: Android at 15000 took 1096.8 ms, more than 5 times the budget. At 10000 it passes at 176.2 ms.
    • The starter row and every example pass with a wide margin on all three devices.
    • The Mac numbers are findings only (R1). No laptop row is over 16 ms at the new limits.

    Next: lower DEFAULT_LIMITS in extensions/extension-mermaid/src/constants.ts and LIMITS in apps/hocuspocus.server/src/modules/mcp/domain/checkDiagrams.ts together, because they must match. Then update the tests and every doc that states 15000 or 500.

  3. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    Live on production on 2026-10-21 (5c28ca8b7, deploy tip 282747489). The webapp bundle carries characters: 10000, edges: 250, and no bundle keeps the old 15000 / 500. The MCP check in rest-api carries LIMITS = { limit: 10000, maxEdges: 250 }, so the two still match. Docs, both CHANGELOGs and the extension README name the phone-measured numbers (docs/pele.md §Measured on real phones).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions