Twenty defects found by reading the code, ordered by consequence. None are fixed — D1 is the most consequential.
Found by reading the code for the reference document. Ordered by consequence. None are fixed.
compositions.ts fits every text layer through fit() with the default Measure, then maps applyFonts over the result. So the size is chosen using sans metrics and the layer is then given the theme’s real face. Mono measures 1.15× wider and the safety margin is 2%.
Reproduced on the Terminal style with a four-slide deck — 3 of 4 slides overflow:
slide 2 "Heading" font=mono size=58 box=67 needs=133
slide 3 "Statement" font=mono size=76 box=448 needs=538
slide 4 "Text" font=mono size=68 box=82 needs=163Serif is safe only by luck: it measures at 0.97, so sans over-estimates. The "text never overflows" guarantee currently holds for sans and serif, not for mono — and not for grotesk, slab, elegant or impact, which have no FAMILY_SCALE entry at all and silently take sans metrics. impact is condensed and elegant is a Didot; both will mis-fit.
Fix: pass the family into Measure at fit time, and apply fonts before fitting rather than after.
Canvas.tsx. Position and size derive from the captured origin rect; fontSize derives from l.fontSize, the already-updated layer. So the cumulative ratio is re-applied on every pointer-move event:
move 1 k=1.1 64 → 70 (correct)
move 2 k=1.2 70 → 84 (should be 77)
move 3 k=1.3 84 → 109 (should be 83)Fix: derive from the origin rect’s font size, captured alongside the geometry.
app-only-slide-count can never be useful advice. Its condition requires count > maxSlides, which is exactly the too-many-slides block condition. A 15-slide Instagram deck gets a warning saying it "works if you post by hand" next to a hard block that disables the export button. Fix: block at appMaxSlides ?? maxSlides.intrudes() assumes symmetric safe-zone insets. It tests l.w >= box.w + box.x * 2 - 1, using the left inset twice. TikTok’s insets are 40 left and 180 right, so the effective full-bleed threshold is 939×1539 rather than 1080×1920 — any layer above that size is exempted wherever it sits.weak-hook warning for the visible copy underneath.Platform, two named Finding. pipeline.ts exports a four-value union including "x"; platforms.ts exports three ids and no "x". A post on "x" has no artboard, no safe zone, no pre-flight and no export target.whatOutliersShare is ungated. It applies neither MIN_TOTAL nor MIN_GROUP, so with a single outlier it can report six "shared traits" from n=1 — contradicting its own file header.Scored.ratio divides by the windowed baseline (last 20); Group.lift divides by the median of all measured posts. The Outliers screen calls both "your median" in adjacent paragraphs.bodyFont reaches exactly one layer. applyFonts gives theme.bodyFont only to a layer literally named "Body". Quote prose and numbered body text all take the display face, despite the module comment saying otherwise.buildFrameworkSamples and buildDocs never stamp doc.framework or doc.styleId, so they are excluded from the attribution that is the product’s main differentiator — even though the bulk UI made the user pick a framework.images: "always" and "sometimes" are identical downstream; only "never" differs.bounds, rectOf, contains, intersects, snap, resize, the selection outline, the handles and reflow’s clamp all use the unrotated box. Past roughly 15°, a layer is grabbable where it is not drawn.visible && !locked; marquee filters only visible. Once selected, a locked layer can be moved, nudged, resized and deleted.Studio.tsx hardcodes THEMES.ink, so slides appended to a Paper or Cobalt project arrive in Ink colours. It also does not enforce MAX_SLIDES.tombstones.write() swallows quota errors, so under quota pressure a delete succeeds locally, records nothing, and the next sync resurrects it. This is the one failure mode that defeats the whole design.PostRow.impressions documents a column that does not exist. The generated column was removed while de-risking a failed migration and the comment was not./api/draft and /api/export take no authentication. Anyone who can reach port 8787 can burn Anthropic tokens or drive Chromium renders. render.ts interpolates client-supplied HTML and CSS unescaped, the browser context is not network-isolated, and there is no rate limiting or CORS policy anywhere.Upgrade.tsx sells things nothing gates. PNG export, AI drafting and the pipeline are advertised as Pro. No screen reads plan, no platform emits PNG, and 02-pro-gate.sql is unrun.CLAUDE.md still lists image upload as not built; media.ts and MediaPool.tsx shipped long agopaint.ts#layerPaint is dead and disagrees with LayerView about whether a fill sits under a gradientFAMILY_SCALE.mono is unreachable — the mono branch substitutes MONO_ADVANCE firstuseStudio.canUndo reads a ref at render time, so it is never reactive; canRedo does not existundo/redo mutate refs inside a setDoc updater, which StrictMode double-invokesM = 96 in compositions.ts, M = 88 in presets.tscompositionLabel, slidesFromText, GRADIENT_STYLE_IDS, asChoice, isValidHex, NO_INSET, hasSafeZone, AuthState, setDisplayName, fetchBillingStatus, closeBrowser, boardCounts, outlierCount, DEMO_COUNTeducational slot 4 is labelled "Breakdown" with placeholder "Technique #1" while slots 5–6 are "Example" with "Technique #2/#3" — labels and placeholders disagreeindex.css and R4 forbid