feat: Complete Elixir School v2.0 rebrand and redesign#245
feat: Complete Elixir School v2.0 rebrand and redesign#245
Conversation
There was a problem hiding this comment.
Pull request overview
This PR completes the Elixir School v2.0 rebrand/redesign while upgrading the app to Phoenix 1.7 and modernizing the asset/tooling pipeline (Tailwind, Esbuild, LiveView patterns).
Changes:
- Upgraded Phoenix/LiveView + related deps and updated flash/dark-mode handling to newer Phoenix 1.7 patterns.
- Introduced a CSS-variable-driven design system and refreshed global layouts/components and most page templates.
- Switched lesson/blog content ingestion to a git submodule workflow and updated build/deploy scripts accordingly.
Reviewed changes
Copilot reviewed 51 out of 54 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/school_house_web/views/layout_view_test.exs | Updates dark-mode helper expectations for new nil behavior. |
| mix.lock | Locks updated dependency versions for Phoenix 1.7 + related packages. |
| mix.exs | Updates Phoenix/LiveView deps, removes deprecated Phoenix compiler, bumps esbuild wrapper. |
| lib/school_house_web/views/layout_view.ex | Changes dark-mode helper to return nil instead of empty string. |
| lib/school_house_web/templates/report/report.html.heex | Restyles report page with new spacing/typography tokens. |
| lib/school_house_web/templates/report/_section.html.heex | Restyles report tables to match new design system. |
| lib/school_house_web/templates/report/_missing.html.heex | Updates missing-row styling and semantic colors. |
| lib/school_house_web/templates/report/_lesson.html.heex | Updates lesson-row styling and semantic colors. |
| lib/school_house_web/templates/report/_coming_soon.html.heex | Updates “coming soon” row styling to new tokens. |
| lib/school_house_web/templates/post/post.html.heex | Redesigns blog post show page with new layout/typography. |
| lib/school_house_web/templates/post/index.html.heex | Redesigns blog index grid and pagination UI. |
| lib/school_house_web/templates/post/_post_preview.html.heex | Updates blog preview cards to new design system components. |
| lib/school_house_web/templates/page/why.html.heex | Major redesign of Why page into card/grid-based sections. |
| lib/school_house_web/templates/page/podcasts.html.heex | Redesigns podcasts page into card grid layout. |
| lib/school_house_web/templates/page/index.html.heex | Full homepage redesign (hero, stats, features, CTA). |
| lib/school_house_web/templates/page/get_involved.html.heex | Redesigns contribution page into card-based sections. |
| lib/school_house_web/templates/lesson/lesson.html.heex | Redesigns lesson page layout (title, excerpt, TOC, body, CTA). |
| lib/school_house_web/templates/lesson/index.html.heex | Redesigns lesson index list into styled cards. |
| lib/school_house_web/templates/lesson/_testing.html.heex | Restyles lesson section header (Testing). |
| lib/school_house_web/templates/lesson/_storage.html.heex | Restyles lesson section header (Storage). |
| lib/school_house_web/templates/lesson/_section_header.html.heex | Updates in-lesson heading/link styling and scroll offset. |
| lib/school_house_web/templates/lesson/_pagination.html.heex | Simplifies/restyles lesson pagination component. |
| lib/school_house_web/templates/lesson/_misc.html.heex | Restyles lesson section header (Misc). |
| lib/school_house_web/templates/lesson/_intermediate.html.heex | Restyles lesson section header (Intermediate). |
| lib/school_house_web/templates/lesson/_ecto.html.heex | Restyles lesson section header (Ecto). |
| lib/school_house_web/templates/lesson/_data_processing.html.heex | Restyles lesson section header (Data Processing). |
| lib/school_house_web/templates/lesson/_basics.html.heex | Restyles lesson section header (Basics). |
| lib/school_house_web/templates/lesson/_advanced.html.heex | Restyles lesson section header (Advanced). |
| lib/school_house_web/templates/layout/root.html.heex | Switches dark-mode to data-theme, updates title helper, adds fonts, adjusts layout padding. |
| lib/school_house_web/templates/layout/live.html.heex | Updates LiveView flash rendering to Phoenix.Flash API + new styling. |
| lib/school_house_web/templates/layout/app.html.heex | Updates controller layout flash rendering to Phoenix.Flash API + new styling. |
| lib/school_house_web/templates/layout/_locale_menu.html.heex | Redesigns locale selector UI using new tokens. |
| lib/school_house_web/templates/layout/_lesson_menu.html.heex | Replaces mega-menu checkbox toggle with Alpine.js dropdown. |
| lib/school_house_web/templates/layout/_header.html.heex | Replaces header/nav with fixed frosted-glass navbar + mobile menu. |
| lib/school_house_web/templates/layout/_footer.html.heex | Rebuilds footer into 4-column design system layout. |
| lib/school_house_web/templates/layout/_dark_mode_toggle.html.heex | Replaces slider toggle with icon button toggle. |
| lib/school_house_web/live/conferences_live.html.heex | Restyles conferences page + modernizes filter form structure. |
| lib/school_house_web.ex | Switches HTML helpers import to Phoenix.Component for newer patterns. |
| content | Adds git submodule pointer for lessons/blog content checkout. |
| config/config.exs | Bumps configured esbuild binary version. |
| assets/tailwind.config.js | Adds design tokens + selector-based dark mode + typography updates. |
| assets/package.json | Updates Tailwind/PostCSS/Alpine/topbar versions for new build pipeline. |
| assets/js/menu-toggle.js | Updates mobile menu toggle behavior for new header DOM. |
| assets/js/initialize-theme.js | Updates initial dark-mode detection to use data-theme. |
| assets/js/dark-mode.js | Updates theme toggle implementation to use data-theme. |
| assets/js/app.js | Removes pickup import and wires new JS modules. |
| assets/css/app.css | Introduces CSS custom properties design system + updated syntax highlighting and transitions. |
| README.md | Updates project description and adds submodule-based setup/docs. |
| Makefile | Switches content acquisition from git clone to submodule update + image copy. |
| Dockerfile | Copies submodule content into build image and extracts images. |
| .gitmodules | Adds content submodule configuration. |
| .gitignore | Stops ignoring /content so submodule can be committed. |
| .github/workflows/deploy.yml | Ensures deploy workflow checks out git submodules. |
Comments suppressed due to low confidence (3)
test/school_house_web/views/layout_view_test.exs:1
- In tests, prefer
assert is_nil(...)(or pattern matching) over comparing with== nil. It reads more idiomatically and produces clearer failures.
lib/school_house_web/templates/page/podcasts.html.heex:1 - Links opened with
target=\"_blank\"should includerel=\"noopener noreferrer\"to prevent reverse-tabnabbing and to avoid leakingwindow.opener. Add therelattribute here (and any other external_blanklinks in this template).
lib/school_house_web/templates/page/why.html.heex:1 - This external link uses
target=\"_blank\"withoutrel=\"noopener noreferrer\", which is a security risk (reverse-tabnabbing). Addrel=\"noopener noreferrer\"here (and apply the same fix to other_blanklinks in this page).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1c142f3 to
14188fc
Compare
|
Congrats on this major rework! |
|
Is this branch live on some staging site? I'd like to review it from the accessibility point of view if you're interested :) If it's not I can still review it on my local dev machine. |
|
Hey @jaimeiniesta! I was just thinking about you as I was doing a little more on this revamp tonight. I don't have this deployed yet but let me see what it would take to get it up somewhere. |
4dfcaad to
6116700
Compare
Upgrade to Phoenix 1.7, LiveView 0.20, phoenix_html 4.0, Tailwind 3.4, and esbuild 0.8. Introduce a new indigo-purple design system with CSS custom properties for light/dark themes, semantic color tokens (surface, brand, accent), and Google Fonts (Outfit, DM Sans, JetBrains Mono). Redesign all templates: frosted-glass nav with Alpine.js dropdowns, hero section with code window, card-based layouts for lessons/blog/ secondary pages, gradient accent bars, hover effects, and responsive grid layouts throughout.
Replace the `make content` clone-and-delete workflow with a git submodule pointing at elixirschool/elixirschool. This pins content to a specific commit for reproducible builds and faster local iteration. Update Makefile, Dockerfile, and CI workflow accordingly.
Rewrite the README to document the git submodule setup for content, including clone with --recursive, submodule initialization, content update workflow, prerequisites, and Docker build instructions.
Remove extra `&` entity before the arity in the decorative code example, changing rendered output from `&hello/&1` to `&hello/1`.
- Align README prerequisites with mix.exs elixir version constraint - Bind aria-expanded to Alpine.js state in locale menu for accessibility - Add aria-controls to locale menu button linking to dropdown - Use idiomatic assert is_nil() in layout view test - Add rel="noopener noreferrer" to all external target="_blank" links across podcasts, get_involved, why, conferences, and lesson templates
- Delete unused pickup.js (dead code since Phase 2) - Remove legacy color definitions from Tailwind config (brand-purple-*, brand-gray-*, brand-red-*, and semantic backgroundColor/textColor mappings) - Remove unused tailwindcss/colors import - Restyle error pages and newsletter partial to new design system - Update html_helpers.ex lesson_link and coming_soon_badge classes - Re-extract gettext strings across all 24 locale PO files
The section header template was updated with new design system classes (font-heading, text-on-surface, hover:text-brand-500, etc.) but the test expectation still had the old classes (flex, pt-1 ml-2, w-6 h-6).
- Update mix.exs elixir constraint from ~> 1.13 to ~> 1.18 - Update CI workflow from Elixir 1.14.1/OTP 25 to 1.18.4/OTP 27 - Update README prerequisites to match - Add mise.toml alongside existing .tool-versions
Move site brand assets (logo, icon, favicons) from the content image pipeline to assets/static/brand/, served directly by Phoenix. Content images are now exclusively for lessons and blog posts.
Fill 19 empty msgstr entries for new UI strings from the redesign. Preserves all existing translations by native speakers.
Fill 19 empty msgstr entries for new UI strings from the redesign. Preserves all existing translations by native speakers.
Fill 25 empty msgstr entries including new redesign UI strings. Preserves all existing translations by native speakers.
Fill 25 empty msgstr entries including new redesign UI strings. Preserves all existing translations by native speakers.
Fill 29 empty msgstr entries including new redesign UI strings. Preserves all existing translations by native speakers.
Fill 207 empty msgstr entries with German translations covering all UI strings, navigation, lesson titles, and feature descriptions.
Fill 207 empty msgstr entries with French translations covering all UI strings, navigation, lesson titles, and feature descriptions.
Fill 207 empty msgstr entries with Portuguese translations covering all UI strings, navigation, lesson titles, and feature descriptions.
Fill 207 empty msgstr entries with Italian translations covering all UI strings, navigation, lesson titles, and feature descriptions.
Update esbuild target from es2016 to es2017 to preserve async/await. Alpine.js v3 uses async function evaluation internally and calls .catch() on the result. With es2016, esbuild transpiled async functions into generator-based code that doesn't return a Promise, silently breaking all Alpine expression evaluation (x-data, x-show). Fix lesson_link default class from block to inline and outer wrapper from flex to contents so numbered list markers in the lesson dropdown render inline with lesson names instead of on separate lines.
Add a dedicated /ecosystem page highlighting key Elixir ecosystem projects: Phoenix & LiveView, Livebook, Nx & Bumblebee, Nerves, Ecto, Oban, Broadway, Ash Framework, and Hologram. Include an ecosystem highlights section on the homepage with a CTA linking to the full page. Update header, footer, and homepage navigation accordingly.
Redesign the lesson page from a single-column layout to a 3-column grid with left sidebar (lesson navigation), main content area, and right sidebar (table of contents + action links). - Add filtered_lessons/0 to Lessons module for section data access - Update LessonController to pass all_sections for sidebar rendering - Create _lesson_sidebar.html.heex with section groups and active state - Create _toc_sidebar.html.heex with TOC links and GitHub action buttons - Add breadcrumb navigation to lesson content area - Integrate prev/next navigation inline in content area - Add responsive CSS: TOC hidden at 1100px, sidebar hidden at 768px - Both sidebars use sticky positioning aligned with 64px navbar
- Wrap lesson layout CSS in @layer components for proper Tailwind integration and CSS ordering - Add min-width: 0 and overflow-x: auto to .lesson-content to prevent code blocks from expanding the grid column beyond its bounds - Replace en-dash characters in CSS comments with ASCII dashes
Instead of showing all 8 sections with every lesson, the left sidebar now displays only lessons within the current section for a cleaner, more focused navigation experience.
The lesson markdown files contain {{ site.erlang.OTP }},
{{ site.erlang.erts }}, and {{ site.elixir.version }} placeholders
from the original Jekyll site that were rendering as raw text.
These are now replaced at compile time with actual BEAM version
values so they stay current automatically.
Track completed lessons and last-visited position using an Alpine.js store backed by localStorage. Lessons auto-complete when the user scrolls to the bottom (via IntersectionObserver with a 2s guard for short lessons). The sidebar shows green checkmarks for completed lessons and a section counter (e.g. "3/15"). A "Continue Learning" card on the homepage links to the last visited lesson.
- Add Sponsors GenServer that fetches sponsor data from GitHub GraphQL API - Cache sponsor data in ETS with 15-minute TTL for fast reads - Create dedicated /sponsors page with tiers, sponsor grid, and CTA - Add sponsors bar on homepage (shown only when featured sponsors exist) - Add "Sponsor the Project" card to Get Involved page - Add sponsor link with heart icon to footer Community column - Extract new gettext strings across all locales
- Remove accidental match on ETS table init (was `@table = :ets.new(...)`) - Guard against empty string GITHUB_TOKEN in addition to nil - Add Logger.warning on all API failure paths for prod observability - Add smoke test for the /sponsors route
- Format sponsors.ex (long lines in default_tiers) - Fix Credo single-function pipeline warning in featured/0 - Update picomatch and yaml to fix high/moderate vulnerabilities - Add minimatch override (^3.1.4) to resolve remaining 2 high alerts - npm audit now reports 0 vulnerabilities
Address 19 accessibility issues across the redesigned site: - Add skip navigation link and main landmark to root layout - Fix muted text contrast ratios in both light and dark themes - Add aria-expanded, aria-controls, and keyboard support to menus - Refactor dark mode toggle to use aria-pressed (remove redundant checkbox) - Add aria-hidden to ~42 decorative SVGs across all pages - Add "opens in new tab" screen reader text to external links - Add landmark labels to lesson sidebars and homepage sections - Add breadcrumb aria-label and aria-current to lesson navigation - Add role="status" live region to lesson completion toast - Add role="menuitem" to locale dropdown links - Add mobile lesson navigation drawer for sidebar access on small screens - Mark hero code blocks as role="img" with descriptive labels - Add global focus-visible outline styles - Add prefers-reduced-motion media query - Fix podcast card redundant link structure - Add newsletter form validation aria-describedby - Update footer copyright year to be dynamic
Fill ~62 empty msgstr entries for Norwegian and Spanish including ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~75 empty msgstr entries for Korean and Greek including ecosystem tool descriptions, sponsor section, and previously untranslated UI strings.
Fill ~65 empty msgstr entries for Japanese including ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for German and French including ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for Portuguese and Italian including ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for Russian and Polish including ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~64 empty msgstr entries each for Arabic, Turkish, and Indonesian including ecosystem tool descriptions, sponsor section, and code showcase strings.
Fill ~35 remaining empty msgstr entries for Bulgarian including sponsor section strings and a few ecosystem descriptions.
Translate all 270 strings for Ukrainian locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Vietnamese locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Slovak locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Bengali locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Persian/Farsi locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Malay locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Tamil locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
Translate all 270 strings for Thai locale which was previously untranslated (0% complete). Covers lessons, UI, ecosystem, and sponsor sections.
…uality
- Validate locale against whitelist before dynamic atom creation in HtmlHelpers
- Use GraphQL variables instead of string interpolation in Sponsors
- Add request timeout (15s) to GitHub API calls in Sponsors
- Change ETS table from :public to :protected in Sponsors
- Guard against nil section in populate_surrounding_lessons
- Wrap String.to_existing_atom in try/rescue in Lessons.list/2
- Standardize error tuples to 2-tuple {:error, {:translation_not_found, locale}}
- Validate locale param in PageController.report/2
- URI-encode dynamic params in GitHub issue URLs
- Add error message for malformed version strings in Lesson
- Consolidate duplicate .sidebar-section-title CSS rules
- Scope dark mode transitions to specific elements instead of body *
- Use window load event instead of setTimeout for preload removal
- Extract TOAST_DURATION constant in progress.js
ca0c136 to
074aaf1
Compare
Summary
Complete redesign and rebrand of Elixir School with Phoenix 1.7 upgrade, new design system, and comprehensive template updates across all pages.
Changes
Phase 0: Phoenix 1.7 Migration & Dependency Upgrades
:phoenixcompilerPhase 1: Design System Foundation
.darkclass todata-theme="dark"attributePhase 2: Global Components (Nav, Footer, Layout)
Phases 3-6: Page Redesigns
Technical Details
Design System
Components
Testing
Deployment Notes
Files Changed
🚀 Ready for review and merge