Skip to content

[UI] Harden Django Showcase UI#112

Merged
rasulkireev merged 3 commits into
masterfrom
rasul/impeccable-ui-audit
Jun 19, 2026
Merged

[UI] Harden Django Showcase UI#112
rasulkireev merged 3 commits into
masterfrom
rasul/impeccable-ui-audit

Conversation

@rasulkireev

Copy link
Copy Markdown
Collaborator

Summary

  • harden shared UI primitives, navigation/dropdown behavior, forms, and newsletter surfaces
  • extract reusable page hero, inline form, search, footer, webring, and ad component styles
  • optimize frontend controllers, Sentry loading, webpack behavior, and the bottom ad reveal lifecycle
  • polish key public templates for labels, mobile fit, touch targets, empty states, and responsive layout

Verification

  • npm run build
  • DJANGO_SETTINGS_MODULE=builtwithdjango.test_settings DJANGO_TEST_USE_DATABASE_URL=true DATABASE_URL=sqlite:////tmp/bwd-pr-check.sqlite3 .venv/bin/python manage.py check
  • git diff --check
  • responsive browser verification for bottom ad reveal/close behavior at 390x844 and 1280x900

@rasulkireev
rasulkireev marked this pull request as ready for review June 19, 2026 20:16
@rasulkireev

Copy link
Copy Markdown
Collaborator Author

@greptile review

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR is a comprehensive UI hardening pass across the Django Showcase: it extracts reusable CSS component classes (bw-ad-*, bw-search-result, bw-inline-form, bw-footer-*, bw-webring-fallback), moves page-hero markup into a reusable page-hero.html component, and improves accessibility with ARIA attributes, landmark regions, and minimum touch targets throughout templates.

  • Frontend controllers: dropdown_controller.js is extracted as a proper subclass of stimulus-dropdown with Escape-key support and aria-expanded management; html_formatter_controller.js and secret_key_controller.js both gain a secure clipboard fallback via execCommand, a status target for live feedback, and proper disabled-button state.
  • Performance & config: Sentry is now loaded lazily via dynamic import(), webpack excludes sample vendor images, a performance budget is added for production builds, and external analytics/webring scripts are gated behind app_environment == \"prod\".
  • Templates: Forms across accounts, newsletter, jobs, projects, and tools are unified under bw-form-input/bw-form-label; all logo/screenshot images gain explicit width/height and decoding=\"async\" attributes; aria-controls/aria-haspopup are added to all nav dropdowns.

Confidence Score: 5/5

Safe to merge — all changes are additive UI/CSS refactoring with no data-layer or auth-boundary modifications.

The PR replaces inline styles and hardcoded Tailwind utilities with named CSS component classes, extracts a reusable page-hero template, and gates external scripts behind an environment flag. The only substantive logic changes are in the ad reveal script and the Stimulus controllers, both of which the author verified at multiple viewport sizes. No server-side business logic, database schemas, or authentication paths are modified.

dropdown_controller.js — the hide() override leaves aria-expanded in an inconsistent state after menu-item clicks (Turbo Drive navigation resets it, but screen readers may observe the stale value during the transition).

Important Files Changed

