make_post_sell/docs/design-system.md
russell@unturf.com 5dbbe697b6
feat: MPS-24 Phase 2 — auto-suggest tags from title + description
Operator with 481 untagged products (printableprompts.com) gets a
one-click path to a usable categorization without hand-tagging each
product. Strictly suggest-then-approve — nothing writes Tag or
ProductTag rows until the operator clicks Apply on a cluster.

- lib/tag_suggest.py: pure-function clusterer. Tokenize title (weight 3)
  + description (weight 1, capped at 100 unique tokens per product),
  strip markdown / URLs / HTML, English + per-shop stopwords, simple
  suffix-strip stemmer, group by stem, drop stems matching existing
  tag slugs, rank by product count, label each cluster with the most
  frequent original word for its stem. No new deps, no ML.
- scripts/backfill_tags.py: CLI preview + --apply for a single shop.
- views/shop.py: shop_tags gains action=apply_suggestion (creates tag +
  bulk-attaches every product in cluster) and action=dismiss_suggestion
  (adds the cluster's words to shop.tag_stopwords_json so it never
  resurfaces). ?show_suggestions=1 triggers the cluster compute.
- templates/shop_tags.j2: "Suggest categories from titles + descriptions"
  button + suggestions well with per-cluster sample titles, Apply, and
  Dismiss buttons.
- 15 new tests (11 unit over tokenize / stem / cluster + 4 functional
  over the suggest/apply/dismiss flow). 1064 total passing.

On a printableprompts-style sample the clusterer surfaces Math, Reading,
Literacy, Seasonal, Novel, Activities, Comprehension — matching what an
operator would manually pick.
2026-05-15 09:09:40 -04:00

17 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

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