Documentation is work in progress and not completeEditor
FlashCC/Reality check/Section 29 of 29

Known defects

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.

D1 — Text is fitted with sans metrics, then restyled

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=163

Serif 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.

D2 — Resizing a text layer compounds its font size

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.

D3 – D10

  • •D3 — 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.
  • •D4 — 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.
  • •D5 — Hidden layers are skipped everywhere except the hook check. A hidden 88pt layer suppresses the weak-hook warning for the visible copy underneath.
  • •D6 — Two exported types named 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.
  • •D7 — 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.
  • •D8 — Two denominators, one label. 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.
  • •D9 — 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.
  • •D10 — Samples and bulk decks are invisible to analytics. 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.

D11 – D19

  • •D11 — Onboarding offers a choice generation does not make. images: "always" and "sometimes" are identical downstream; only "never" differs.
  • •D12 — Rotation is invisible to the entire geometry layer. 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.
  • •D13 — Locked layers are marquee-selectable. Click filters visible && !locked; marquee filters only visible. Once selected, a locked layer can be moved, nudged, resized and deleted.
  • •D14 — Pasting slides ignores the document’s style. 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.
  • •D15 — Undo coalescing has no boundary. Two separate drags minutes apart collapse into one undo step.
  • •D16 — A full quota silently defeats tombstones. 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.
  • •D17 — 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.
  • •D18 — The production posture is unfinished. /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.
  • •D19 — 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.

D20 — Smaller things

  • •CLAUDE.md still lists image upload as not built; media.ts and MediaPool.tsx shipped long ago
  • •paint.ts#layerPaint is dead and disagrees with LayerView about whether a fill sits under a gradient
  • •FAMILY_SCALE.mono is unreachable — the mono branch substitutes MONO_ADVANCE first
  • •useStudio.canUndo reads a ref at render time, so it is never reactive; canRedo does not exist
  • •undo/redo mutate refs inside a setDoc updater, which StrictMode double-invokes
  • •The same 10-colour palette literal is written out three times
  • •M = 96 in compositions.ts, M = 88 in presets.ts
  • •Dead exports: compositionLabel, slidesFromText, GRADIENT_STYLE_IDS, asChoice, isValidHex, NO_INSET, hasSafeZone, AuthState, setDisplayName, fetchBillingStatus, closeBrowser, boardCounts, outlierCount, DEMO_COUNT
  • •educational slot 4 is labelled "Breakdown" with placeholder "Technique #1" while slots 5–6 are "Example" with "Technique #2/#3" — labels and placeholders disagree
  • •Seven components re-enable transitions that index.css and R4 forbid