Filename Overview
frontend/src/controllers/dropdown_controller.js New Stimulus subclass with Escape support and aria-expanded tracking; hide() override leaves aria-expanded=true when a menu item (inside .element) is clicked.
frontend/src/controllers/html_formatter_controller.js Significantly improved: adds empty-input guard, status target, disabled copy button state, and a secure clipboard fallback that now throws on execCommand failure.
frontend/src/controllers/secret_key_controller.js Refactored to async/await with a status target, disabled button state, and the same secure clipboard fallback as html_formatter_controller.js.
frontend/src/sentry.js Sentry initialization moved to an async function with dynamic import for code-splitting; early errors before the chunk loads will not be captured.
templates/components/bottom-right-corner-ad.html Ad reveal/hide logic fully refactored to use CSS classes (bw-ad--visible) instead of inline styles; separate mobile/desktop containers are kept in sync via a matchMedia listener and requestAnimationFrame.
tailwind.config.js Safelist narrowed from broad wildcards to explicit shades 50–900 for all standard Tailwind color families; JS source files added to content scan.
frontend/src/styles/tailwind.css Large additions of component utilities (bw-ad-, bw-search-result, bw-inline-form, bw-footer-, bw-webring-fallback, bw-status-error); all cleanly scoped inside the @layer components block.
templates/base.html highlight.js CDN removed (no usages remain in codebase), analytics/webring scripts gated behind app_environment, ARIA improvements added to all three nav dropdowns.
projects/views.py login_url = account_login added to InactiveProjectListView, consistent with the same attribute already on all other login-required views in the project.
templates/components/page-hero.html New reusable page-hero component supporting optional kicker, title, description, CTA, and override classes; used to deduplicate hero markup across 6 templates.
frontend/webpack/webpack.config.prod.js Production performance budget added (244 KB per asset/entrypoint); source maps and the Sentry chunk are correctly excluded from the budget filter.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Browser
    participant SentryJS as sentry.js (async)
    participant DropdownCtrl as dropdown_controller.js
    participant AdScript as bottom-right-corner-ad.html
    participant Analytics as analytics.py

    Browser->>SentryJS: Page load
    SentryJS->>SentryJS: readSentryConfig()
    alt config.enabled and config.dsn
        SentryJS->>Browser: import sentry chunk
        Browser-->>SentryJS: Sentry module
        SentryJS->>SentryJS: Sentry.init()
    end

    Browser->>DropdownCtrl: connect()
    DropdownCtrl->>DropdownCtrl: super.connect()
    DropdownCtrl->>Browser: addEventListener keydown closeOnEscape
    DropdownCtrl->>DropdownCtrl: setExpanded(false)

    Browser->>AdScript: DOMContentLoaded
    AdScript->>AdScript: hasClosedCookie() adIsClosed
    alt not past intro scroll
        Browser->>AdScript: scroll event
        AdScript->>AdScript: isPastIntro() true
        AdScript->>AdScript: showAd() syncVisibleAd()
        AdScript->>Browser: showContainer + rAF bw-ad--visible
    end
    Browser->>AdScript: close button click
    AdScript->>AdScript: hideAd() setClosedCookie()
    AdScript->>Browser: remove bw-ad--visible setTimeout 220ms add hidden

    Browser->>Analytics: HTTP request
    Analytics-->>Browser: app_environment context
    alt app_environment prod
        Browser->>Browser: Load plausible script
        Browser->>Browser: Load webring.js
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Browser
    participant SentryJS as sentry.js (async)
    participant DropdownCtrl as dropdown_controller.js
    participant AdScript as bottom-right-corner-ad.html
    participant Analytics as analytics.py

    Browser->>SentryJS: Page load
    SentryJS->>SentryJS: readSentryConfig()
    alt config.enabled and config.dsn
        SentryJS->>Browser: import sentry chunk
        Browser-->>SentryJS: Sentry module
        SentryJS->>SentryJS: Sentry.init()
    end

    Browser->>DropdownCtrl: connect()
    DropdownCtrl->>DropdownCtrl: super.connect()
    DropdownCtrl->>Browser: addEventListener keydown closeOnEscape
    DropdownCtrl->>DropdownCtrl: setExpanded(false)

    Browser->>AdScript: DOMContentLoaded
    AdScript->>AdScript: hasClosedCookie() adIsClosed
    alt not past intro scroll
        Browser->>AdScript: scroll event
        AdScript->>AdScript: isPastIntro() true
        AdScript->>AdScript: showAd() syncVisibleAd()
        AdScript->>Browser: showContainer + rAF bw-ad--visible
    end
    Browser->>AdScript: close button click
    AdScript->>AdScript: hideAd() setClosedCookie()
    AdScript->>Browser: remove bw-ad--visible setTimeout 220ms add hidden

    Browser->>Analytics: HTTP request
    Analytics-->>Browser: app_environment context
    alt app_environment prod
        Browser->>Browser: Load plausible script
        Browser->>Browser: Load webring.js
    end
Loading

Reviews (4): Last reviewed commit: "fix: guard dropdown parent disconnect" | Re-trigger Greptile

Comment thread frontend/src/controllers/dropdown_controller.js
Comment thread frontend/src/controllers/like_controller.js Outdated
Comment thread templates/components/modal-newsletter.html Outdated
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR hardens the Built with Django frontend across 38 files: it extracts reusable CSS component classes, adds ARIA attributes and Escape-key handling to navigation dropdowns, rewrites the bottom-ad lifecycle to use CSS-class transitions with proper resize and cookie-close guards, lazy-loads Sentry via dynamic import, and gates analytics/webring behind an app_environment context variable so they only fire in production.

  • UI / accessibility: nav dropdowns gain aria-controls, aria-haspopup, and Escape-key focus-return; filter dropdown's aria-expanded is corrected from a hardcoded \"true\" to \"false\"; touch targets are raised to 2.75rem; form inputs gain type=\"email\" and autocomplete attributes throughout.
  • JS controllers: secret_key_controller and html_formatter_controller are rewritten to async/await with structured error/status display and a clipboard-API-with-execCommand-fallback; search_controller replaces inline Tailwind class strings with BEM CSS class names defined in tailwind.css.
  • Build / CSS: Sentry is split into its own webpack chunk and excluded from performance-budget checks; the Tailwind safelist is narrowed from broad regexes to specific explicit classes, which reduces bundle size but carries a risk of purging dynamic color utilities used in unchanged templates.

Confidence Score: 4/5

The changes are predominantly additive UI hardening with no database migrations or auth-boundary changes; the main risk is visual regression in production from the narrowed Tailwind safelist.

All three findings are non-blocking quality issues. The narrowed Tailwind safelist is the highest-risk item — if unchanged templates contain dynamic color utility classes outside the new pattern (only -100 and -800 shades are kept), elements could silently lose styles in the production bundle. The execCommand false-return and missing super.disconnect() are confined to edge-case code paths. No auth, data, or migration changes are touched.

tailwind.config.js and any template that injects Tailwind color utilities from model data — verify the new safelist covers every shade actually used at runtime.

