Improve default slide generation styling - #4598
Conversation
This comment has been minimized.
This comment has been minimized.
|
Here's a visual recap of what changed: Open the full interactive recap |
There was a problem hiding this comment.
Builder reviewed your changes and found 2 potential issues 🟡
Review Details
Incremental Code Review Summary
The latest changes add regression assertions for the light Markdown palette and updated padding, but the production implementation still has the previously reported export, background, contrast, sanitization, and editor-token findings; those open comments were not reposted. The new review identified two additional regressions in the standalone export and imported-slide rendering paths.
New Findings
- 🟡 MEDIUM — Standalone HTML export now declares Inter/design-system fonts without loading or embedding them, so exported decks silently fall back to generic/local fonts and can reflow or clip text.
- 🟡 MEDIUM — Applying the new padding to every
.fmd-slidealso affects source-preserving imported PPTX/PDF wrappers, shifting the autofit coordinate system for absolute-positioned source elements and potentially triggering false overflow/transforms.
The fallback styling direction and added tests are useful, and the focused changed-slide tests pass according to the reviewers. However, the export needs a deliberate font-loading strategy and imported source canvases need to remain full-coordinate-space. This remains standard risk due to shared rendering/export behavior.
🧪 Browser testing: Will run after this review (PR touches UI code).
| width: 100%; height: 100%; | ||
| background: #111; | ||
| overflow: hidden; | ||
| font-family: 'Poppins', sans-serif; | ||
| font-family: 'Inter', sans-serif; |
There was a problem hiding this comment.
🟡 Provide the selected font in standalone HTML exports
The generated export now declares Inter or a design-system font family, but it no longer loads or embeds any font resource (the previous Poppins stylesheet was removed). On machines without those fonts, the file falls back to a generic sans-serif, changing line wrapping and potentially causing fit/clipping differences from the editor. Load a supported fallback font or provide a deliberate self-host/embed path for selected design-system fonts.
Additional Info
Found independently by 2 review agents.
There was a problem hiding this comment.
Fixed in 4d991c3. Standalone exports load supported selected Google Fonts, with the existing system fallback for other families.
There was a problem hiding this comment.
Builder reviewed your changes and found 5 potential issues 🟡
Review Details
Incremental Code Review Summary
The latest commit fixes the named dark utility detection and Builder-proxy fallback issues previously reported; those comments were resolved and are not reposted. The remaining open default-font export comment also remains unchanged. The new implementation adds token substitution, background compatibility, and broader inline-editor styling, but introduces several consistency and state issues.
New Findings
- 🟡 MEDIUM — Standalone export still drops most valid named/gradient Tailwind backgrounds, falling back to the neutral canvas.
- 🟡 MEDIUM — Builder token substitution occurs after sanitization and can reintroduce unsafe CSS into the generated style attribute.
- 🟡 MEDIUM — Builder-tokenized slide backgrounds are not resolved because the
--ds-bgserialization omits the hydrated token map. - 🟡 MEDIUM — Short/arbitrary dark CSS backgrounds are misclassified by export/renderer contrast detection.
- 🟡 MEDIUM — Newly inserted text boxes can capture a stale design-system font because the placement callback dependencies are incomplete.
- 🟡 MEDIUM — TipTap inline editing still forces white child text on the new light canvas despite the added CSS variables.
The shared background/token direction is improving, but renderer, inline editor, and standalone export still need one canonical background/contrast/token path. This remains standard risk. Focused renderer/style tests passed in agent validation; broader typecheck remains blocked by local production configuration.
🧪 Browser testing: Will run after this review (PR touches UI code).
| function standaloneBackgroundCssValue(value: string): string { | ||
| return ( | ||
| backgroundCssValue(value) ?? | ||
| STANDALONE_TAILWIND_BACKGROUNDS[value] ?? | ||
| DEFAULT_SLIDE_BACKGROUND |
There was a problem hiding this comment.
🟡 Preserve all supported named and gradient backgrounds in standalone export
The renderer retains every bg-* class, but standalone output has no Tailwind stylesheet and this resolver maps only a small dark-color allowlist. Valid values such as bg-blue-500, bg-indigo-950, and gradient utilities therefore fall back to #F5F2EA in exported HTML even though they render differently in the app. Translate the supported utility combinations to CSS or emit an equivalent stylesheet instead of silently replacing them.
Additional Info
Found independently by 2 review agents.
There was a problem hiding this comment.
Fixed in bc5c4d3. Standalone export now preserves arbitrary CSS backgrounds and translates supported Tailwind solid colors plus gradient direction/from/via/to combinations instead of falling back to the default canvas.
| const resolved = sanitized | ||
| .replace(/[{}<>;]/g, "") | ||
| .replace( | ||
| /var\(\s*(--[a-zA-Z][\w-]*)\s*\)/g, | ||
| (_, name: string) => builderTokenValues?.[name] ?? `var(${name})`, |
There was a problem hiding this comment.
🟡 Sanitize hydrated Builder token values after substitution
safeCssToken sanitizes the original string before replacing var(--token), but inserts builderTokenValues[name] without sanitizing that replacement. A hydrated value containing a semicolon, url(...), or other CSS payload can therefore re-enter the generated style attribute after the sanitizer has run. Validate/sanitize substituted values and revalidate the final resolved token.
Additional Info
Found by 1 review agent; this is a security-sensitive export path.
There was a problem hiding this comment.
Fixed in bc5c4d3. Hydrated Builder substitutions are sanitized before insertion, and the final resolved token is sanitized and rejected when an unresolved var or unsafe value remains.
| const darkBackground = isDarkStandaloneBackground(slideBackground); | ||
| return [ | ||
| `--ds-bg: ${safeCssToken(standaloneBackgroundCssValue(slideBackground), DEFAULT_SLIDE_BACKGROUND)}`, |
There was a problem hiding this comment.
🟡 Resolve hydrated Builder values for the exported slide background
The exporter passes builderTokenValues to foreground/font token serialization but not to the --ds-bg call. A design-system background such as var(--brand-canvas) is therefore treated as unresolved and replaced with the warm-neutral fallback, even when hydration supplied that token. Pass the hydrated map through the background serialization path and cover a tokenized slide background.
Additional Info
Found independently by 2 review agents.
There was a problem hiding this comment.
Fixed in bc5c4d3. The hydrated Builder token map now flows through slide background serialization, so tokenized backgrounds resolve in the standalone export.
| value === "bg-black" || | ||
| /^bg-(?:slate|gray|zinc|neutral|stone)-(?:900|950)$/i.test(value) || | ||
| (() => { | ||
| const hex = value.match(/^#([\da-f]{6})$/i)?.[1]; |
There was a problem hiding this comment.
🟡 Detect short and arbitrary dark backgrounds in export contrast selection
The renderer handles 3/4/6/8-digit hex values, but export darkness detection only matches six-digit hex and does not parse arbitrary CSS colors after bg-[...] unwrapping. A no-design-system slide with #000, #000f, or bg-[rgb(0,0,0)] exports a dark canvas with the light-canvas dark text tokens. Mirror the renderer's supported luminance parsing before choosing fallback foreground tokens.
Additional Info
Found independently by 2 review agents for the broader renderer/export mismatch.
There was a problem hiding this comment.
Fixed in bc5c4d3. Export contrast detection now handles 3/4/6/8-digit hex and rgb/rgba colors, including arbitrary CSS background values and dark gradients.
| box.style.fontFamily = | ||
| designSystem?.typography.bodyFont ?? "Inter, sans-serif"; |
There was a problem hiding this comment.
🟡 Refresh the design-system font used for new text boxes
placeTextBoxAt is memoized with a dependency list that does not include designSystem, but this new assignment reads designSystem?.typography.bodyFont. If the design system resolves asynchronously or changes after mount, the callback keeps the old value and new text boxes use Inter instead of the linked body font. Include the selected font/design-system value in the callback dependencies.
Additional Info
Found by 1 review agent; confirmed from the callback closure and new font read.
There was a problem hiding this comment.
Fixed in bc5c4d3. placeTextBoxAt now depends on the selected body font, so asynchronously resolved design systems are used for newly created text boxes.

Summary
Validation
pnpm guard:no-raw-colorsnode_modules/.bin/vitest --run app/components/deck/SlideRenderer.test.tsx app/hooks/use-deck-design-system.test.ts app/lib/normalize-slide-padding.test.ts --maxWorkers=1node_modules/.bin/vitest --run actions/create-deck.test.ts actions/add-slide.test.ts app/lib/create-deck-generation.test.ts --maxWorkers=1pnpm guards(one initial raw-color exception fixed and rechecked)Visual browser proof was unavailable because the local CUA service failed to start; the local Slides app shell did serve successfully.