make_post_sell/docs/design-system.md
russell@unturf.com 155f7ff66f
fix: MPS-24 Phase 2.8 — bulk tagger AJAX tag-focus + real drag-to-reorder
Operator (printableprompts.com, 481 products) reported the bulk tagger
'still refreshing the whole screen' and 'dragging tags doesn't work'
after 2.7. Two real defects the 2.7 static audit missed:

1. Tag-focus was a full-page navigation: clicking a tag chip is
   <a href=?focus=slug>, and the view loaded+rendered ALL products on
   EVERY GET. On a 481-product catalog every tag click reloaded a
   multi-MB page. The forms were AJAX; the dominant workflow was not.
2. Drag-to-reorder never existed: shop_tags.j2 shipped draggable=true +
   a handle + help text, but tag_bulk.js had ZERO drag handlers.

Fix:
- shop.py:shop_tags — all_products loads only when focus_tag or
  show_suggestions (bare GET is light). New AJAX branch: is_ajax +
  ?focus=slug -> JSON {focus, products:[{id,title,url,attached}]}.
- shop_tags.j2 — stable [data-focus-section] (always in DOM, hidden
  until focused); ?focus= chips carry data-tag-focus-link. No-JS
  unchanged (real navigation, server renders the section).
- tag_bulk.js — wireFocusLinks() intercepts chip clicks, fetchFocus()
  + renderFocus() swap the list in place, active-chip + history
  pushState/popstate, real-navigation fallback. wireDragAndDrop()
  HTML5 DnD -> persistOrder() POSTs action=set_order&tag_slugs=…
  (view already supported it) + re-syncs up/down disabled states.
  .tag-list-dragging CSS added.
- Tests: TestProductTagsSpa +4 (ajax focus json, unknown-slug null,
  set_order persists positions, bare GET no catalog). 1128 passed.

Docs: mps-24.md Phase 2.8, architecture.md, design-system.md, CLAUDE.md.
Deferred: AJAX 'Suggest categories' link (occasional click, not hot path).
2026-05-16 08:31:03 -04:00

18 KiB
Raw Blame History

MPS Design System

Overview

Make Post Sell uses a design token architecture with CSS custom properties as the single source of truth. Mobile-first. CSS Grid only (no Flexbox). Accessible. Respects reduced motion. Supports light and dark themes.

Architecture Diagram

┌─────────────────────────────────────────────────────────┐
│                    tokens.css                            │
│              Design Tokens (:root)                       │
│                                                          │
│  Colors · Typography · Spacing · Shape · Elevation       │
│  Motion · Z-index · Layout · State overlays              │
│                                                          │
│  [data-theme="dark"] overrides                           │
│                                                          │
│  Base resets · Focus · Selection                         │
│  Typography utilities (.type-*)                          │
│  Elevation utilities (.elevation-*)                      │
│  Surface utilities (.surface-*)                          │
│  State layer · Ripple effect                             │
│  Animations · Skeleton loading · Spinner                 │
│  Spacing utilities (.mt-*, .mb-*, .p-*, .gap-*)         │
│  Container utilities (.container, -narrow, -wide)        │
└──────────────────────┬──────────────────────────────────┘
                       │ consumed by
                       ▼
┌─────────────────────────────────────────────────────────┐
│                    common.css                            │
│              Component Styles                            │
│                                                          │
│  Body layout · Navigation · Forms · Buttons              │
│  Product grid · Cards · Cart · Comments                  │
│  Wells · Alerts · Ribbons · Footer                       │
│  Watch mode · Landing page · Login                       │
│  Responsive breakpoints                                  │
│                                                          │
│  References tokens via var(--token-name, fallback)       │
└──────────────────────┬──────────────────────────────────┘
                       │ rendered in
                       ▼
┌─────────────────────────────────────────────────────────┐
│                   templates/                             │
│              Jinja2 Templates                            │
│                                                          │
│  base.j2 → theme toggle, nav, footer                    │
│  styleguide.j2 → live component reference               │
│  product.j2, content.j2, shop.j2, etc.                  │
└─────────────────────────────────────────────────────────┘

File Map

File Lines Purpose
static/css/tokens.css 775 Design tokens, utilities, animations, resets
static/css/common.css 3879 Component styles consuming tokens
templates/styleguide.j2 1083 Live styleguide at /styleguide
views/misc.py:23 Styleguide view controller