Important Files Changed

Filename Overview
frontend/src/controllers/dropdown_controller.js New custom Dropdown controller extending stimulus-dropdown; adds Escape-key close and aria-expanded management. disconnect() omits super.disconnect() call.
frontend/src/controllers/secret_key_controller.js Refactored to async/await with proper loading states, generateButton target, and aria-live status. copyText fallback doesn't check execCommand return value.
frontend/src/controllers/html_formatter_controller.js Hardened with empty-input guard, structured error/status display, and WCAG-friendly aria-live region. Same execCommand fallback issue as secret_key_controller.
templates/components/bottom-right-corner-ad.html Ad reveal lifecycle rewritten: replaces style manipulation with CSS class transitions, adds resize handling via matchMedia, and properly guards against double-show and cookie-closed state.
frontend/src/sentry.js Converts static Sentry import to async dynamic import for code-splitting; Sentry chunk is excluded from webpack performance budgets. Silent catch is intentional.
tailwind.config.js Safelist narrowed from broad regex patterns to specific explicit classes and a targeted color pattern; may drop dynamic utility classes used in unchanged templates.
frontend/src/styles/tailwind.css Adds BEM-style component classes for ads, search results, inline forms, footer links, webring fallback, and status/error states; moves inline style blocks from templates into the stylesheet.
templates/base.html Gates Plausible analytics and webring widget behind app_environment == 'prod', adds aria-controls to all three nav dropdowns, and improves logo image with explicit dimensions.
builtwithdjango/analytics.py Exposes ENVIRONMENT setting as app_environment template context variable, enabling prod-only feature gates in base.html.
projects/views.py Adds login_url = 'account_login' to InactiveProjectListView so unauthenticated users are sent to the allauth login page instead of the default /accounts/login/.
templates/components/page-hero.html New reusable page-hero component accepting kicker, title, description, CTA, and class override variables; replaces inline hero markup in several list pages.
templates/projects/all_projects.html Uses new page-hero component and fixes filter dropdown aria-expanded incorrectly hardcoded to 'true'.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[DOMContentLoaded] --> B{hasClosedCookie?}
    B -- yes --> Z[Do nothing]
    B -- no --> C[maybeShowAd]
    C --> D{isPastIntro scroll > 420px?}
    D -- no --> E[Add scroll listener]
    E --> F[scroll event]
    F --> G{adClosed?}
    G -- yes --> H[Remove scroll listener]
    G -- no --> D
    D -- yes --> I[showAd]
    I --> J{adIsVisible OR adIsClosed?}
    J -- yes --> Z
    J -- no --> K[adIsVisible = true, Remove scroll listener, Add resize listener]
    K --> L[syncVisibleAd]
    L --> M{desktopMedia.matches?}
    M -- yes --> N[showContainer desktop, hideContainer mobile]
    M -- no --> O[showContainer mobile, hideContainer desktop]
    N --> P[Remove hidden class, RAF: add bw-ad--visible]
    O --> P
    Q[User clicks close] --> R[hideAd]
    R --> S[adIsClosed = true, Remove listeners, setClosedCookie]
    S --> T[Remove bw-ad--visible from both containers]
    T --> U[setTimeout 220ms]
    U --> V[Add hidden to both containers]
    W[resize event] --> L
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[DOMContentLoaded] --> B{hasClosedCookie?}
    B -- yes --> Z[Do nothing]
    B -- no --> C[maybeShowAd]
    C --> D{isPastIntro scroll > 420px?}
    D -- no --> E[Add scroll listener]
    E --> F[scroll event]
    F --> G{adClosed?}
    G -- yes --> H[Remove scroll listener]
    G -- no --> D
    D -- yes --> I[showAd]
    I --> J{adIsVisible OR adIsClosed?}
    J -- yes --> Z
    J -- no --> K[adIsVisible = true, Remove scroll listener, Add resize listener]
    K --> L[syncVisibleAd]
    L --> M{desktopMedia.matches?}
    M -- yes --> N[showContainer desktop, hideContainer mobile]
    M -- no --> O[showContainer mobile, hideContainer desktop]
    N --> P[Remove hidden class, RAF: add bw-ad--visible]
    O --> P
    Q[User clicks close] --> R[hideAd]
    R --> S[adIsClosed = true, Remove listeners, setClosedCookie]
    S --> T[Remove bw-ad--visible from both containers]
    T --> U[setTimeout 220ms]
    U --> V[Add hidden to both containers]
    W[resize event] --> L
Loading

Reviews (2): Last reviewed commit: "feat: harden Django showcase UI" | Re-trigger Greptile

Comment thread frontend/src/controllers/dropdown_controller.js
Comment thread frontend/src/controllers/html_formatter_controller.js
Comment thread tailwind.config.js
@rasulkireev

Copy link
Copy Markdown
Collaborator Author

@greptile review

@rasulkireev

Copy link
Copy Markdown
Collaborator Author

@greptile review

@rasulkireev
rasulkireev merged commit f68832a into master Jun 19, 2026
3 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.

1 participant