Skip to content

Remove CDN dependencies: build-time Tailwind + inline SVG icons - #11

Merged
ryanbaumann merged 1 commit into
mainfrom
claude/web-app-ui-ux-review-cyobs6
Jul 16, 2026
Merged

Remove CDN dependencies: build-time Tailwind + inline SVG icons#11
ryanbaumann merged 1 commit into
mainfrom
claude/web-app-ui-ux-review-cyobs6

Conversation

@ryanbaumann

Copy link
Copy Markdown
Owner

Summary

Eliminates all runtime CDN dependencies by compiling Tailwind at build time and replacing the Material Symbols icon font with inline lucide-react SVG icons. The app now renders fully with zero blocking external requests—fonts load non-render-blocking, and the single-file bundle is completely self-contained.

Changes

  • New Icon component (src/components/Icon.tsx): Maps Material Symbols ligature names to lucide-react SVG components, sized to 1em so existing text-* classes control dimensions and color via currentColor.
  • Build-time Tailwind: Moved Tailwind config from inline <script> in app.html to tailwind.config.js + postcss.config.js. CSS is now compiled at build time and inlined by vite-plugin-singlefile.
  • Non-render-blocking fonts: Google Fonts stylesheet now loads with <link rel="preload" as="style" onload="..."> + <noscript> fallback. Slow/blocked font host no longer delays first paint or load event; text falls back to system-ui stack.
  • Removed CDN references: Deleted Material Symbols font link and Tailwind Play CDN (cdn.tailwindcss.com) from app.html. Updated CSP to remove those hosts.
  • New TrialNotice component (src/components/TrialNotice.tsx): Slim free-trial indicator showing remaining turns and one-click path to add a user key.
  • Trial status helpers (src/services/geminiService.ts): Added getTrialStatus(), hasTrialKey(), hasUserApiKey(), and TRIAL_TURN_LIMIT constant for consistent trial messaging across the app.
  • Updated all icon usage: Replaced <span className="material-symbols-outlined">...</span> with <Icon name="..." /> throughout components.
  • New src/index.css: Tailwind directives + custom utilities (e.g., .scrollbar-thin).
  • Updated docs: docs/learnings.md now documents the self-contained rendering pattern and non-blocking font loading strategy.

How Tested

  • Lint checks pass (npm run lint)
  • Unit tests pass (npm test)
  • Production build succeeds (npm run build)
  • Manual testing: Verified app renders fully with all external requests blocked (fonts, CDN); text uses system-ui fallback until fonts arrive; load event fires in ~140ms even when font host hangs

Notes

  • The single-file bundle (dist/index.html) now has zero blocking CDN dependencies. The only optional, non-blocking fetch is Google Fonts, which gracefully degrades to the system sans-serif stack.
  • Icon rendering is now instant and reliable—no font-loading race conditions or fallback to raw ligature text.
  • CSP is tighter: removed https://cdn.tailwindcss.com from both script-src and style-src.

https://claude.ai/code/session_01PNNzU5kBGqjsD4FsLpMTHD

UI/UX audit follow-through: the app previously rendered completely
unstyled whenever the Tailwind Play CDN was slow, blocked, or offline,
and the Material Symbols icon font degraded to raw ligature words. The
free-trial messaging was also contradictory. This makes the whole app
render reliably with no blocking CDN dependency and makes the trial
honest and visible.

Rendering / robustness
- Compile Tailwind at build time (tailwind.config.js + PostCSS) and
  inline it into the single-file bundle instead of the runtime Play CDN.
- Replace the Material Symbols icon font with inline lucide-react SVGs
  via a new Icon component (name -> component map, sized to 1em so
  existing text-* classes still control size/color).
- Load the Inter/Roboto web fonts non-render-blocking (preload swap +
  noscript) so a slow/blocked font host never delays first paint or the
  page load event; text falls back to system-ui.
- Tighten the CSP (drop cdn.tailwindcss.com from script-src/style-src)
  across app.html, nginx.conf, and public/_headers.
- Result: the production build renders fully with every external request
  blocked (verified offline).

Free-trial / bring-your-own-key clarity
- Centralize the limit as TRIAL_TURN_LIMIT and add getTrialStatus() /
  hasUserApiKey() helpers.
- Show remaining free turns honestly on the hero, Create, and Studio
  screens (new TrialNotice component) and in Settings.
- Fix the Settings panel claiming a key was "saved" during an active
  trial and hiding the turn counter (it now bases that on the user's own
  key, not the built-in trial key).

Simplification / mobile polish
- Remove the non-functional grayed-out "Upgrade to 2K/4K Premium"
  buttons from the Studio action row.
- Reorder the hero so the primary Get Started CTA leads (above the fold
  on mobile) with the mode shortcuts below.

Docs
- Update README (new "API Keys & the Free Trial" section), SECURITY.md,
  AGENTS.md, CHANGELOG.md, and docs/learnings.md to match.

Verified: tsc, eslint, 68 unit tests, and 42 Playwright e2e tests all
pass; screenshots reviewed across desktop/mobile in light and dark mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PNNzU5kBGqjsD4FsLpMTHD
@ryanbaumann
ryanbaumann merged commit 332c5bd into main Jul 16, 2026
2 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.

2 participants