Token Categories

Colors

Brand           Surface              Text                Border
──────────      ──────────           ──────────          ──────────
--color-green   --surface-base       --text-primary      --border-default
--color-blue    --surface-dim        --text-body         --border-light
--color-navy    --surface-container  --text-secondary    --border-focus
--color-purple  --surface-container- --text-muted        --border-error
--color-danger    high               --text-faint
--color-gold    --surface-inverse    --text-disabled
                                     --text-inverse

Typography Scale (Major Third 1.250)

Token          Size     Use
──────────     ──────   ──────────────────────
--text-xs      12px     Captions, overlines
--text-sm      14px     Labels, small body
--text-base    16px     Body text (root)
--text-md      18px     Large body
--text-lg      20px     Titles
--text-xl      24px     Title large
--text-2xl     30px     Headline 3
--text-3xl     36px     Headline 2
--text-4xl     48px     Headline 1
--text-5xl     60px     Display

Spacing Scale (4px base)

Token          Value
──────────     ──────
--space-0      0
--space-1      4px
--space-2      8px
--space-3      12px
--space-4      16px
--space-5      20px
--space-6      24px
--space-8      32px
--space-10     40px
--space-12     48px
--space-16     64px
--space-20     80px
--space-24     96px

Shape (Border Radius)

--radius-none   0        Sharp corners
--radius-sm     4px      Inputs, code blocks
--radius-md     8px      Cards, buttons
--radius-lg     12px     Modals, panels
--radius-xl     16px     Large surfaces
--radius-2xl    24px     Pills
--radius-full   9999px   Circles

Elevation (Box Shadow)

--elevation-0   none       Flat
--elevation-1   subtle     Cards at rest
--elevation-2   low        Raised cards
--elevation-3   medium     Dropdowns
--elevation-4   high       Modals
--elevation-5   highest    Popovers

Motion

Durations                    Easing
──────────────────           ──────────────────
--duration-instant   50ms    --ease-standard    general transitions
--duration-fast      100ms   --ease-decelerate  entrances
--duration-normal    200ms   --ease-accelerate  exits
--duration-slow      300ms   --ease-emphasize   emphasis
--duration-slower    400ms   --ease-spring      playful bounce
--duration-entrance  250ms
--duration-exit      200ms

Z-Index Scale

--z-base       0      Default stacking
--z-dropdown   100    Dropdowns, popovers
--z-sticky     200    Sticky headers
--z-overlay    300    Overlays, backdrops
--z-modal      400    Modals
--z-toast      500    Toast notifications
--z-ribbon     600    Shop ribbon banner

Layout Breakpoints

--content-narrow   400px    Login forms, narrow content
--content-width    800px    Default content width
--content-wide     1200px   Wide layouts
--bp-tablet        800px    Tablet breakpoint
--bp-desktop       1200px   Desktop breakpoint

Theme System

Light mode is default (:root). Dark mode activates via [data-theme="dark"] on the <html> element. The dark theme overrides all semantic tokens — surfaces, text, borders, brand colors — so components adapt automatically without per-component dark mode rules.

Light                           Dark
──────────────────────          ──────────────────────
--surface-base: #FFFFFF         --surface-base: #0d1117
--text-primary: #333333         --text-primary: #ffffff
--border-default: #e0e0e0      --border-default: #7ab9ff
--color-green: #a3c765          --color-green: #08e700
--color-navy: #5871ad           --color-navy: #7ab9ff

Theme toggle: window.setTheme('dark') / window.setTheme('light').

Typography Utilities

CSS classes that compose token values into complete type styles:

Class Size Weight Use
.type-display clamp(36px, 5vw, 60px) bold Hero headlines
.type-headline-1 48px bold Page titles
.type-headline-2 36px bold Section titles
.type-headline-3 30px bold Subsection titles
.type-title-lg 24px semibold Card titles
.type-title 20px semibold List titles
.type-title-sm 16px semibold Small titles
.type-body-lg 18px regular Lead paragraphs
.type-body 16px regular Body text
.type-body-sm 14px regular Secondary text
.type-label-lg 14px semibold Form labels
.type-label 12px semibold, uppercase Overline labels
.type-caption 12px regular Captions
.type-overline 11px bold, uppercase Section overlines
.type-code 0.9em mono Inline code

Component Library

All components are documented with live examples at /styleguide. The styleguide is the single source of truth for the component library. If it is not in the styleguide, it does not exist as a pattern.

Styleguide Sections

Section ID Description
Tokens #tokens Raw token reference table
Colors #colors Brand, surface, text, border, alert swatches
Typography #typography Type scale and utility classes
Elevation #elevation Shadow levels
Motion #motion Animations, transitions, easing
Spacing #spacing Spacing scale visualization
Shape #shape Border radius samples
States #states Interactive state layers
Loading #loading Skeleton and spinner patterns
Buttons #buttons Button variants (green, blue, red, navy, outline)
Forms #forms Input fields, textareas, selects, settings-form (.settings-form / .settings-form-grid / .settings-field / .settings-field-hint)
Wells #wells Content wells and containers
Alerts #alerts Success, info, warning, danger alerts
Status #status Status indicators
Product Cards #cards Product grid cards, profile card (.profile-card-header / .profile-avatar / .profile-handle / .profile-email-reveal), action button grid (.action-columns / .action-button-grid), MPS-24 tag chips (#tag-chips), MPS-24 sectioned lanes (#tag-lanes)
Cart #cart Cart and checkout components
Gift Cards #gift-cards Gift card purchase, balance check, management
Comments #comments Comment form and list
Toggle #toggle Toggle switches
Ribbon #ribbon Shop ribbon banner
Environment Banner .environment-banner Staging/dev environment indicator
Trial Banner .trial-banner Trial countdown and expiry notice
Task Bar #taskbar Task bar component
Layout #layout Grid layout patterns
Theme System #theme Theme toggle and dark mode
Footer #footer Footer component

MPS-20 / MPS-21 components

Class Where Description
.cart-negotiation-card cart.j2 Soft-green "Offer accepted / Auction won" well at the top of a negotiated cart
.cart-negotiation-deadline cart.j2 Pay-by countdown line inside the negotiation card
.cart-negotiation-pill cart.j2 "Offer accepted · quantity locked" pill on line items
.offer-pay-cta-actions offer.j2 Two-col grid: Cancel left, Pay right (stacks on narrow viewports)
.offer-pay-deadline-note offer.j2 Buyer's "Payment due in 23 hours, 14 minutes" copy
.offer-pay-link-row offer.j2 Seller's shareable offer-link with Copy button
.offer-respond-deadline-note offer.j2 "Respond in 5 days, or this offer auto-expires"
.auction-winner-pay auction.j2 "You won this auction!" well with countdown + pay CTA
.product-add-disabled-note product.j2 Caption under disabled Add To Cart when active cart is locked
.shop-offers-page user_offers.j2 + user_bids.j2 + shop_offers.j2 Wider page (max-width 1100px) overriding .one-column for the offers/bids inbox tables
.shop-offers-table inbox templates Responsive table; collapses to per-row block list below 720px
.notification-badge base.j2 + /u/settings Danger-color pill showing unread notification count
.notification-row + .notification-row-unread user_notifications.j2 Notification list rows; read rows fade to 0.65 opacity, unread rows get an alert-info background + navy left border
.notification-row-breadcrumbs user_notifications.j2 Shop Product Offer/Auction/Invoice chain under each row
.billing-page / .billing-paypal-card / .billing-actions billing.j2 /billing redesigned with Grid + tokens (no inline styles)
[data-pay-deadline] offer + auction + cart Convention attribute the shared static/js/pay-countdown.js (and inline tickers in offer.js / auction.js) rewrite once per second with a prose human delta ("in 23 hours, 14 minutes, 8 seconds") matching the server-rendered ago.human() fallback

MPS-24 components — tags, chips, lanes

Class Where Description
.tag-chip-strip home.j2, shop.j2, shop_tag.j2 Horizontally scrolling row of chips at the top of the shop home; grid-auto-flow: column + overflow-x: auto so chips swipe on mobile
.tag-chip every chip strip + bulk tagger Pill-shaped link, tokens-only colors, hover brightens border + bg
.tag-chip-active current filter chip Accent-fill chip; click on home filters the grid in place via static/js/tag_filter.js
.tag-lane home.j2, shop.j2 (layout 2) One <section> per top tag; capped product count via shop.home_layout_per_lane_limit
.tag-lane-header inside .tag-lane Grid 1fr / auto — lane title left, "See all →" right
.tag-lane-grid inside .tag-lane Reuses .serp grid pattern; horizontal lane is the outer layout, items inside still use product grid
.tag-list / .tag-list-item shop_tags.j2 (bulk tagger) Operator tag overview: tag pill + product count + view / delete row
.tag-product-list / .tag-product-row shop_tags.j2 (bulk tagger) "Apply / Applied" toggle per product per tag
.tag-detail-header shop_tag.j2 Tag detail page header (tag name + back link)
[data-tag-strip] chip strip JS hook for tag_filter.js
[data-tag-grid] flat .serp JS hook — items inside carry data-tag-slugs for in-place filter
[data-tag-slugs] .serp-item Space-separated tag slugs the item carries; consumed by tag_filter.js
.tag-suggest-list / .tag-suggest-item shop_tags.j2 (Phase 2) Suggested-cluster well rendered when operator clicks "Suggest categories from titles + descriptions"; per-cluster grid with label, sample titles, Apply / Dismiss actions
.tag-suggest-actions shop_tags.j2 Apply / Dismiss button row, grid-auto-flow: column
.product-tag-chips / .tag-chip-list product_edit.j2 (Phase 2.7) Per-product SPA tag editor container; js-only, revealed by product_tags.js. Chips wrap as inline-level pills (the <ul> is deliberately not a grid/flex parent)
.tag-chip-removable product_edit.j2, /styleguide#tagchips Same pill language as .tag-chip but an <li> with a label + an always-visible .tag-chip-removable-x remove button (no hover-only controls). inline-grid auto auto
.tag-chip-add product_edit.j2 Add-a-tag row, grid 1fr auto, stacks to one column under 600px
.tag-chip-flash product_edit.j2 Toast region reusing .tag-flash-toast / .tag-flash-{success,error,info}
[data-product-tags] / [data-product-tags-url] product_edit.j2 JS hooks for product_tags.js: container + the /p/{id}/tags endpoint URL
[data-focus-section] / [data-focus-heading] / [data-focus-list] shop_tags.j2 (Phase 2.8) Stable bulk-tagger focus container — always in the DOM, hidden until a tag is focused; tag_bulk.js swaps the product list in place instead of a full reload
[data-tag-focus-link] shop_tags.j2 (Phase 2.8) Tag chip in the All-tags list; tag_bulk.js intercepts the click and fetches ?focus=<slug> as JSON
.tag-list-dragging / .tag-list-drop-target shop_tags.j2 (Phase 2.8) Dragged row (dimmed) + active drop position during HTML5 drag-to-reorder; tag_bulk.js POSTs set_order on drop

CSS Conventions

Layout

  • CSS Grid only — never use Flexbox for layout
  • Mobile-first: base styles target mobile, @media queries enhance for larger screens
  • Primary breakpoint: max-width: 800px for mobile

Token Consumption

Components in common.css reference tokens with fallbacks:

/* Good — token with fallback for resilience */
background-color: var(--surface-base, #ffffff);
border-radius: var(--radius-sm, 4px);

/* Good — token without fallback (tokens.css always loaded) */
padding: var(--space-4);

Media Sizing

Never combine width: 100% with max-height on media elements. Use:

/* Correct */
width: auto;
max-width: 100%;
max-height: 33vh;

/* Wrong — creates dead whitespace */
width: 100%;
max-height: 33vh;

Mobile Usability

Never use hover-only interactions. All interactive elements must be always visible and tappable. Design touch-first, then optionally enhance for desktop hover.

Reduced Motion

All animations respect prefers-reduced-motion: reduce via a global media query in tokens.css that collapses durations to near-zero.

Capability-Driven Presentation

Follow Russell Ballestrini's capability-driven presentation practice. A page need not look identical across all browsers. Accommodate what the user's browser can do. Use the js-only / <noscript> pattern for progressive enhancement.

Adding New Components

  1. Define the component styles in common.css, consuming tokens from tokens.css
  2. Add a live example to /styleguide (templates/styleguide.j2)
  3. If the component participates in watch mode SPA navigation, update all three layers: template, watch.js, watch.py

Load Order

base.j2
  └─ <link> static/css/tokens.css     ← tokens + utilities + resets
  └─ <link> static/css/common.css     ← components consuming tokens
  └─ per-page <style> blocks          ← page-specific overrides