Canvas Design System
Main Site Tokens

Changelog

A log of additions, improvements, and fixes to the Canvas Design System.

August 2026
v2.16.0: the canvas directory gets a recommender and a pager
  • New .cv-find (css/canvas/styles.css): the Canvas Directory's guided recommender, a conversation panel sitting above the grid. The block covers the head and its Ask toggle, the thread's two bubble sides (.cv-find__msg--agent on white behind a var(--gray-200) edge, .cv-find__msg--user right-aligned on var(--phase-learn-light)), the starter and answer chips, a three-dot .cv-find__typing indicator that holds still under prefers-reduced-motion, and the result group: a .cv-find__best label, an italic .cv-find__why line, and the card capped at 420px so a recommendation reads as one answer rather than a second grid.
  • New .cv-pager: the index paginates at 15 canvases a page, so it needed a pager. Every control is an existing .cv-chip rather than a new numeral button; the current page is that chip's .is-active state. The one addition is .cv-chip--disabled, 0.45 opacity with pointer-events: none, for the Previous and Next ends, which the page renders as <span> so a reader never tabs to a link that goes nowhere.
  • Improved .cv-note a now carries var(--phase-learn-dark) and var(--font-semibold). The attribution footnote is var(--gray-100) under var(--text-muted) body text, and its two links are the whole way a reader reports a wrong credit or suggests a canvas. At the block's own weight they read as prose. Weighted, they read as the actions they are.
  • Improved The canvas of the day and the find panel take a var(--phase-learn-dark) border. Both sit on the index above a grid whose cards are edged in var(--gray-200), and a light edge left them reading as two more cards in it. The dark red says they are the page's two offers instead. .cv-today__card:hover keeps the same border colour and moves only the 1px lift, so the edge never announces itself twice.
  • Fixed The directory's stylesheet moved from css/canvases/ to css/canvas/. The pages now live at /canvas/, and the sheet's path was the last thing on the platform still naming the old route.
August 2026
v2.15.0: the hero folds, and the card menu ships once
  • New .app-hero--compact (app/css/app-shell.css): the section hero for a reader who is already using the product. The full hero is a pitch: pill, two display lines and a lede, about 280px of it, which on 1440x900 put the reader's own list below the fold on every visit to Plan, Facilitate, Synthesize and Prove. The modifier drops the lede and the red second line, takes the headline to var(--text-display) on one line, and closes the gap below to var(--space-5). The 48px above is deliberately untouched: .app-rail opens level with the hero at that same value, and moving one without the other steps the two columns apart.
  • New chips option on ws_workshop_list(): whether to draw the stage filter chips, default true, with filters kept as its older name rather than added as a second switch that could disagree. With the chips off, filter stops being a control and becomes the listing's subject: the rows are cut to that stage server-side, so the heading's count, the pager, the empty state and what the reader can see all describe one set. The footer option gained a variant key, for the plain text link the phase pages close their list with.
  • Improved The card actions menu is one <template> per listing, not a popover per card. Every row used to ship its whole panel: nine or so links and buttons, in each of the views, none reachable until a trigger is pressed, which came to 421 links and buttons in a home page showing fourteen workshops. ws_wl_menu() now emits the trigger and the row's facts as data attributes (data-wl-owned, data-wl-collab, data-wl-delivered, data-wl-deck, data-wl-stage); ws_wl_menu_template() emits the superset once with {id} where the plan id goes, and workshop-list-menu.js clones it on open, drops what the row does not have, and throws the clone away on close. Same document at 148. Every entry, the keyboard traversal, the in-panel confirmation and the focus return are unchanged.
  • New meta_extra on a listing row: extra bits for the card's meta line, after the facts every listing states. Prove is the first caller: it says when the room happened and what it captured. Also counts, carried through from the Workshop Record so a page can build its own line without a second query.
  • New badge on a ws_task_checklist() row (includes/components/task-checklist.php): a short count rendered as a quiet ws_badge pill after the label, for a step whose name cannot say how much of it is left ("Run the session · 2 ready"). A done row carrying a badge stays actionable rather than going inert, because a completed step with work still queued behind it is somewhere the reader needs to be able to go.
  • New short on a ws_sidenav() item (includes/components/sidenav.php): a second, shorter name for widths the full one cannot survive. Both ship in the DOM, .ws-sidenav__label--short is hidden by default and the layout picks which one shows, and the item takes the full label as its aria-label so the accessible name never shortens with the screen. The app shell's bottom bar is the first caller: below 640px Strategize, Facilitate and Synthesize were all running off the end into an ellipsis, and now read Strategy, Run and Synth.
  • Fixed About 60 lines of dead stylesheet removed. .prvh-grid, .prvh-card and their two media queries were the last hand-rolled workshop card on the platform; Prove renders ws_workshop_list() now, so they went, and the card-border rule in app-shell.css stopped naming a selector nothing draws.
August 2026
v2.14.12: the home hero's primary button answers the pointer
  • Fixed .dash__hero-btn.ws-btn--primary now has its own hover and focus state. Rest, :hover and :focus-visible shared one declaration in app/css/dashboard.css, so the one emphatic button on the app home page never moved under the cursor or the keyboard. Hover and focus take var(--phase-learn-very-light) as the fill and keep var(--bg-surface) as the border, so the edge stays put while the face darkens.
August 2026
v2.14.11: the frame bar offers the real app window
  • New The app footer's full bar carries an "Install the app" action. Rendered hidden and revealed only by Chrome's beforeinstallprompt — the installed PWA is the one window a browser draws without a URL row, which no popup feature string is permitted to do. The click spends the event's one-shot prompt(); either outcome retires the button for the visit, appinstalled retires it for good, and browsers that never fire the event never show it. Wired in the template's own script (registered at parse, since Chrome can fire before DOMContentLoaded); the control reuses .ws-app-footer__link, so no new CSS.
August 2026
v2.14.10: the featured row leads with the front door
  • Improved The app shelf's featured row now points at /start/. ws_app_shelf_featured() sends the Workshopr App row through the app's landing page, which pitches the app and signs the visitor in on one screen, then forwards an already-signed-in reader straight to /app/. No site surface lands anyone on the shell's bare sign-in wall any more.
August 2026
v2.14.9: the app shelf grows a featured row
  • Improved The app shelf's consolidated-app row moved above the phases. ws_app_shelf_featured() returns the Workshopr App as its own row, and the logotype dropdown (.wsh-panel__app--featured), the homepage shelf (.tue-shelf__app--featured) and the features shelf (.ft-shelf__app--featured) all render it full-width above the phase groups. Strategist and Planner left the STRATEGIZE/PLAN group on the same reasoning that removed Facilitator: they are views inside the app the featured row already covers.
August 2026
v2.14.8: the password field's eye is anchored, and stops seeing double
  • Fixed ws_input's password reveal. Two rules the component always needed lived only in one app's stylesheet. .ws-password-wrapper had no position, so the absolutely-positioned toggle anchored to whatever ancestor happened to be positioned: on the app shell's new sign-in screen it landed at the far right edge of the window, a pair of eyes floating in the paper. And the inline handler swaps the two eyes by toggling a hidden class that nothing defined, so both drew at once on every password field on the platform. Both rules now ship with the component, scoped to the toggle rather than claiming a global .hidden utility.
August 2026
v2.14.7: ws_choice_cards counts columns from its container
  • Fixed ws_choice_cards is no longer laid out against the window. The set was repeat(3, 1fr) with breakpoints at 1100px and 640px, which is a measurement of the browser, not of the grid: inside the app shell the same component sits in a column that gives up 320px to the rail and paints at zoom: 0.8, so at a 1160px window each card was about 200px wide and its time label collided with its button. The track floor is now max(240px, (100% - 2 * gap) / 3) with auto-fill, which reads the container: three columns while there is room for three, then two, then one, and never more than three however wide the screen gets. The featured card still takes the full row once the set folds, now via @container (max-width: 760px) rather than a window breakpoint. --2 and --1 keep their meaning as caps.
August 2026
v2.14.6: ws_app_bar's icon actions get their tooltips
  • Fixed Icon-only actions in ws_app_bar now carry a tooltip. The component's docblock has always claimed tooltip behaviour, but an icon action shipped with aria-label alone: named for assistive technology, silent to a pointer. Each one now also gets data-tooltip from the same label plus data-tooltip-position="bottom", since the bar sits at the top of the window and the default top placement would open off-screen. A caller passing its own data-tooltip in attrs keeps it, and labelled actions are untouched (their text is already the name). No CSS was needed: the bar was already overflow: visible and the [data-tooltip] rules were already there.
August 2026
v2.14.5: A lighter gear
  • New icon('settings_light'), the weight-300 cut of the Material Symbols gear at images/icons/settings_light.svg. The stock weight-400 settings glyph carries about a third more ink than the outline icons it sits beside (measured 10.1% coverage against home's 7.5%), so on a red app bar it read as bold; the 300 cut lands at 7.3%. First used by the planner's Plan settings button; reach for it wherever a gear sits in a row of outline icons.
August 2026
v2.14.4: ws_task_checklist stops crossing things out
  • Improved Done rows dim instead of striking through. .ws-task-checklist__item.is-done .ws-task-checklist__label drops text-decoration: line-through and keeps the --text-muted shift; the check is already the state, and a stack of struck labels reads as ruled lines between the rows rather than as finished work. The complete state's text-decoration: none reset went with it, being redundant now.
  • Improved The card's action right-aligns. .ws-task-checklist__cta becomes a flex row justified to flex-end, so the button lands under the rows' arrows at the card's trailing edge. Consumers that ship no cta (the app home's first-run card, whose action is its own element) are unaffected.
  • Improved The rail's Getting started card halved in height (app/includes/rail.php): no subtitle, no per-step description or phase, and progress => false. Every one of those was already optional on the component, so this is a caller change, not a component one.
August 2026
v2.14.3: The Intervention Cards rail card shows the deck, not a diagram of it
  • Improved .app-rail__cards-art carries two cards instead of five. The fanned-five artwork was recognisable as a deck but rendered each card at roughly 55px wide, too small for the livery or the scene to read, so the card advertised "cards" in the abstract. It now carries a composed pair (images/intervention/cta-cards-pair.png): the hero square-on with a second angled behind it, built from the real fronts in images/intervention/cards/ rather than a Nano Banana imitation, with shadows derived from each card's own alpha so the rounded corners cast correctly. This is the move the DNA rail card already makes, where the medallion is the pitch rather than an icon standing in for one. The sizing rule goes 200x150 fixed to 216px wide with height: auto: the pair is portrait, and a fixed height would squash the angled card. Quantised to 38KB from 139KB, visually identical at display size.
August 2026
v2.14.2: The DNA badge label sits on one line
  • Improved .ws-dna-profile-cta__text goes 14px/17px to 16px/20px semibold on a single line. "Your DNA Profile" and "Discover your DNA Profile" lose their <br> and gain white-space: nowrap; tracking eases from 0.02em to 0.01em so the earned pill's 156px text box still holds the phrase beside the medallion. Both states keep their 59px pill; the line simply centres in it. Sizes stay in px for the reason the block documents.
August 2026
v2.14.1: The DNA medallion loses its plate, its label gains a step, and the feedback modal widens
  • Fixed The ws_dna_profile_cta medallions are transparent. The five images/dna/profile/medallion-*.png exports carried an opaque #F5F5F5 plate, which painted a pale box over the tinted pill the roundel is supposed to overlap. The plate is knocked out by a border-seeded contiguous flood fill rather than a global colour replace, because the artwork holds near-white of its own (the shirt, the scroll, the roundel's inner field) that a global replace would punch through. The anti-aliased rim is unmixed against the plate colour, so no grey halo survives on the tint. No CSS or markup change: the component's filemtime cache-bust carries the new files.
  • Improved .ws-dna-profile-cta__text goes 12px/15px to 14px/17px. At the 80% root the frames' 12px read small beside the medallion. Both states keep their geometry: two lines still clear the pill's 59px box, and the start card is unchanged at 59px. Sizes stay in px for the reason the block already documents: --text-* would paint this at 0.8x the size the wireframe frames are drawn at.
  • Improved The app footer's Send feedback modal moves from sm to default (360px to 500px), giving the message textarea a usable measure. A size step the modal component already ships, not a one-off width. The v2.14.0 entry below describes the original sm.
August 2026
v2.14.0: The app footer's three actions grow up
  • Improved The full app footer's right side reworked. Send feedback is now a button opening a ws_modal (size sm, branded) whose fields are ws_select and ws_input (textarea type included), footed by ws_modal_footer; it posts JSON to the existing api/contact.php with client-side floors mirroring the endpoint's own. Resources and Company open small link panels anchored above their buttons (disclosure pattern: aria-haspopup/aria-expanded/aria-controls, menu roles inside, no aria-modal), carrying the marketing footer's four Resources and six Company links. Panels close on Escape (focus returns to the trigger), on outside click, and when the other opens; arrow keys, Home and End walk the items. All panel chrome is tokens; the explicit .ws-app-footer__panel[hidden] rule keeps the display gotcha closed. The launcher lost its footer entry point: the rail's More button is its one way in, and data-app-footer-launcher plus the launcher option are gone.
August 2026
v2.13.0: The DNA Profile CTA takes the wireframes' badge design, and [hidden] learns to win
  • Improved ws_dna_profile_cta() rebuilt from the app wireframes' badge frames (file kRqtDXi210tzoXK5uoHQLz: one frame per archetype plus the default). Earned: the archetype's illustrated medallion, its name lettered on the ribbon, overlapping a tinted "Your DNA Profile" pill; the aria-label and the medallion's alt carry the name in text. Start: the pill alone with the DNA glyph and "Discover your DNA Profile". All five medallions exported at 2x to images/dna/profile/medallion-<slug>.png; a slug without its medallion on disk falls back to the start card. Card chrome is all tokens: --phase-learn-very-light fill, --phase-learn-dark border and type, --radius-lg. New demo page at design-system/components/demos/dna-profile-cta.php.
  • Fixed The workshop listing's [hidden] attribute now always wins. .wl-card's display: flex (and its kanban, timeline and pager siblings) beat the user-agent's [hidden] { display: none } (the exact trap the bulk bar's own comment documents), so a filtered-out card stayed fully painted while the filtered-empty note counted it as gone. The chips have carried this since they shipped; the per-phase default filters made it visible on every phase home. One rule in workshop-list.css closes it for every element the filter and the pager govern.
August 2026
v2.12.1: The listing footer link becomes a real button
  • Improved The footer link on ws_workshop_list() now renders through ws_button() as a small secondary variant with a right-hand arrow_forward icon, replacing the hand-rolled red text link and its literal arrow glyph. The listing's exit is an action, and it inherits the button's phase-colour hover and focus behaviour instead of restating them. .wl__all survives as a layout hook only; the colour, weight and size rules it carried are gone, so the footer no longer redefines what a secondary button already looks like.
August 2026
v2.12.0: The workshop listing's chips learn a default, and the pager learns the chips
  • New filter option on ws_workshop_list() — which chip opens pressed ('all' or a stage key, default 'all'), with first-paint row visibility matching it server-side. A value outside the listing's own stages scope falls back to 'all' rather than opening on a chip that isn't rendered. The four phase homes each open on their own stage; Home opens on All.
  • Improved Paging composes with the chips instead of refusing them. The old guard existed because two scripts writing hidden on the same rows is how a card goes missing; the replacement is single ownership: on a paged listing, workshop-list-pages.js is the sole writer of row visibility on grid and list, and the chips (workshop-list-views.js) report the active stage to it. The window runs over the rows the active chip leaves visible, a chip change re-pages from page one, and a chip with pageSize rows or fewer shows no pager. Kanban columns and the timeline stay with views.js: whole-collection layouts, never paged.
  • Fixed A stale stored view preference no longer blanks the listing. setView() hid every panel before discovering the requested one didn't exist — exactly what a remembered kanban would do on a listing that stopped offering it. It now checks first and leaves the current view standing.
  • Improved The pattern page (design-system/app-workshop-list.php) documents both options and demos the chips and pager working together: fourteen static rows, six to a page, opening on the Plan chip. Still renders signed out.
August 2026
v2.11.2: ws_choice_card loses its tint
  • Improved .ws-choice-card (includes/components/components.css): the linear-gradient from --phase-learn-very-light down to --card-bg is gone; the card now paints a flat var(--card-bg) like every other card on the platform. The tint was the component's only deviation from the plain card surface, and it read as a wash across the top of each card rather than as emphasis. Featured cards still separate themselves by border colour and the filled badge, which is where the emphasis belonged all along. Applies everywhere the component is used: Plan's home, the planner home, and the builder's empty canvas.
  • Improved .lp2 .launchpad-choice-screen__header (app/css/app-shell.css): the header now sets margin-bottom: var(--space-6), replacing an inline style="margin-bottom:0" in app/views/plan/home.php that left the choice cards sitting directly under the subtitle. The standalone planner home keeps --space-10 there, which is a page-opening gap rather than a heading-to-cards one. Note the shell renders under zoom: 0.8, so 24px of token space is 19.2 physical pixels; author against the tokens, not the ruler.
August 2026
v2.11.2: The Agenda Check dialog joins the paper, and loses a one-off chip
  • Improved #planCheckModal-overlay .ws-modal__body paints the drafting paper (app/css/app-shell.css): --bg-paper plus --paper-grid-image, so the dialog stands on the same ground as the region it opens over rather than a white sheet floating above it. The composer and the report cards already paint their own --bg-surface, so they still read as cards on paper. Written longhand for the reason platform-tokens.css gives at the token: the background: shorthand wipes background-image, which is how five pages have silently lost the grid before. Scoped to this dialog, not to .ws-modal__body at large: the other Plan dialogs are forms, and a form on grid paper reads as a mistake.
  • Improved .ac-sample-chip is retired (agenda-check/agenda-check.css). "Try a sample agenda" was a dashed pill with its own hover, focus ring and transition, and no second instance anywhere on the platform; it now renders ws-btn ws-btn--secondary ws-btn--md, the same vocabulary as the "Check my agenda" primary two rows above it. The became a ws-btn__icon holding Material Symbols auto_awesome, since a raw emoji inside a design-system button was the tell that it was not one. #acSampleChip stays: agenda-check.js toggles its hidden as the textarea fills, so the id is contract and only the styling changed.
August 2026
v2.11.1: The rail's DNA card becomes artwork
  • New .app-rail__dna-art (app/css/app-shell.css): the block for the baked DNA cards in images/dna/profile/. Deliberately chrome-free: the artwork carries its own border, padding and cream ground, so a ws_card wrapper would have drawn a second frame around the first. The image holds its slot with aspect-ratio: 27 / 10 rather than a fixed height, so a slow network cannot reflow the rail beneath it, and it caps at 360px under 1100px where the rail goes full width, because the widest cut is 640px and a stretched rail would upscale the artwork into mush.
  • Improved The old .app-rail__dna row is now the fallback, not the default. appRailProfileCard() returns the artwork when both cuts of a card exist and the text row otherwise, so an archetype added without art degrades to a working card rather than a broken image. The block comment says so; do not delete it as dead CSS.
  • Improved Not to be confused with ws_dna_cta (includes/components/dna-cta.php), the full-width marketing strip on the homepage and library index. That one is real text with a real button and has no earned state; this one is a 270×100 rail widget whose wording is part of the image. Two different jobs, and worth keeping straight before either grows into the other. ws_dna_cta still points at the pre-Red-Unification green Energizer badge.
August 2026
v2.11.0: The app frame footer, the checklist's celebration state, and the branded modal earns its styles
  • New ws_app_footer() grows a full variant: the consolidated shell's 56px beta bar (wordmark, 2.0 Beta pill, build version read live from the changelog, Send feedback, Resources as the launcher trigger, Company). The 24px band stays the default; the composite's automatic routing is unchanged, and the shell picks the variant through $GLOBALS['ws_app_footer_options']. The template also honours $appFooterStylesOnly so a documentation page can borrow the styles without consuming its one footer.
  • New ws_task_checklist() gains cta and a designed complete state. The in-card action renders through ws_button below the items; with every item done the card wears .ws-task-checklist--complete: red-tinted surface, red filled checks, plain ink labels. The app shell's rail journey card (app/includes/rail.php) is the reference consumer, with past-tense labels and a dismiss that persists by cookie.
  • Fixed ws_modal's branded variant renders. The JSON has documented variant: branded since the catalog was written, but only the per-call headerColor path had styles, so the variant fell through to the default. It now paints the red header band the planner dialogs and the app launcher wear; reach for the variant before headerColor.
  • New The workshop listing pages. ws_workshop_list() accepts pageSize: rows past page one ship hidden, workshop-list-pages.js draws the strip (numbers to seven pages, arrows and a count past that), and pagination refuses to engage alongside the stage filters: both hide rows, and two hands on one attribute loses cards. Grid and list only.
  • New The app shell's two-column frame. .app-cols + .app-rail (app-shell.css): the 320px sticky aside Home, Plan, Facilitate and Synthesize share, carrying the DNA badge card, the journey checklist, the film cards and the promo cards from the app wireframes.
  • Improved The shell's hero second line goes upright. Every app wireframe draws the hero's red line without the italic or the tint underline, so .app-hero__title em (and the Strategist's .st-hero copy, inside the shell) retire the Display Text Pattern there. The pattern stays the marketing surfaces' signature per DESIGN.md §7; the <em> stays in the markup because the emphasis is real, only the slant is not.
August 2026
v2.10.1: The bulk bar honours [hidden]; the card menu honours ownership
  • Fixed .wl-bulkbar ignored its own hidden attribute. The block sets display: flex, which beats the user agent's [hidden] { display: none }, so the bar the script hides by toggling that attribute stayed on screen under every workshop listing, Delete button and all. Clicking that Delete did nothing, because workshop-list-select.js returns early on an empty selection. An explicit .wl-bulkbar[hidden] { display: none } now closes it, and the block header says why the rule is not redundant.
  • Fixed Arrow keys inside the card menu's confirmation stayed inside it. hidden sits on the list and confirmation wrappers, not on the buttons within them, so the popover-wide focus query in workshop-list-menu.js walked ArrowDown from Cancel onto the invisible Delete and appeared to swallow the key. The traversal now reads from whichever panel is showing.
  • Improved ws_wl_menu() renders per ownership. Duplicate, Copy share link, Slide deck and Delete are owner-only in api/plans.php, every one of them scoping its query to user_id = ?, so the menu now omits them on a shared card and offers Remove from my workshops instead, backed by remove_collaborator, which gained a plan-scoped self-service branch: one account can hold more than one accepted row on a plan (the unique key is plan_id + email), and dropping a single row would leave the workshop on the list. A row with no owned key reads as not owned, matching ws_wl_select_box(), which has applied the same rule to the selection checkbox since it shipped.
August 2026
v2.10.0: The app launcher becomes the journey, five phases across
  • Improved .app-launcher__groups is a five-column grid. The launcher now lists Learn, Strategize, Plan, Facilitate and Synthesize, and the grid was still repeat(4, 1fr), so the fifth phase wrapped to a second row and stopped reading as a step in a sequence. The column count is fixed rather than auto-fitting, for that reason.
  • Improved .app-launcher__panel widens to 1360px (from 1040), which holds five columns at 246px each — the width the longest app name plus its leaves-the-frame mark needs. The break to three columns moved from 860px to 1130px to match. Both numbers are window measurements against a frame that paints at zoom: 0.8, and the block says so: 1130px of window is 1412 CSS pixels inside the shell, which is the panel plus its padding.
  • Fixed A launcher row's name no longer runs under its leaves-the-frame mark. The mark is positioned out of flow, so nothing held the name back from it; "Post-Workshop Toolkit" finished two pixels short of touching. .app-launcher__app:has(.app-launcher__out) .app-launcher__name now reserves var(--space-6) on the right.
August 2026
v2.9.99: ws_lifecycle() retired; My Stuff gains a submission form
  • Fixed ws_lifecycle() is gone. The helper (includes/components/helpers.php), its template (includes/components/lifecycle.php), its demo page (design-system/components/demos/lifecycle.php) and its entry in workshopr-design-system.json were all removed with the MyWorkshopr consolidation. It shipped as a second, older answer to a question ws_wl_journey() (app/includes/workshop-list.php) already answered, and it never gained a caller in eleven months: a documented component nobody uses is a component the next person builds against by mistake. The .ws-lifecycle CSS in components.css staysplanner/includes/views/saved-body.php still hand-rolls that markup in JS for the saved-agenda cards, and is now the block's only consumer. Retire it there and the CSS goes with it; the block header says so.
  • New /app/?view=stuff&step=contribute — the exercise submission form, ported out of myworkshopr/add-exercise.php (742 lines, an inline IIFE, its own orange accent family) into the shell. Everything the old page hand-rolled is a component now: ws_input for the eight fields, ws_radio_card_group for difficulty and the category picker, ws_chip_input for materials, ws_icon_button for the duration stepper and the step deletes, and the platform Toast in place of a hand-built .ws-alert template string. New sheet at app/css/stuff-contribute.css, scoped to .app-contribute and painting entirely from platform tokens — the retired --exercise-accent orange is the one Learn red now, per Red Unification. The fixed right-hand drawer became a sticky column: the shell draws at zoom: 0.8 inside its own scroll region, where a viewport-anchored drawer lands against the wrong edges.
  • Improved The myworkshopr header section is retired. _wsh_sections() in includes/components/header.php lost the key and its [data-section="myworkshopr"] tag-width rule, WS_APP_PATHS in helpers.php lost the path, and the nav inventory at includes/nav-example.php lost the row. Every /myworkshopr/ URL is a 301 that renders no header at all, and an unknown section key already falls back to home, which is what the shell uses.
August 2026
v2.9.98: The colour input becomes a control
  • Fixed ws_input($name, ['type' => 'color']) rendered as a hairline. A colour input's intrinsic content box is a few pixels tall, so .ws-input's shared padding left a ~6px swatch inside a 26px field — a control that read as broken rather than as a colour. input[type="color"].ws-input now carries the rendered height of a text field (44px, 36px at --sm), 4px of padding, and a borderless swatch inset to the field's radius. The Planner's new Slide deck modal is the first place two of them sit side by side, which is how it surfaced.
August 2026
v2.9.97: Closing out the 23 August audit
  • Fixed --mainsite-gradient starts at #D13A3A, not #E54D4D (WCAG SC 1.4.3, Level AA). This gradient backs --button-bg-primary, whose label is --text-small: 11.2px at the 80% root, so normal text with a 4.5:1 bar. White measured 3.82:1 at the light stop. It is now 4.80:1, with the dark stop unchanged at 5.02:1. Flat brand red is untouched and still lives in --mainsite-primary.
  • Fixed --planner-gradient and --planner-topbar-bg now alias --mainsite-gradient. The audit named only the primary button, but the planner carried the identical failure with white text on it in two places: the topbar and .recommend-item__select:hover. Worse, css/planner/base/_variables.css rebuilt the gradient from --mainsite-primary rather than sharing it, so the planner would have kept the failing stop after the button was fixed. One definition now, one place to fix.
  • Fixed .ws-audio-player__btn hovered purple. It read var(--color-primary-dark, #651FFF), and neither --color-primary nor --color-primary-dark exists in platform-tokens.css, so the rest state fell back to red while the hover fell back to a pre-Red-Unification violet. Both now read the real phase tokens.
  • Improved ws_dna_cta's CSS moved into components.css and was tokenised. It also referenced var(--text, ...) and var(--display, ...), neither of which is a token, so both always rode their literal fallbacks; they now read --font-body and --font-heading. Every rendered value was verified byte-identical afterwards. Font sizes stay in px deliberately: at the 80% root, var(--text-body) would paint 12.8px, not the 16px the banner is drawn at.
  • Improved Six bare #FFFFFF literals tokenised in the pill and toggle components. The audit called these ambiguous, and they are in the abstract, but the property settles it: five are text on a filled pill (--text-inverse) and one is a toggle knob surface (--bg-surface).
  • Improved Dead CSS removed: .action-buttons-standalone and the .pv-toggle block, the latter a transitional planner v1/v2 switch whose own comment asked for its deletion once the rollout landed. Neither had any markup left.
  • New Demo pages for ws_audio_player, ws_audio_recorder and ws_sidenav, added to the sidebar so they are reachable rather than direct-URL only.
  • New Second audit at AUDIT-2026-08-23-css-foundations.md, covering the foundation layer specifically. Its headline finding is that 59 custom properties across five stylesheets are defined as themselves, which under the CSS cycles rule destroys them rather than inheriting the platform value, discarding 335 declarations on live pages. Not yet fixed: each change is visible and wants review.
v2.9.96: The focus ring the system described but never applied
  • Fixed Baseline :focus-visible indicator in platform-tokens.css (WCAG 2.2 SC 2.4.7, Level AA). The file defined --focus-ring and documented at length why it is sky blue, but nothing applied it: the site's only global rule lives in css/styles.css, which a page gets only through includes/header.php. Every page that hand-rolls its own <head> had no visible keyboard focus outside forced-colors mode, including the homepage, all of /tips/, and every page of this design system. The new rule uses :where() so its specificity is zero: a floor that never fights a component's own focus style.
  • Fixed .ws-app-bar__switch-option and .ws-choice-card drew no focus ring at all. Both wrote outline: 2px solid var(--focus-ring, #38BDF8), but --focus-ring is the whole shorthand, so the value expanded to 2px solid 2px solid #0284C7 and was discarded as invalid. Both now read outline: var(--focus-ring, ...) like the eleven correct usages elsewhere in the file.
  • Fixed Two pointer targets under 24x24 (WCAG 2.2 SC 2.5.8, Level AA). .ws-chip__remove was 18x18 with no padding and now carries an ::after { inset: -3px } hit area, which reaches 24x24 without changing the chip's height. .ws-planner-activity-inspector__close went from 22x22 to 24x24. --target-min: 24px already existed, annotated with the SC number, and nothing was using it.
  • Improved css/tips/styles.css imports platform-tokens.css, as css/dna/styles.css and the academy bundles already did. It declared its own palette and imported nothing, so no token ever reached /tips/.
  • New Audit at AUDIT-2026-08-23-system-audit.md: inventory, naming, completeness, WCAG 2.2 AA, token coverage and theming. Registry mirrors cleanly at 64 = 64, token coverage is 92.7%, theming is deliberately single-theme and documented as such, and reduced motion is handled by a universal gate. One finding needs a product decision: white on .ws-btn--primary is 3.82:1 at the light end of the brand gradient, below the 4.5:1 that 11.2px button text requires.
August 2026
v2.9.95: The homepage gives up its inline CSS
  • New /css/homepage.css, holding what were two <style> blocks in index.php. The page was 1,591 lines, 1,305 of them CSS; it is now 287. Markup is byte-identical and every surviving rule moved unchanged.
  • Fixed 892 dead lines removed, 156 rules. The page renders its hero with ws_section_hero() and its DNA band with ws_dna_cta(), which emit .wsh-hero* and .ws-dna-cta*. The hand-rolled .home-hero*, .hp-hero* and .hp-dna-band* families they replaced were still declared, along with .hp-product-card*, .hp-solution*, .hp-tool-card*, .hp-journey-grid, .hp-final-cta*, .hp-mini-agenda* and .hp-phase-pill. Three hero designs were declared; one is rendered.
  • Fixed The retired phase palette is gone from the homepage. Plan blue #0284C7, Facilitate purple #7C4DFF and Reflect emerald #10B981 survived as hardcoded fallbacks behind --phase-plan, --phase-facilitate and --phase-reflect. Those tokens are #E54D4D today so nothing painted them, but a fallback is a value waiting for its token to move. All of it sat inside the dead rules.
  • Improved body takes --paper-grid-image / --paper-grid-size instead of re-typing the two gradients. --grid-line-color is rgba(44, 36, 22, 0.06) and the size is 24px 24px, exactly what the page had, so this is identical pixels; the token also resolves to none under forced-colors, which a literal gradient does not. The token's own comment names this as how five pages lost the grid.
  • Improved .hp-quote-card onto the card tokens. Moving the CSS into a .css file made it visible to check-drift.php for the first time, which immediately failed it on padding: 24px. It now reads --card-padding (20px, a real 4px change), plus --card-radius and --card-shadow in place of --radius-xl and --shadow-sm, which are renames at identical values.
  • Fixed Known gaps, not closed here: five component templates still carry their own <style> blocks rather than using components.css (footer.php 269 lines, header.php 159, section-hero.php 109, dna-cta.php 39, app-footer.php 5), and the homepage still speaks a private token alias layer (--paper, --ink, --learn, --display, --text) over the platform tokens.
August 2026
v2.9.94: The app shell's views give up their inline CSS
  • Improved Four <style> blocks moved out of app/views/*.php into app/css/app-shell.css: the .modal-overlay FOUC guard, .lp2-nudge*, .prvh-* and .pwa-install-banner*. Inline CSS in a PHP file is invisible to check-drift.php, which filters on the .css extension, so the card rules in those blocks had drifted to border-radius: 12px; padding: 18px with nothing to catch them. They now read --card-radius and --card-padding, and the card hover reads --card-shadow-hover instead of a hand-rolled warm shadow that no token defined.
  • Fixed About 660 lines of dead stylesheet removed. .lp2-card*, .lp2-life*, .lp2-btn*, .lp2-recent*, the whole .fach-* vocabulary, all of app/css/synthesize-home.css, and dashboard.css's .dash__row section were each orphaned by the same event: the page moved to ws_workshop_list() and its old styles stayed. app-shell.css's workshop-card border rule and section-heading rule were carrying nine of those names.
  • Fixed body.launchpad-page deleted from Plan's home view. It re-declared nine --mainsite-* and --planner-* tokens as red, and it could never match: the shell sets $body_class = 'app-shell'. Those were eight of the eleven hardcoded-hex violations the file carried in page-baseline.json; the entry is now {"raw-component": 4}.
  • Improved Prove's picker joins .app-hero. It was the one section with a hand-rolled heading and its own clamp() ramp, and the one page missing from the shell's page-frame neutraliser, so it indented its content further than every other view.
  • Fixed Known gap, not yet closed: check-page.php reads <style> blocks but only scans them for hex literals, so card geometry written inside a PHP file is still ungoverned. See AUDIT-2026-08-22-app-css-localization.md.
August 2026
v2.9.93: The 6C sequence strip and step chips
  • New .agenda-gen-6c in css/planner/components/_agenda-generative.css: the 6C sequence strip in the generator's rationale panel. Six rows on a shared vertical spine drawn with a ::before hairline in --planner-gray-200, trimmed to half height on the first and last rows so the line starts and ends at the dots rather than overshooting. A covered step gets a filled --phase-learn-dark dot with a --planner-bg-white ring punching it out of the spine; a missing step gets a hollow dashed --planner-gray-400 dot and its minutes column turns red. Grid is 16px 1fr auto so the minutes stay right-aligned and tabular-nums keeps them from dancing.
  • New .agenda-gen-c: the per-row step chip (Connect, Collect, Choose, Create, Commit, Celebrate) sitting next to ws_activity_badge in the generated agenda row head. Brand red on --phase-learn-very-light with a --phase-learn-light border.
  • New Two colour systems, deliberately kept apart. The activity-type badge keeps its sanctioned functional coding (exercise blue, icebreaker green, break grey, custom purple) because item types must stay distinguishable on the canvas. The 6C steps are a sequence, not categories, so they are not colour-coded against each other: one brand-red accent marks "covered" and a hollow dot marks a gap. Giving the steps their own six-colour scale would have put a second colour axis in direct competition with the type colours in the same row.
August 2026
v2.9.92: Plan Settings goes tabbed, and the app-bar gear turns white
  • New .planner-settings-tabs in css/planner/components/_modals.css: the modal's ws_tabs nav, flush under the modal header with a --planner-border rule beneath it. The panes are stock .ws-tab-pane, and switching runs on the existing wsTabsInit/wsTabsSwitch pair in includes/components/components.js rather than a second implementation. Note that components.js already auto-runs wsTabsInit over every .ws-tabs on DOMContentLoaded, so callers should only ever call wsTabsSwitch; calling wsTabsInit again double-binds the click handlers.
  • Fixed Ghost icon buttons in an app-bar trail were reading dark on the red bar. ws_icon_button renders a .ws-btn--ghost, which inherits the default warm ink, while every .ws-app-bar__action beside it is --text-inverse white. Added .workshop-planner .ws-app-bar__trail .ws-btn--ghost to _component-bridge.css giving the glyph, hover and focus ring the same white treatment as its neighbours. Scoped to the planner deliberately: Synthesize's gear sits in the same slot and has the same problem, but sweeping it is a separate call.
  • Removed .planner-settings-section and .planner-settings-section__title from css/planner/components/_modals.css. Tabs replaced the stacked sections, so there is no adjacent-sibling divider to draw and no section heading to style. .planner-settings-note stays (it still styles the signed-out and no-workshop captions) and .deck-settings-hint is untouched.
  • Fixed Paired fields in modals must be direct children of .ws-form-row. ws_form_row($a . $b) nests its content in a single .ws-form-row__field, which is a block, so the two .ws-field elements never become flex siblings and .modal-overlay .ws-form-row .ws-field { flex: 1 } has nothing to act on. Use a raw <div class="ws-form-row"> with the fields inside it, as the Create New Workshop modal does. No CSS change was needed; the rule was already correct.
August 2026
v2.9.91: Planner settings modal styles
  • New Planner settings-modal section styles in css/planner/components/_modals.css: .planner-settings-section (adjacent sections divided by a --planner-border rule), .planner-settings-section__title (mirrors .export-label), .planner-settings-note (muted caption), and .deck-settings-hint (the export modal's pointer to Planner Settings). All values are platform tokens; the modal itself is the standard modal-planner.php shell with ws_input/ws_select/ws_button fields, and the topbar gear is a stock ws_icon_button.
August 2026
v2.9.90: The lifecycle strip catches up to the fourth phase
  • Improved ws_lifecycle() now renders all four workshop phases: Strategize, Plan, Facilitate, Synthesize, matching the vocabulary ws_wl_journey() already uses in the workshop listing. The component previously still spoke the retired three-step reading (Designed, Delivered, Synthesized) even though nothing calls it: a documented design-system component teaching a lifecycle the product no longer has would mislead anyone building from it. The retired three values are kept as aliases (designedplan, deliveredsynthesize, synthesizedsynthesize), cheap insurance since this repository's working tree carries substantial uncommitted work that a plain grep of committed code cannot see.
  • Fixed The strip wraps instead of overflowing at narrow widths. .ws-lifecycle and .ws-lifecycle__steps gained flex-wrap: wrap, a max-width: 100% ceiling, and a row-gap token for the wrapped state. The old single-row layout held three steps at any width but pushed a fourth off the edge of a 375px viewport.
August 2026
v2.9.89 — The unified shell, rehearsed in mockups
  • New Record-strand mockup set: 24 tokens-only HTML states at synthesize/mockups/record-strand/ (contact sheet at index.html) rehearsing the unified app shell: white global header, dark icon rail, red per-app bar, and a bottom action rail. Every value is a var() against platform-tokens.css, so the mockups cannot drift from the system. The shell patterns (ws-global-header, rail-variant sidenav, ws-app-bar, synth-action-bar) are candidates for promotion to ws_* components when the strand is built.
August 2026
v2.9.88 — Every card is one size, and the check that keeps it that way
  • New ws_choice_card() and ws_choice_cards(): one way into a task, offered beside the others. Icon, title, a sentence of what you get, and a foot carrying the time cost and the action. Three pages were each carrying their own copy of this markup and CSS — Plan's home, the planner home, and the builder's empty canvas — and the copies had drifted: 20px padding against 18px, a link-styled call to action against a filled button. One component now, with a demo page.
  • Fixed The card is a real control. The old markup was a <div role="button">, which looks like a button and does nothing on Enter unless you write the key handling yourself, and nobody had. It renders as a <button>, or an <a> when you pass href, and brings the behaviour with it.
  • Improved --card-padding now means something. It was declared at 24px and read by nothing while every page-level card drew itself at 20. The token moves to 20 — the value the cards were actually at — and --card-padding-compact (12px) joins it for dense rows like the planner's library. .wl-card, .listing-card, .library-card and .ws-card--content now read the card tokens instead of hardcoding their own geometry.
  • New Check 4 in check-drift.php: a card rule that sets padding or border-radius with a literal instead of a token fails the build. Ratcheted against card-geometry-baseline.json, which grandfathers the 124 literals that predate the tokens — they show as a WARN count, and shrink as cards are repointed. Nine self-test fixtures cover what counts as a card: BEM modifiers and pseudo-classes do, __element classes and .card-shelf do not.
August 2026
v2.9.87 — The Synthesize action rail, and a note that changes its mind
  • New ws_synth_action_bar(): the stone rail that closes an empty Synthesize surface. A note pinned at the left says what the room should have brought back; the one way in sits at the right, rendered through ws_button() rather than hand-cut. Built from the synth-action-bar frame, which is now a component in Figma too.
  • New The note rotates. One nudge covers one situation, and a reader with nothing to synthesize is rarely stuck for the reason the first note names, so the bar cycles its set every 12 seconds instead of repeating itself. Hovering or focusing holds the current note, a hidden tab stops the clock, and under prefers-reduced-motion nothing moves at all. Pass your own notes, or one note to make it stand still.
  • Improved The note is paper, not handwriting. The mock set it in Marker Felt; the platform has two families and this is not one of them, so the sticky reads as a sticky through the tilt, the amber paper, and the soft shadow, with the text in Inter. Colours all resolve to existing tokens: --color-warning-very-light paper, --color-warning-light edge, --color-ink-secondary ink, --color-ink-faint rail.
August 2026
v2.9.86 — The app bar learns which app it is in
  • New 'state' option on ws_app_bar(): strategize, plan, facilitate or synthesize. Each supplies that app's centre (nothing for Strategize, the duration/date/start-time read-out for Plan, start-session and join-code for Facilitate, the Recap/Workspace switch for Synthesize) plus the shared action cluster, with Share replacing Continue in Synthesize. Built from the four bars drawn in Figma, which are now one component set there with a matching state variant.
  • New .ws-app-bar__control and .ws-app-bar__switch: the read-out and the segmented switch, promoted out of the planner's and Synthesize's own markup into the component. Both are translucent white, because they sit on the red bar rather than on a surface.
  • Improved A named state fills only what the caller left empty. Pass your own center or actions and they survive untouched. A state inferred from the shell's ?view= is more cautious still, adding only the .ws-app-bar--{state} class: the existing callers pass their own actions, and a bar must never grow controls the page has no code behind.
  • New App Bar demo page at /design-system/components/demos/app-bar.php, showing the four states together.
August 2026
v2.9.85 — The workshop listing learns its stage scope, and all three pages now share it
  • New 'stages' option on ws_workshop_list(): the stages a given listing can actually contain, e.g. ['plan', 'facilitate']. It drives the filter chips and the kanban columns together, so neither offers a stage that can never appear there. It deliberately does not touch a card's journey strip — that shows the workshop's whole life, and truncating it would misreport where the workshop is. Added when Facilitate exposed the inconsistency: hiding its chips because only two stages apply, while the kanban still drew four columns with two that could never fill.
  • Improved All three list pages now render from this one component, each configured to its own job rather than forked: Plan's "Recent workshop agendas" (in-planning only, six, grid and list), Facilitate's "Choose a workshop" (runnable only, scoped to two stages, with the kanban standing in for the retired Ready-to-run / In-progress tabs), and Synthesize's picker (all stages, per-row action by stage). Between them this retired three hand-cut card templates, three copies of the same fetch-and-render JavaScript, and the skeleton placeholders all three used to paint while waiting.
August 2026
v2.9.84 — Synthesize gets a bottom rail: one place to commit
  • New The Synthesize bottom rail (app/css/synth-rail.css, filled per screen by syncRail() in synthesize.js): context on the left, the screen's one commit action on the right. Intake's "Start synthesis" left the artifacts rail, review's "build the synthesis" left the end of a long scroll, and the extraction screen's Cancel left the content — all three now share one address. The rail owns no logic: its buttons re-dispatch the data-act values the delegated handler already had, so each action still has exactly one implementation.
  • Fixed A surface outside #synth-app needs its own data-act listener. render() owns that node's innerHTML, so anything that must survive a re-render lives outside it — and the delegation bound to the root never sees those clicks. The topbar already carried its own binding for this reason; the rail shipped without one and its buttons rendered perfectly while doing nothing. Caught by the existing Playwright suite, and now covered by a test that asserts the click reaches the handler rather than that a screen changed.
  • Improved Its own stylesheet rather than a block in synthesize.css, because the picker page does not load the tool's stylesheet and two copies of the rules would drift. Not fixed-position either: .synth-shell is already a flex column with a scrolling stage, so the rail is that column's last child and never covers the content above it.
August 2026
v2.9.83 — The workshop listing gets its data layer, and Synthesize adopts it first
  • New ws_workshop_list_fetch($pdo, $userId) returns every workshop a user can see, already normalized. It calls the same functions the two endpoints wrap — getWorkshopList() for lifecycle stage and the room's counts, plus one grouped join for duration and item count — so a page renders its rows in the HTML instead of painting a skeleton and filling it a round-trip later. The three list pages can now share one server-side read.
  • Fixed An unknown item count renders as nothing rather than "0 items": ws_workshop_list_normalize() keeps a missing count as null, and the meta line omits it. Claiming zero when you mean "not known" is a different and wrong statement.
  • New ws_wl_duration() formats minutes as "2h" / "1h 45m" locally rather than borrowing includes/data.php's formatDuration() — that file calls loadFromDatabase() at the bottom, so requiring it for a string helper would fire a full content load on every page that lists workshops.
  • Improved Synthesize's home page is the first adopter: its "Ready to synthesize", "Working from something else?" and "Recent recaps" sections are replaced by the listing, and the page's list JavaScript is gone with them. Timeline is deliberately not among the offered views there — it groups by scheduled date and the Workshop Record carries no scheduled-at stamp yet, so every row would land in one undated bucket.
August 2026
v2.9.82 — The workshop listing becomes one shared pattern
  • New ws_workshop_list() (app/includes/workshop-list.php, styles in app/css/workshop-list.css, behaviour in app/js/workshop-list-views.js): one listing with four interchangeable views — grid, list, kanban by stage, and a timeline grouped by scheduled / recent / drafts — under one chrome of eyebrow, title, stage filter chips, view switcher and an "All workshops" footer. Built from the Figma Recent workshops frames; the file's three variables map exactly onto existing tokens (paper #FAF7F2 = --bg-paper, #C73E3E = --phase-learn-dark, #FEE2E2 = --phase-learn-light), so it introduced no new colour.
  • New Server-rendered, unlike the three hand-cut lists it is meant to replace: every view is in the HTML and the switcher only toggles which is visible, so changing view or filtering by stage costs no fetch and no skeleton flash. ws_workshop_list_normalize() takes a /api/plans.php row plus lifecycle_status and maps it to the stage a workshop is ready for (designed → Plan, delivered → Facilitate, synthesized → Synthesize).
  • New Demo and tweaking surface at /design-system/app-workshop-list.php, carrying the Figma frames' own sample content plus the three per-page configurations. Not yet adopted by Plan, Facilitate or Synthesize — those three swaps are the next step.
August 2026
v2.9.81 — Tokens must exist: the drift check resolves every var() reference
  • New check-drift.php check 3: every var(--x) in shipped CSS must name a custom property defined somewhere real. The universe is all scanned sheets plus runtime-set properties (inline --x: in markup, setProperty, React computed style keys), because app-local token sheets and per-item inline values are legitimate here; what fails is a no-fallback reference to a property defined nowhere at all, the class where the declaration is invalid at computed-value time and silently resets. References carrying a fallback warn instead. --self-test runs the parser fixtures, including the historical var(--space-7) case that shipped a collapsed padding; accepted one-offs go in $KNOWN_DRIFT as css-var:<file>:--<token>.
  • Fixed The 26 no-fallback references the first run caught: --space-7 (taste buy strip), --space-9 (workshop-record artifact icons), Facilitator's never-defined --font-size-base/sm/xs scale (29 declarations were inheriting instead of sizing), the orange card/badge family plus two legacy surface and ink names in styles.css, admin's --admin-accent-* variants, MyWorkshopr v3's neutral vocabulary, --planner-primary-dark--planner-red-dark, --color-amber-700 defined beside its existing --color-amber-100, the unused .mw-svg-icon mask block removed, and the bounce-in animation riding --ease-out now that the overshoot easing is retired.
  • Fixed Ported Synthesize renders at native scale inside the /app/ frame: the frame's 0.8 zoom had shrunk the app's literal-px type and its rem-token ws_* labels alike, so .app-shell .synth-shell { zoom: 1.25 } composes back to 1:1 rather than compensating per component.
August 2026
v2.9.80 — The stsh shell becomes a shared component
  • New The phase-journey shell (top bar + dark rail + ruled canvas) is now shared chrome: markup in includes/components/stsh.php (stsh_topbar(), stsh_rail(), stsh_sym()), styles in css/stsh-shell.css, and a document-head layout at includes/components/stsh-header.php (registered in check-page's LAYOUT_FILES). Born in the Strategist; extracted when Synthesize adopted it. The Strategist's st_* helpers now delegate here, and the /app/ frame deliberately never loads it — its own sidenav is the chrome there.
  • New .stsh-canvas--app variant for viewport-locked tools: the canvas stops scrolling and hands its box to the app inside, keeping the app's own #main-content sizing rules working unchanged.
August 2026
v2.9.79 — Synthesize's results screen finishes adopting shared platform chrome
  • Fixed The metadata-first "New synthesis" modal, source toolbar, flat/triage review, five-tab result board, Kanban/Outline theme layouts, persistent source rail, and side-panel AI are removed with their dead CSS — each had no remaining caller once the recap-first redesign shipped (Tasks 2–10). The legacy internal home/gallery is not among them: it is retired from the in-shell experience only (the "Your syntheses" control and the Back-past-earliest-stage path now redirect to the /app/ product home instead of rendering it) and is retained solely as the standalone synthesize/app.php entry's legacy/anonymous landing and boot-failure fallback. What's left carrying the in-shell screen is entirely shared platform pattern: the ws_app_bar, the .ws-tabs component driving Workspace's own tab bar, the .recap-*/.ev-* evidence-card family, the platform focus-ring token on every disclosure and tab control, and the existing responsive rules. No new shared component was added here — this closes out adoption of ones already in the system, not a new pattern.
  • Fixed .ol-theme and the rest of the retired Outline/Kanban/theme-card/card-menu selectors are deleted rather than left as unreferenced rules for the next design audit to re-flag.
August 2026
v2.9.78 — The Workshop Thread graduates to a production page
  • New The connected-workshop direction now renders at /index-app.php with production components. The root-owned responsive stylesheet and interaction script retain the Thread mockup's two-page hero, four-stage workflow, and accessible tab behavior while the real ws_header(), ws_button(), and ws_footer() supply the shared shell.
August 2026
v2.9.77 — The run-sheet preview is a two-page stack
  • Improved The Workshop Thread hero layers the facilitator-notes page behind the agenda. Opposing token-based offsets expose the back page's dark rail while keeping the front page dominant; mobile narrows the spread so the stack stays inside the viewport.
August 2026
v2.9.76 — The Workshop Thread hero shows the finished run sheet
  • Improved The landing mockup's hero artifact now uses the real Planner run-sheet preview. A token-based paper mount and restrained offset layer keep the tall export legible in the asymmetric hero, then remove the rotation and narrow the preview on smaller screens.
August 2026
v2.9.75 — The Workshop Thread begins before the agenda
  • Improved The landing mockup's lifecycle now supports four connected stages. Strategize precedes Plan, Facilitate, and Reflect; the mobile stage switcher becomes a two-by-two grid so all four labels remain readable without shrinking the type.
August 2026
v2.9.74 — app pages close on a 24px rail, not on the marketing footer
  • New ws_app_footer() — a 24px --gray-400 rail (template includes/components/app-footer.php) that ends a page inside a sub-application. It holds nothing: no links, no signup, no copyright. A tool the user opened deliberately should stop when the content stops, and the previous ending was 2,500px of Level Up grid, newsletter CTA and dark brand bar below every workspace. Self-contained like the composite it replaces — it emits the token sheet, its own style block, and the toast/auth core, each once — because the Planner and Facilitator run their own CSS stacks and don't all load components.css. Carries aria-hidden="true": an empty contentinfo landmark is one more thing a screen-reader user has to check and discard.
  • Improved The routing is one test in one file, not a flag on every page. ws_footer(), a direct components/footer.php include and the legacy includes/footer.php all funnel through the composite template, so if (ws_is_app_page()) at its top covers roughly 120 pages across sixteen apps without touching any of them. ws_is_app_page() matches the first request-path segment against WS_APP_PATHS, which mirrors the app inventory in PRODUCT.md §5 — adding an app is one string. Reading the request path rather than the script path is deliberate: under the built-in dev server the router owns SCRIPT_NAME, so the real page is only visible via REQUEST_URI.
  • Improved Marketing surfaces are untouched. The homepage, /about/, /pro/, /solutions/, /help-center/ and /changelog/ keep the full composite — the Level Up grid and the newsletter CTA are how those pages earn their keep, and the rail would cost signups. The split is by path, so an app's own landing page (/planner/, /facilitator/) takes the rail with the rest of its app.
  • New Documented at components/demos/app-footer.php, with the Footer demo now linking to it instead of repeating the stale claim that app shells run footer-less. Both demos are in the sidebar; Footer had never been listed. DESIGN.md §12.7 replaces its "app-shell exception" with the app rule.
August 2026
v2.9.73 — /app/ renders at 80% via zoom, not via redeclared tokens
  • Improved body.app-shell takes zoom: 0.8. This is the second attempt at a smaller frame and it is deliberately the other mechanism. zoom scales the rendered result, so every proportion inside stays the one the design system specifies and the frame is simply drawn smaller; redeclaring tokens at a fraction (v2.9.68, reverted in v2.9.71) rounds each token independently, so a 2px hairline and a 48px gutter shrink by different ratios and the relationships drift — which is what made 0.67 read as broken rather than small. It also composes with the reader's own zoom instead of replacing it: a reader at 125% sees this frame at 100%. And one property reverses it, where the token pass needed two stylesheets, 128 var() fallbacks and 45 literals put back.
  • Fixed The body height is 125dvh, not 100dvh. zoom scales the box after layout, so 100dvh is drawn at 80% of the viewport and leaves a fifth of the screen blank under the frame; 100 / 0.8 fills it exactly. This is the one real cost of the approach, and any viewport unit added to these stylesheets needs the same treatment. Verified: the frame bottom sits flush with the window, and the launcher scrim still covers the full viewport despite the zoomed containing block.
  • Improved The workshop card takes a #A8A29E border in the shell--gray-400, four steps up from the --gray-200 it had, so the card reads as a drawn edge on the drafting paper rather than a hairline that dissolves into it. .dash__row, .lp2-card, .fach-card, .synh-card and .synh-pickup are one object seen from five places, so they are named together in app-shell.css rather than edited in four files — which also leaves the standalone pages on the --gray-200 they share with the rest of the site. .dash__viz, .dash__ftrack and .dash__empty keep the neutral border: they are panels, not workshops.
  • Fixed The dashboard row's hover no longer runs backwards. That hover was tuned to lift --gray-200 to --gray-300; against a --gray-400 rest state the same rule lightens the edge, so the card receded at the moment it lifted. .app-shell .dash__row:hover takes --gray-500 and keeps the direction the interaction was designed around. It is the only one of the five whose hover touches the border; the rest move transform and shadow.
  • New Synthesize's hero eyebrow carries a BETA marker.app-hero__beta, inside the .app-hero__eyebrow pill rather than beside it. As a sibling pill it read as a second eyebrow: same outline, same fill, so the eye had to read both to learn one was a status. Inverted and tucked inside, it is unmistakably a marker on the section name, and solid --phase-learn-dark carries white at 4.98:1 where the eyebrow's own light fill would have put 10px text at 3.49:1.
August 2026
v2.9.72 — The shell's header bar is 48px, and it is a layout decision, not a scalar
  • Improved body.app-shell > .wsh-header is 48px with 20px gutters, down from the component's 64px and 24px. Inside the shell the bar carries a brand, two icon buttons and an avatar — the nav, section tag and switcher are hidden — so it is an app title bar, not the site's navigation band, and 64px gave a row of chrome the height of a content card.
  • Improved The controls come down to 32px (mark, icon buttons, avatar), radius 9→8px, mark glyph 22→20px, wordmark 18→16px. 36px boxes in a 48px bar leave 6px of clearance, which reads as a bar too short for its contents; 32px restores a clean 8px, measured. The icon glyphs inside the buttons are untouched — the target tightens, not the icon.
  • Note This is the shape v2.9.71 said a density change should take. One region is resized and its contents fitted to it, so the bar reads as deliberately compact; the 0.67 pass that preceded it applied a scalar to the whole token set and read as a zoomed-out page. Restated in app-shell.css rather than the component because ws_header draws itself in literal px with no height token to theme.
August 2026
v2.9.71 — The shell's 0.67 density is reverted; /app/ renders at platform scale
  • Fixed The body.app-shell token block is gone. v2.9.68 redeclared spacing, radii, the type scale and icon sizes at 0.67 to correct a frame judged too large. What it produced read as a permanently zoomed-out page: --text-body resolved to 0.67rem against the platform's 80% root, so body copy painted at 8.6px. The tokens are inherited from platform-tokens.css again, which restores every ws_* component rendered inside the app at once.
  • Fixed The literals that were scaled to match are back too — 128 var() fallbacks across app-shell.css and dashboard.css, plus 45 bare values in the dashboard and the shell's launcher, hero and onboarding. Borders, outlines, focus offsets and breakpoints are untouched, because they were never scaled down.
  • Fixed The ws_header override block is deleted rather than rewritten. It existed only to restate the header at 0.67 in the literal px the component draws itself in; with the shell at platform scale the header renders at its own 64px, which is the point. The nav, section tag and switcher stay hidden — that is composition, not sizing.
  • Improved The rail returns to its documented 108px with a 56px indicator, 64px destination height, 24dp icon and 12px label — the figures its own comment describes, which the 0.67 pass had left the comment out of step with.
  • Improved The content column returns to 1131px, and the sentence step's 992px override is removed with it: that cap existed to widen the step against a 758px column and would now be pinning it narrower than the page it sits on. The sparkline keeps its 316px cap — that makes a user unit a pixel and is independent of the density question — and its labels go back to 10.5 and 12 units.
August 2026
v2.9.70 — The section hero opens on 48px, not on the content padding
  • Improved .app-hero takes padding-top: 48px, and Strategize's .st-hero takes it inside the shell, so all four sections open on the same gap above the pill. The pill had been sitting on .app-shell__content's 16px alone, which read as the page starting mid-scroll rather than at its top.
  • Note 48 is a literal, not var(--space-12). The shell declares its spacing scale at 0.67, so the token would have rendered 32px; the figure here is the one measured on screen. Any future convergence of .st-hero onto .app-hero inherits it from the one rule.
August 2026
v2.9.69 — The sentence step opts out of the shell's reading measure
  • Improved .app-shell__content widens from 822px to 1056px on the Strategize sentence step, taking the madlib card from 758px to the 992px it renders at standalone (the app's own 1040px page less its 48px gutters). The cap is a reading measure and the sentence is not prose — it is six inline controls read across a line, and at 758px it wrapped mid-clause into short ragged lines. Selected with :has(#stSentencePage) rather than a new body class, so the other four Strategize steps, which are prose and cards, keep the 822px measure untouched.
  • Improved The hero sub on that step takes a 62ch measure — the same figure .app-shell__lede uses — so the copy above the card does not stretch to the card's new width. Widening a container widens everything in it; the measure has to move down to the prose that still wants one.
August 2026
v2.9.68 — The app shell encodes its density instead of relying on browser zoom
  • Improved /app/ renders at 0.67 of the platform scale, stated in CSS rather than reached by zooming out. The shell had been drawn ~1.5× too large and only read correctly at 67% browser zoom. A forced zoom was rejected: it overrides the reader's own zoom instead of composing with it, and media queries keep measuring the unscaled window, so every breakpoint fires at the wrong width. Instead body.app-shell redeclares the spacing, radius, type and icon tokens at 0.67, which carries every var(--space-4, …) in app/css and every ws_* component rendered inside the app; the 229 literal px in those two files are scaled to match. Scoped to the shell's own <body>, so nothing outside /app/ moves.
  • Improved ws_header is scaled to match inside the shell. The component draws itself in literal px in its own <style>, so the token scale cannot reach it and it would otherwise sit a third larger than the frame under it. app-shell.css restates the pieces the shell actually shows — height 64→43px, brand mark and icon buttons 36→24px, the account menu — and skips the nav, tag and switcher, which the shell already hides.
  • Fixed The dashboard sparkline's labels were painting at twice the surrounding text. Its sizes are SVG presentation attributes in user units, so a stretched chart magnifies them: at full column width a 10.5-unit month label rendered at 15.9px beside 8px body copy. The attributes are scaled and the SVG is capped at its own 316-unit viewBox so it never renders above 1:1. This is the one place encoded density and real browser zoom genuinely differ — the grid columns are fr, so they keep their pixel width while their contents shrink, where zoom would have enlarged the CSS viewport instead.
  • Note Breakpoints, border widths and focus rings are deliberately unscaled. Breakpoints map to real device widths and shrinking them would hand a 390px phone the desktop layout; a 0.67px hairline renders as a smudge; the focus ring is an affordance, not decoration. The cost of leaving breakpoints alone is that the shell collapses to its narrow layout slightly earlier than a zoomed browser would, which costs nothing because it collapses to a layout that works.
August 2026
v2.9.67 — The app launcher adopts the coloured modal header
  • Improved The shell rail's active indicator inverts--ws-sidenav-indicator moves from --phase-learn to --phase-learn-light and --ws-sidenav-on-indicator from --phase-learn-light to --phase-learn-dark, so a filled dark red glyph sits on a light red pill. The rail's active label takes the pill's light red too, so both halves of the state read as one thing. Themed entirely through the component's own token API in app-shell.cssws_sidenav itself is untouched, and its default light-rail theme still applies wherever else it renders. Numbers: glyph 4.06:1 on the pill (past the 3:1 for a filled shape), rail label 12.6:1 on ink, up from the 4.01:1 it used to run at. The expanded variant wraps the whole row in the pill, so its 15px label sits at 4.06:1 — under the 4.5 text floor, up from the 3.13:1 this replaced, and recorded in the stylesheet rather than adjusted away.
  • Improved The All applications launcher takes ws_modal's coloured header. Same --phase-learn the Strategist passes to headerColor, same white title, same translucent close. It is bespoke markup rather than a ws_modal — it carries its own scrim, focus handling and rise animation — so this matches the component's treatment rather than replacing the dialog wholesale. Structure follows too: the panel drops its padding and clips, so the band runs full width and the corners cut it, and the scroll moves off the panel onto .app-launcher__groups so a long list travels under a header that stays put, which is the split ws_modal makes with a flex-shrink:0 header over a scrolling body. The panel widens from 880px to 1040px at the same time: at 880 every one of the four phase columns wrapped its descriptions, which made four short lists read as one dense block.
  • Fixed The launcher's close is the platform's close again. It is a ws_icon_button, so it arrived in ghost chrome — grey border, near-white hover fill — which on a red band read as a white box with a white glyph lost inside it. It now matches .ws-modal__close on a coloured header exactly: no border, no fill at rest, a 70% white glyph, and a 10% white wash on hover. The quarter turn is ws_modal's as well, and it turns the glyph rather than the button, so the X becomes a + and the button's own square does not move. Overriding the ghost hover needs :hover:not(:disabled).ws-btn--ghost:hover:not(:disabled) outranks a plain :hover.
August 2026
v2.9.66 — Dashboard rows: stacked, on paper, marked by phase
  • Improved .dash__row is a full-width row again rather than a 199px two-up card. One column of long rectangles gives a "what next" queue a single left edge to scan down; the two-up grid made each entry a destination to weigh against its neighbour. .dash__list is a flex column, the row is one line of icon → body → pill with the pill taking the slack on margin-left:auto, .dash__phases runs horizontally and wraps, and the title is one ellipsised line at 16px. The narrow-screen grid that keeps the icon beside the title is unchanged, but its .dash__cta rule now matches the one-line rule's specificity — an auto margin beats justify-self and had stranded the pill on the far right of its own row.
  • Improved Every dashboard surface moves to --bg-paper from --bg-surface.dash__row, .dash__viz and .dash__empty. The shell already paints paper, so white cards floated a layer above the page; on paper they read as sheets on that desk, with borders and the hover lift doing the separating. ws_task_checklist carries its own white card, which reinstated the white inside the two panels that hold one, so it drops its background, border, radius and padding inside .dash__journey and .dash__firstrun and lets the panel be the card. Scoped to those two containers rather than changed at the component — MyWorkshopr's overview panel uses the checklist standalone and still wants the card, and its demo page is unchanged.
  • Improved .dash__cta reads "Open" on every row, in both the recent and attention lists, replacing "Synthesize it", "Build the agenda" and "Run it". The destination is unchanged — each row still opens on its own unfinished step — but the phase strip beside the button already names where the workshop is, and a button naming it again in different words could only disagree.
  • Improved The row's icon names the phase the workshop has reached — the furthest done in journey order, not its lifecycle stage. Marks are the shell rail's own, so a row and the rail agree: Strategize knight, Plan calendar_view_day, Facilitate co_present, Synthesize lightbulb. Strategize was lightbulb_2 in the rail, a near-twin of Synthesize's bulb; it is the knight in both now, which also picks up the knight-fill.svg active state added in v2.9.64.
  • Fixed Facilitate rows rendered an empty icon tile. co_present is font-only and icon() returns an empty placeholder for it — the same trap ws_button hit in v2.9.65. appDashboardIcon() in app/includes/dashboard.php takes the SVG when there is one and lets the font draw the ligature otherwise.
  • Retired .dash__q is retired and its CSS deleted. The quick action was a tinted borderless pill, deliberately quieter than the hero, sized for a row of three. The row is two now — New workshop and Run a session, after "Pull a rescue card" moved out to the rail where it belongs — and at two they are the only ways into the day the page offers, which makes them calls to action rather than shortcuts past it. Both are ws_button primary, so the dashboard carries no bespoke button CSS at all. .dash__quick stays as the flex row that lays them out; the promotion ledger on design-system/redesign/dashboard.php records the pattern as retired rather than candidate.
August 2026
v2.9.65 — ws_button: an icon name without an SVG no longer renders a gap
  • Fixed ws_button() drew a blank space wherever the icon name had no local asset. The Material Symbols set is far larger than images/icons/, and icon() returns an empty <span> placeholder for anything it cannot find — so co_present, lightbulb_2 and the rest of the font-only names produced a button with a hole where its icon should be. The button now takes the SVG when one exists and lets the font draw the ligature otherwise, which is the same fallback ws_sidenav() has used since v2.9.64. Template: includes/components/button.php; no new classes, no markup change for icons that already resolved.
August 2026
v2.9.64 — ws_sidenav: the active destination's icon fills in
  • New The active item's glyph now renders in Material's filled style, per the M3 rail spec. The local SVG set is outline-only and its cutouts are wound for the default fill rule, so CSS cannot solid-fill it (tried, verified); instead a {name}-fill.svg asset wins when one exists, and everything else falls back to the Material Symbols font glyph with the FILL axis on, which the platform's font request already loads. images/icons/knight-fill.svg ships for the custom knight. Template: $renderActiveIcon in includes/components/sidenav.php; one new class, .ws-sidenav__icon--filled.
August 2026
v2.9.63 — Dashboard patterns, registered before they are components
  • New Redesign › Dashboard Patterns: the promotion ledger for the shell home's vocabulary. The consolidated shell's dashboard ran ahead of the ws_* rule while its patterns were still moving; this page is the decision that they are design-system material. Four candidates — quick-action pill, attention row, hero card, funnel card — each shown live from app/css/dashboard.css, each mapped against the ws_* component it overlaps, with a stated graduation rule: a pattern that earns a second consumer becomes a ws_* helper; one that stays dashboard-only stays a pattern.
  • New The measurements travel with the patterns. Worst text contrast on the dashboard is 5.30:1 (dark red on the tint), the hero's decorative shapes sit at z-index: 0 because ::after otherwise paints over the title, and every font size is stated in px because the platform's 80% root font-size renders the rem type tokens a fifth small.
August 2026
v2.9.62 — The Material 3 navigation rail, in Workshopr's colours
  • New A Redesign section in the design system. The app-shell redesign track: global, shipping components that belong to a direction still being worked out, documented apart from the established set. Same gates as everything else — Workshopr tokens, measured contrast, every state specified, enforcer clean.
  • New ws_sidenav() — the vertical rail an app shell wears down its left edge. A standalone global component: it ships from includes/components/helpers.php like every other ws_* and works on any page, not just the Strategist. Built for the chromeless window, where there is no browser chrome and no horizontal platform nav, so the rail is the only way between apps. Template at includes/components/sidenav.php, styles under .ws-sidenav, documented at Redesign › Side Navigation. It pairs with ws_app_bar(): the rail moves you between apps, the bar acts on the open document.
  • New Follows the Material 3 navigation rail for geometry and interaction — a pill-shaped active indicator, 24dp icons, a 56×32 indicator in the collapsed rail and a 56dp-tall pill when expanded, 60dp destinations, and state layers at 8% / 10% rather than flat hover swatches. Colour is not Material's. Red Unification and platform-tokens.css outrank an imported palette, so M3's roles map onto ours: secondaryContainer--phase-learn-light, onSurfaceVariant--color-ink-secondary.
  • New Two variants. rail (80px, label under the icon, indicator around the icon alone) and expanded (240px, icon beside label, the pill is the item container, inset 20px). Labels show in both, as M3 prescribes; the rail keeps a tooltip on hover and focus because Workshopr's destination names are longer than the short words an M3 rail assumes, and a truncated label must stay recoverable from the keyboard.
  • Fixed Two colour pairings failed AA and were caught by measuring rather than assuming. M3's onSecondaryContainer was first set to --phase-learn-dark, which measures 4.11:1 on the indicator — under the 4.5 floor for text this size. It is now --color-ink at 12.5:1, which is also the more faithful reading of M3, whose light theme puts a near-black tone on the tonal pill. The badge was --phase-learn at 3.82:1 against white; it is now --phase-learn-dark at 5.0:1.
  • New Every state is specified and demoed: rest, hover, focus, pressed, active, disabled, badge, disclosure open/closed, and the rail tooltip. M3 ships no visible focus ring, so the platform's sky-blue one is kept on top of the state layer — a keyboard user needs it. Current-page never rests on colour alone: the indicator is a filled shape, backed by a weight change, aria-current="page", and a forced-colors mapping to Highlight.
  • New images/icons/knight.svg — a chess knight for the Strategize entry, drawn to the house 24px / 0 -960 960 960 convention and checked for legibility down to 20px. The platform's own Strategist glyph is strategy, which has no local SVG; the knight is a deliberate substitution for this nav, not a replacement for it.
  • Fixed A closed disclosure was still rendering its children. .ws-sidenav__sublist { display: flex } is a class selector, so it outranks the user-agent's [hidden] rule and the attribute silently did nothing. Restored with an explicit .ws-sidenav__sublist[hidden] { display: none } — worth remembering for any component that sets display on a element it also toggles with hidden.
  • New Every M3 metric is now a CSS custom property--ws-sidenav-item-height, --ws-sidenav-indicator-w/h, --ws-sidenav-radius, --ws-sidenav-inset, --ws-sidenav-icon-size, the label sizes, the state-layer opacities, and the four colour roles. A shell can retune the rail without forking it, and the defaults remain the M3 spec values.
  • New A playground and a bare preview. The playground drives those properties live and emits only the ones you changed as a copyable override block; it also reports the active and inactive contrast ratios as you pick colours, so a pretty pairing cannot ship unmeasured. The preview renders the rail full-height with no design-system chrome — the context it was actually built for — via ?variant=rail, ?variant=expanded or ?both=1.
  • Fixed The rail clipped its own tooltips and grew a stray horizontal scrollbar. The list was set to overflow-y: auto with a comment claiming overflow-x stayed visible — CSS does not allow that, and computes the second axis to auto as soon as the first scrolls. Only the expanded variant scrolls now; the rail is overflow: visible, since a rail holding more destinations than fit is a content problem rather than something to hide behind a scrollbar.
  • Improved Icon sizing uses --icon-sm/md/lg rather than the type scale — icons are not type, and the tokens already existed for exactly this.
August 2026
v2.9.61 — The first illustrated hero, a rule about the mascot, and a background the model would not match
  • New Strategist gains a hero illustration in the platform house style at /images/hero/strategist_hero.png — a facilitator filling the last blank in her sentence, the unbooked room waiting empty behind the glass. Filed with the rest of the set in images/hero/ under the <app>_hero.png convention. Matches the Facilitator / Pre-Read / Post-Workshop heroes: 1344×768 (16:9), full-bleed monochrome red scene, populated workspace, presented full width under the hero copy at --radius-2xl with the same soft shadow as .prk-hero-image.
  • Improved The house hero spec, written down: ~16:9 (1344×768 dominant, 1024×576 also in use), one warm red hue family over cream, mean saturation 0.33–0.47, full bleed, a real populated room. Measure a candidate against that saturation band before shipping — the image model consistently lands near 0.20, roughly half as saturated as the existing set, and reads washed out beside them.
  • Improved New rule, narrowing DESIGN.md §9: Nano Banana never stands in for the user. The first version of this hero cast the banana as the facilitator making the pitch, and was rejected — "it should never be the banana." §9's character remains right for ebook covers, chapter openers and emotional beats, but where an illustration depicts the person using Workshopr, draw a person. Prompts need an explicit "no fruit, no banana, no animal or food characters" ban or the model reintroduces one.
  • Improved Two prompt lessons worth reusing: raster words must be drawn as abstract rounded bars of varying length to read as prose (uniform pills read as a form or progress bar, and any attempt at real lettering garbles — one draft produced "ru ama oi"); and the palette needs naming as a closed set, since an open brief let amber and gold in beside the salmon.
  • Fixed The image model will not honour an exact background hex. Asked repeatedly for a flat #FAFAF9 ground, it returned a warm cream #FBEDD9 every time — which would have read as a pasted rectangle against --bg-page. Resolved by knocking the background out to alpha with a border-seeded flood fill (contiguous only, tolerance 12), so the character's interior cream highlights and the ivory light wedge survive untouched. The asset now composites onto the page ground with no fringe. Reuse this approach for the next one — do not trust a hex in the prompt.
  • Improved .st-land__hero becomes a two-column grid (minmax(0,1fr) + minmax(0,26rem)); the old max-width moved to the new .st-land__hero-copy. Art is bare on the page — no card, border or shadow — and collapses to one column under 900px.
  • Improved 369 KB → 170 KB via pngquant --quality 70-95; palette PNG keeps full alpha. The illustration carries alt="": it restates the h1, so announcing it would only repeat the headline.
August 2026
v2.9.60 — The modal close twists, and the Strategist joins the component runtime
  • Improved .ws-modal__close now twists its icon a quarter turn on hover — --duration-base / --ease-out micro-motion, guarded under prefers-reduced-motion. Applies to every ws_modal platform-wide.
  • New The Strategist header now loads components.js — it always had components.css but not the runtime, so wsModalOpen/wsModalClose (focus trap, inert background) didn't exist there. Its "Other…" sentence modal is the first consumer, using the headerColor red header per the modal demo.
  • Improved strategist.css polish pass: panel hints and the practice page's reading text sit at --text-body (labels stay small); .st-toolnav retired with its markup; the exit strip steps up to --text-h2 with a --mainsite-dark border; the generated 60-second pitch renders as a --mainsite-very-light card; tool-card CTAs anchor bottom-right; .st-panel__hint--wide lets full-width panels run one line.
August 2026
v2.9.59 — Strategist becomes six pages, and a max-width in the wrong unit
  • New strategist.css gains a marketing block (.st-land) for the new overview page, plus .st-page--tool, .st-subhead, .st-navlink and .st-toolnav for the tool pages. Platform tokens throughout; the only functional color is the unified Learn red.
  • Fixed A max-width: 40ch on the landing hero crushed the whole column to roughly 320px. ch resolves against the element's own font-size, and the hero wrapper inherits body text (~16px) — not the display size its heading renders at. The cap now sits on .st-land__title, where ch measures in the display font and 17ch is the three-line hero it was meant to be.
  • Improved Dead rules retired with the panel-toggle behaviour they served: .st-tool[data-open], .st-viewport[hidden], .st-kit[hidden]. Panels are pages now, so nothing toggles.
  • Improved .st-panel__title is the h1 on a tool page and is sized as one there; the print sheet learned the three new blocks.
August 2026
v2.9.58 — A bare app-shell token collapsed the Facilitator layout in production
  • Fixed The Facilitator live dashboard rendered as one long scrolling column on production while looking correct on localhost. --app-rail-width arrived with the app-shell metrics and was consumed bare — grid-template-columns: var(--app-rail-width) 1fr 300px. A bare var() that resolves to nothing is invalid at computed-value time, so the whole declaration is dropped and grid-template-columns reverts to none; the agenda rail, script pane, and interventions rail then auto-placed into rows instead of columns. Reproduced in Chromium and verified fixed: with the token file blocked entirely the grid still resolves to 320px 804px 300px with all three columns side by side.
  • Fixed The same bare use existed in synthesize.css and css/planner/base/_variables.css, so Synthesize and Planner carried the identical latent collapse. All four uses now carry the , 320px fallback.
  • Improved Why production only: the token reaches these files through @import '/design-system/platform-tokens.css?v=…', and that hand-written stamp was never bumped when the app-shell metrics landed. Production serves CSS with max-age=604800, so a browser can hold a seven-day-old token file under an unchanged URL; the dev server sends no such header. The Facilitator stamp is bumped, but the fallback is what makes the layout independent of it. Pages that stamp with filemtime() instead of a hand-edited date do not have this problem.
August 2026
v2.9.57 — Every component page in the sidebar was a dead link
  • Fixed All 40 component links in the design-system sidebar were dead — and because ds-header.php is included on every design-system page, they were dead from every page, not just one. The nav listed each component as components/<name> while the pages actually live at components/demos/<name>; the router bounced the missing path to /, so clicking any component in the sidebar silently returned you to the homepage. The card grid on component-library.php was unaffected — it already used the demos/ path, which is why the break was easy to miss.
  • Improved The nav slug now matches the real page path for components, as it already did for every other entry. It does double duty as both the link target and the active-state key, so all 47 demo pages had their $ds_page updated in step — verified that each of the 40 still highlights itself in the sidebar.
  • Fixed The Button demo's example linked to /workshops, which does not exist; both the live button and its copyable code sample now point at /library/workshops/.
August 2026
v2.9.56 — Toasts that wait for you, and a truly modal modal
  • Fixed Toast auto-dismiss now pauses while hovered or focused (WCAG 2.2.1 Timing Adjustable) — the remaining time is kept across pauses and resumes when the pointer and focus both leave. Previously a toast with an action button could vanish mid-read or mid-Tab. Covered by tests/toast-a11y.spec.js.
  • Fixed wsModalOpen() now makes the page behind the dialog inert — the focus trap stopped Tab, but screen-reader virtual cursors could still wander the background. Restoration is stack-safe (each modal restores only what it set) and the toast container stays live so toasts fired from modal actions still announce. Covered in tests/modal-focus.spec.js.
  • Improved ws_alert() / ws_banner() now document their HTML-slot contract: $message renders unescaped by design (callers pass <strong>, links) — user-supplied data must be escaped with htmlspecialchars() before passing. The call-site audit found every dynamic caller already does.
July 2026
v2.9.55 — Sixty unnamed buttons, and a ratchet that was counting its own convention
  • Fixed 60 icon-only buttons had no accessible name — a WCAG 4.1.2 Level A failure across 24 files. Every one now carries an aria-label. Worst concentrations: includes/footer/modals.php (11), admin/banners.php and admin/system/banners.php (6 each), facilitator/app.php (6). Note icon() emits a bare <svg> with no <title>, so <?= icon('close') ?> inside a button leaves it unnamed.
  • Fixed check-page.php counted var(--token, #hex) fallbacks as hardcoded hex — the same defect ds-lint.mjs had fixed in 8dcdacfe, so the page ratchet was firing on the codebase's own defensive convention. The rule now strips fallbacks before counting; a chunk carrying both a real hex and a fallback still reports the real one. Baseline re-derived: 5,601 → 4,723 violations across 320 → 311 files, with zero drift absorbed.
  • Fixed The one genuinely alt-less image on the product surface — a JS-built reviewer avatar in includes/ratings-ui.php — now carries alt="" (decorative; the reviewer's name renders beside it). The audit's "94 missing alt" figure was a regex artifact: <img\b[^>]*> truncates at the > inside ?>, hiding any alt that follows a PHP attribute. Real numbers: 1,133 <img>, 40 without alt, 39 of them in ad creative, ebook HTML, or regex literals that merely contain the text <img.
  • Improved All eight TODO: no token match markers cleared from components.css with no visual change: #7F1D1D--toast-bg, #15803D--color-green-700, #B45309--color-warning-darker, plus three comment-only deletions on lines already using tokens. Two were not stale but real token gaps (amber-50 #FFFBEB vs the orange-50 --color-warning-surface; the checkbox-card purple) and are now documented as such.
July 2026
v2.9.54 — The shell arc's missing record, and the review fixes
  • New Retroactive record of the app-shell arc (this entry should have shipped with the branch, per the §15 rule the code review caught): ws_app_bar — the shared document bar under ws_header across Planner, Facilitator, and Synthesize; tokens --app-bar-height, --app-rail-width (320px unified rails), --header-bg, --paper-grid-image; --bg-page moved to #FAFAF9 with the drafting grid; ~575 lines of shell CSS in components.css.
  • New DESIGN.md §12.8 written. synthesize.css and the ws_app_bar docblock cited a §12.8 that didn't exist. It now does: one anatomy, component-owns-the-shell ownership rule, title modes, the single save-state pill, action variants, and the slots-are-for-legacy-JS rule.
  • New tests/header-shell.spec.js — the header migration's missing coverage: seven migrated pages assert exactly one ws_header, zero references to the retired header stack, zero stylesheet 404s; plus shell-token resolution (--app-rail-width = 320px).
  • Improved The identical seven-property interactive transition, repeated verbatim eight times in components.css, is now one definition: --ws-transition-interactive. Byte-identical expansion, so no visual change is possible.
  • Fixed Change-narration comments rewritten as constraints per CLAUDE.md §0 in academy/training/index.php, podcasts/index.php, includes/components/header.php (×2), and platform-tokens.css.
  • Fixed Stale references to the deleted global-header.php removed from the Navigation demo's Files table and corrected in CLAUDE.md §11 Key Files.
  • Fixed Synthesize docs squared with reality: EVAL-BENCHMARK is now the true contract (36 fixtures, new category P row for P-01…P-04 photo/CV traps, High-severity threshold re-based); ROADMAP no longer claims the v2-cv baseline is "pending" (it's committed, from the 16 Jul pre-fix run) and flags that the post-C-01-fix eval run is not yet committed to eval/reports/.
  • Improved synthesize.js: both synthesis success paths now enter results through one enterResults() — the duplicated setState + draft-autosave tail can no longer drift apart.
July 2026
v2.9.53 — The defects the doc pass turned up
  • Fixed Modal focus never entered the dialog. wsTrapFocus() called focus() in the same tick as the .is-open class, but .ws-modal-overlay animates in from visibility: hidden and an unrendered element can't take focus — so the call was a silent no-op and focus stayed on the trigger, outside the trap it had just installed. New wsFocusWhenVisible() waits for the transition. One frame isn't enough: the overlay reports visible at frame 2 while still at opacity: 0.01 and only becomes focusable once painted. Confirmed in headless Chromium, not the preview pane — the pane is itself visibility:hidden and can't measure this.
  • Fixed wsModalClose() now restores focus to the element that opened the modal, guarded on isConnected. Previously focus dropped to <body>.
  • Fixed closable: false now blocks Escape — the template emits data-closable="false" and the document handler honours it. Backdrop dismissal stays governed by backdrop.
  • Fixed wsFilterBarRemove() is now defined in components.js. The filter-bar template had called it since it shipped; nothing implemented it, so every chip remove threw a ReferenceError. Drops one query param, resets page, preserves the rest of the query string and the hash.
  • Fixed ws_progress omits aria-valuenow when indeterminate, per the ARIA spec — it was announcing a definite percentage for work of unknown length. Both progress animations now honour prefers-reduced-motion, matching ws-skeleton.
  • Fixed aria-atomic moved from the toast container onto each toast. On the container it told assistive tech to re-read the whole stack whenever one arrived.
  • Fixed ws_prompt_select renders a real <label for> instead of a <span> — the select had no accessible name, and clicking the prompt did nothing.
  • Fixed ws_chip_input's add button falls back to an aria-label when addText is omitted, so the icon-only form isn't an unnamed button.
  • New tests/modal-focus.spec.js covers all three modal fixes. Note strategist/tests/sections.spec.js is separately broken — it requires js/modules/sections.js, retired in v2.9.51 — so a bare npx playwright test fails before reaching anything else.
  • Improved Corrected a claim in the previous entry's doc pass: ws_chip_input's label is properly bound via for. The unbound-label finding stands for Form Row, Prompt Select, and the two card groups.
July 2026
v2.9.52 — Every component page, one structure
  • Improved All 47 pages in components/demos/ rebuilt to the section order in templates/README.md: When to Use · Variants · States · Real-World Usage · Options · Accessibility · Tokens · CSS Classes · Files. Adoption was 0/47 before this pass — the template existed, nothing followed it.
  • New Accessibility and Tokens sections on every component. Accessibility was present on 5 pages; Tokens on none. Each is written from the template and CSS source, not assumed — including the gaps.
  • Fixed Input documented icon, iconPosition, state, and helper — none are read by input.php. Its select example also passed flat value => label pairs, throwing Cannot access offset of type string on string; ws_input(type: 'select') wants a list of arrays, unlike ws_select().
  • Fixed Navigation described a data-property attribute and .property-switcher-* / .ps-* classes that exist nowhere in header.php. Rewritten against ws_header(?string $section, array $opts), the 17 section keys, and the real .wsh- prefix.
  • Fixed Accordion and Tabs documented arrow-key, Home/End, and roving-tabindex navigation; both JS inits bind click only. Modal claimed focus return to trigger and an inert background; neither is implemented. All now recorded as known gaps.
  • Fixed Filter Bar chips call wsFilterBarRemove(), which is defined nowhere in the codebase — every remove button throws a ReferenceError. Documented, with a working implementation in the usage example.
  • Fixed Unbound labels recorded on Form Row, Prompt Select, Chip Input, Radio Card Group, and Checkbox Card Group — each renders a visible label with no for, fieldset/legend, or aria-labelledby, leaving the control unnamed.
  • Fixed Retired phase hexes removed from Level Up Grid (#0284C7, #7C4DFF, #10B981), Navigation (plus #F87171, #BE123C), Button's var() fallbacks, and Planner Inspector Rail's theming advice. Every phase token resolves to #E54D4D.
  • Fixed Emitted-but-unstyled classes flagged where found: .ws-badge__text, .ws-avatar--initials, .ws-tabs__label, and Input's orphaned .ws-input-wrap__icon--* rules.
  • New Undocumented options surfaced: showHeader and scoreBadge.variant on Energy Arc Card, group_by_phase and phase_copy on Level Up Grid, meta on Planner Card, and the phase variants on Banner. Energy Arc Card's theme default corrected from dark to light.
July 2026
v2.9.51 — Strategist madlib rebuild
  • New strategist.css rewritten for the madlib screen: hero on the Display Text Pattern and a display-scale six-dropdown sentence own the first viewport, with three tool cards + full-width panels and a soft very-light-red exit bar below the fold. Platform tokens throughout; the page sits on the standard paper-and-grid background.
  • New .st-blank is a documented strategist-local control (like st-tabs was): an inline sentence blank that ws_input/ws_select can't express — they render labeled block fields. Baselined in check-page; if a second app ever needs inline blanks, promote it to a ws_* component.
  • Fixed Retired the three-phase shell CSS (docbar, phase tabs, steplist, wizard nav, readiness meter, rail). The st-brief/st-doc document blocks stay — brief-view.php renders with them.
July 2026
v2.9.50 — The icon-ligature sweep, finished (and a guard so it stays finished)
  • Fixed The remaining ligature-named controls in Synthesize: theme disclosure chevrons and theme overflow menus, which announced as chevron_right and more_vert — 22 of the 58 controls on the results screen, all sharing two names. New disclosureAttrs(what, open) emits the label and aria-expanded together, so a toggle can't ship a name without its state.
  • Improved A count rendered in its own span concatenates into the button's name — <span>Themes</span><span>11</span> is announced "Themes11". Where a label and a badge sit in one control, give the control an explicit name with the separator restored.
  • New Regression guard in synthesize.spec.js: it sweeps every visible control on the results screen and fails if any accessible name is a bare snake_case token. Catches new icon-only controls without needing a test per button.
  • Improved Sweep result across the input, review and results screens: 162 controls, none named by an icon. The rule this leaves behind — when a control's only content is icon(), it needs an explicit name; the ligature is not one.
July 2026
v2.9.49 — Icon ligatures are not labels; disabled controls owe a reason
  • Fixed icon() emits the Material Symbols ligature as real text, so any button whose only content is icon('close') announces as "close" — the wrong verb for a destructive control, and identical on every row of a list. New removeLabel(kind, text) in synthesize.js gives each of the seven per-row remove buttons a name carrying its own row's text, trimmed to 60 chars because these are read aloud in sequence.
  • Improved Deliberately not fixed by marking every icon aria-hidden: plenty of controls here carry no other text, so hiding the ligature would leave them nameless. Label the control, don't silence the icon.
  • Improved A disabled control owes the reason it is disabled, in the place people look. Synthesize's Start button has two blocking conditions — too much material already explained itself in an alert; an empty tray explained nothing, and the adjacent hint talked about saving drafts, so the greyed button read as broken. The hint is now conditional and wired via aria-describedby.
  • Improved Placeholder names should look unfinished. "Workshop synthesis" reads like a real title, so a library of them was unsortable; the fallback now borrows the first source's name.
July 2026
v2.9.48 — Synthesize overlays become real dialogs
  • Improved overlay() in synthesize.js now emits role="dialog" + aria-modal="true" on .synth-modal — the panel, not the backdrop, since the backdrop is the click-to-dismiss surface. One helper, so every Synthesize overlay gained it at once.
  • New wireOverlayA11y() names each dialog from its own heading via aria-labelledby rather than a hand-maintained name→label map, and pulls focus in. Two traps worth knowing: several overlays lead with a hidden file input, which silently refuses focus — pick the first visible field and verify focus landed, or the dialog keeps focus on <body>. And because render() replaces root.innerHTML wholesale, focus must be restored on every render behind an open overlay, not just on open.
  • Improved Escape closes the top overlay, with no exemption for focused fields. The usual "let the input's own Escape win" rule backfires here: focus lands in a field on open, so exempting fields meant Escape never closed the dialogs that have one — which is most of them.
July 2026
v2.9.47 — Planner: name the keyboard model, and the recovery route per audience
  • New .toast-undo-hint in css/planner/components/_modals.css — the keyboard shortcut shown beside the toast's Undo button, quieter than the button so it reads as a hint rather than a competing action. Paired with an .sr-only spoken form, since ⌘Z does not read well aloud.
  • Improved A toast action button is a mouse affordance. It mounts at the end of the document, focus is elsewhere, and it auto-dismisses — so a keyboard user cannot realistically reach it. When a toast offers recovery, state the shortcut too.
  • Improved #agendaItems now carries role="list" + aria-label, and each item an aria-describedby naming the keyboard model. The description is deliberately one sentence: it is read on every focus, so the fuller wording lives in the visible Instructions panel instead.
July 2026
v2.9.46 — ws_header: a nav href's fragment is part of its identity
  • Fixed ws_header() compared nav items on path alone, via parse_url(..., PHP_URL_PATH), which discards the fragment. Nine sections build anchor navs from absolute paths — /synthesize/, /synthesize/#how, /synthesize/#app — so all three collapsed to /synthesize and an activeHref of /synthesize/ matched every one, lighting the whole nav. Matching now uses path + fragment.
  • Improved Fragment items are excluded from path matching outright: the server never receives the anchor, so it cannot know which one the reader is on. Dropping them also leaves the fragment-less sibling as the unique match, where the group previously cancelled out as ambiguous and nothing highlighted.
  • Improved Bare #anchor hrefs still resolve to "" and never match — that is why sections written that way (/sidekick/) were already correct, and why the bug looked inconsistent across the platform.
  • Fixed activeHref must name the nav item's href, not the current page's URL. Both Facilitator session pages passed /facilitator/app.php, which appears nowhere in that nav, so they highlighted nothing despite a comment saying "highlight Run Session here".
July 2026
v2.9.45 — ws_header: aria-current, and 'active' means CTA not location
  • New ws_header() emits aria-current="page" on the nav item matching the current path or the page's activeHref. The red highlight was visual-only, so "you are here" never reached assistive tech.
  • Fixed Only one item can ever carry it. Anchor navs whose hrefs all resolve to a single path (/synthesize/, /synthesize/#how) light several items at once; the highlight tolerates that, the accessibility tree does not.
  • Improved The registry's 'active' => true flag paints a section's call to action red — it is not a location marker, and aria-current deliberately does not follow it. Setting it on a plain destination lights two items with two different meanings, which is what happened on templates.php. Use activeHref for location.
  • Improved The planner section's default nav is now Planner / Templates / Saved Agendas, matching the four planner surfaces. It previously said "Launchpad", a name retired when that page became a 301 to brief/.
July 2026
v2.9.44 — Planner: named the Energy score, retired the mixed badge row
  • New .energy-arc__score-label in css/planner/components/_canvas.css — a quiet caption naming the score pill, which otherwise rendered as a bare number discoverable only on hover. Carries aria-hidden, since the button's own aria-label already names the score; without it screen readers hear "Energy" twice.
  • Improved .lp2-option__badge now appears once per row, on the featured card only. Three badges in the same slot encoded three unrelated axes — a recommendation, a method, and a price — so the row read as one comparison it wasn't. One slot, one meaning.
  • Improved Quoted library counts must come from workshopr_count_label() (includes/library-counts.php), never a literal. Four planner pages had drifted to 300+/380+ against a 440-activity library; the helper's docblock already claimed to be the single source of truth, but the call sites had not adopted it.
July 2026
v2.9.43 — Planner: undo control in the toast, named dialog close buttons
  • New .toast-undo-btn in css/planner/components/_modals.css — the Undo control inside the agenda delete toast. Inherits the toast's white-on-blue (~8.6:1), and uses display: inline-flex so min-height/min-width: 24px actually applies; on a plain inline button the min-height is ignored and the target lands under the WCAG 2.5.8 floor.
  • Improved modal-planner.php now emits aria-label="Close {title}" and type="button" on .red-header-modal-close. One template, so all 15 planner dialogs gained a named close button; they announced as bare "button" before. The overlay already carried role="dialog"/aria-modal.
  • Improved Client-side field errors should reuse ws_input()'s own error markup — .ws-field--error, .ws-input--error, aria-invalid, and <p class="ws-field__error" role="alert">. setFieldError() in planner-home.js mirrors it exactly rather than inventing a second error style.
July 2026
v2.9.42 — Synthesize modals: entry animation belongs to opening
  • Fixed .synth-overlay's synth-fade and .synth-modal's synth-pop restarted on every state change behind an open modal, because Synthesize re-renders by replacing the whole of root.innerHTML — the modal visibly flashed and reopened. New .synth-overlay--settled sets animation: none on an overlay that was already on screen when the render started (snapshotOverlays() in synthesize.js), so the animation only plays on the render that opened it.
  • Improved Worth copying wherever a string-rendered surface re-mounts animated chrome. .synth .menu and the AI log (which already carries a comment about this) have the same exposure.
July 2026
v2.9.41 — Synthesize frame: full-height rail, flush surface
  • Improved .sy-rail drops its border and radius and stretches to the full height of the frame (.sy-app moves from align-items: start to stretch). It is no longer sticky: a stretched element has nothing to stick to, so keeping the rail fixed under a long board would mean moving it outside .synth-stage.
  • Fixed Full height needed a definite-height ancestor chain. A percentage min-height resolves against nothing when every ancestor is height: auto, so .synth-stage .synth is now a flex column and .wrap--app / .sy-app pass height down with flex: 1.
  • Improved .wrap--app loses its top padding and trims the inherited 110px bottom pad to --space-6; .sy-main goes to padding: var(--space-6) 0 so content runs the full width of the surface.
  • Fixed The zero top padding has to be restated inside @media (max-width: 640px), where .synth .wrap's padding shorthand resets it at equal specificity.
  • Improved .prio-tray moved below the matrix. Noted as a watch item rather than a clean win: the 2×2 is 543px tall, so a tray of several rows can fall below the fold on a 900px viewport.
July 2026
v2.9.40 — Synthesize adopts the shared components
  • Improved Synthesize's private re-implementations of button, tab, badge, pill, alert, empty state and skeleton are deleted; the app emits the platform class names instead. components.css was already on every page via includes/header.php — the components were present all along, just unused.
  • New wsAlert, wsEmpty, wsSkel, wsBadge, wsPill in synthesize.js, beside the existing icon(). They mirror the markup includes/components/*.php produces, because the PHP helpers can't be called from a client-rendered body. Keep them in step if those templates change.
  • Fixed The .tab collision is gone rather than worked around. Synthesize needed an explicit reset of the global pill rule's radius, shadow, shimmer ::before and hover lift — same specificity, decided only by source order. Adopting .ws-tabs--underline removed the reason for the reset.
  • Improved Two loading messages were wearing .empty. They are transient status, not empty states, and now use .load-hint.
  • Improved What stayed local is now only what carries data-driven colour: .pri-badge and .cat-pill set a background inline from app state, so the rules that remain do nothing but keep the text legible on top of it.
  • Fixed .ws-skeleton sets no display, so it must be emitted as a <div> — the <span> the old skeleton used would collapse to nothing.
July 2026
v2.9.39 — Synthesize app guide + component gallery
  • New design-system/app-synthesize.php — the fourteenth App Guide, and the first to double as a component gallery. Synthesize renders every screen from JavaScript template strings, so it has no ws_* helpers to demo; the page renders its class names directly against synthesize/synthesize.css, which means editing that stylesheet moves the specimens with the app.
  • New Forty specimens across nine groups, each shown in its real states — empty, loading, done, muted, error, and the degenerate cases (no notes, no owner, an overlong source name). Specimens are wrapped in .synth, since the whole stylesheet is scoped under it.
  • New A “Swap Candidates” section pairing each duplicated element with its platform equivalent: .btn.ws-btn, .badge/.pill.ws-badge/.ws-pill, .empty.ws-empty-state, .guardrail.ws-alert--warning, .sy-skel.ws-skeleton.
  • Improved ds-header.php gains a Synthesize entry under App Guides. Strategist is still missing from that nav — noted, not fixed here.
  • Fixed Recorded the one genuine platform gap the gallery surfaced: there is no shared segmented control, and Synthesize, Planner, and Facilitator each ship their own .seg. Flagged as the strongest candidate for a real ws_segmented.
July 2026
v2.9.38 — Synthesize app frame: rail, working surface, AI panel
  • New .sy-app replaces .sy-work as the shell every Synthesize working screen wears: var(--app-rail-width) rail + minmax(0, 1fr) surface, widening to a third 340px column for the AI panel above 1440px. .sy-work__main is now .sy-main and keeps the drafting grid on var(--bg-page).
  • New .sy-ai — a panel that is a grid column on wide screens, a fixed right-edge sheet below 1440px, and a bottom sheet below 1080px. The sheet's top comes from --synth-stage-top, set once per open, so a fixed element can sit under chrome it can't measure.
  • New .sy-tools / .sy-tool — a 38px icon rail with a hover tooltip, replacing the spelled-out .sy-rail--add column.
  • Fixed .synth .tab now resets the global .tab rule's pill radius, shadow, shimmer ::before, and hover lift. Same specificity, so only source order was keeping them apart — Synthesize's tabs are an underline row and now say so explicitly.
  • Improved Retired .uni-drop, .act-table, and .dec-row in favour of .sy-drop, .act-card, and .dec-card — the card forms survive a narrow column, the table did not.
July 2026
v2.9.37 — Synthesize work surface on the page paper
  • Improved .sy-work__main keeps the drafting grid but takes var(--bg-page) (#FAFAF9) instead of the warmer var(--bg-paper), so the centre column and the page behind it read as one sheet rather than two paper stocks.
  • New .sy-rail--add is now a panel on var(--bg-paper-dark) (#F0EBE3) — the same wash the global header wears.
  • Improved Both rails (.sy-rail) picked up the centre column's panel geometry — 1px solid var(--border), --rCard radius, --space-5 padding — so the work screen reads as three panels of one family. Fill is the only thing that distinguishes them: grid paper in the middle, the darker wash on the controls, nothing behind the sources list.
July 2026
v2.9.36 — Planner progress-header buttons on the red token
  • Improved .progress-header-btn (the History / Clean up / AI / Share / Export / Schedule / Delete cluster on the planner's agenda bar) takes its background from var(--planner-very-light) instead of a hardcoded rgba(229, 77, 77, 0.08). Same red family, now a solid #FEE2E2 that matches the soft fills used elsewhere on the platform.
  • Improved Shared ?v= stamp on every planner @import bumped to 20260726a, per the rule in planner-new.css — the imports are cached for 7 days and the entry file's own stamp does not invalidate them.
July 2026
v2.9.35 — Global header background token
  • New --header-bg: #F0EBE3 in platform-tokens.css (mirrored as backgrounds.header in the JSON). The global header now sits a step deeper than the #FEF7F1 page cream, so the chrome reads as its own band instead of bleeding into content. Same value the apps already carry as --color-paper-dark.
  • Improved ws_header() takes the colour from the token in both places it set the old hex — the .wsh-header rule and the inline style attribute the component emits (the inline one wins, so they have to move together). The bg option still overrides per call.
July 2026
v2.9.34 — ws_skeleton workshop-card variant + reduced-motion guard
  • New ws_skeleton(['variant' => 'workshop', 'count' => 3]) — a placeholder that mirrors the saved-workshop card (name, meta row, lifecycle strip, footer action) at the same 12px radius / 18px padding / 12px stack gap the real card uses. count emits sibling cards so it drops straight into an app's own grid. Adopted on planner/planner-home.php, facilitator/facilitator-home.php, and synthesize/synthesize-home.php, replacing three "Loading your workshops…" text lines. New .ws-skeleton-grid helper for pages whose skeleton isn't already inside an app grid.
  • Improved The skeleton shimmer now stops under prefers-reduced-motion: reduce (the placeholder stays visible, holding its "content is coming" status) — an infinite loop is decorative motion per DESIGN.md §7.
July 2026
v2.9.33 — ws_app_bar, one document bar for every tool shell
  • New ws_app_bar() — the bar every app shell wears under ws_header. One anatomy: app label or document title, contextual controls, save state, action cluster. The component owns the shell (60px min-height, red gradient, Fraunces title, 36px action geometry, tooltips); apps pass only their own markup through the leadSlot, center, trailLead, and trailSlot slots. Template in includes/components/app-bar.php, CSS in includes/components/components.css, registered in workshopr-design-system.json.
  • Improved Adopted by all three tool shells, replacing .planner-topbar, .synth-topbar, and .fac-app-bar. Every id and class the apps' JS binds to was preserved; the superseded layout, title, action, and tooltip rules were deleted from the three app stylesheets rather than left to fight the component on cascade order.
  • Fixed Removed the dead $header_variant = 'app' / $header_app_title / $header_app_actions block from facilitator/app.php — an unfinished app-mode header nothing ever read.
July 2026
v2.9.32 — Workshop Record architecture note linked from Decisions
  • New The Design Decisions page gained a Workshop Record section linking the architecture note (docs/workshop-record.md) and the self-contained, theme-aware diagram (docs/workshop-record-diagram.html). Explains the plan spine, the derived Designed → Delivered → Synthesized lifecycle that ws_lifecycle renders, and the federated read model — so the component's stages have a documented source of truth.
July 2026
v2.9.31 — ws_lifecycle workshop journey strip
  • New ws_lifecycle() component — a three-step workshop journey strip (Designed → Delivered → Synthesized). Options: status (designed|delivered|synthesized), variant (compact for card badges, full with an optional next-step cta). Template in includes/components/lifecycle.php, CSS in includes/components/components.css, demo at design-system/components/demos/lifecycle.php, registered in workshopr-design-system.json. Red Unification compliant: completed steps use the very-light red fill with full-strength border, pending steps are neutral outline — state is expressed by fill/weight, never by additional hues.
  • New Adopted on MyWorkshopr workshop cards and the Planner's saved-workshops list, hydrated client-side from GET /api/workshop-record.php?list=1 (lifecycle derived server-side; strips are absent, not broken, when the call fails).
July 2026
v2.9.30 — Facilitator start-agenda adopts the Planner card
  • New .fac-skeleton shimmer primitive (transform-only animation, reduced-motion-safe) plus per-part skeleton layouts — .picker-skeleton, .fac-sidebar-skeleton, .start-agenda-skeleton__*, .start-stat-skeleton, .fac-script-skeleton, .fac-panel-skeleton, and generic .fac-skel-line width helpers. Emitted by the shared FacilitatorSkeletons JS builders so the live app and the /facilitator/loading-states gallery render identical markup.
  • Fixed .fac-live__body gained grid-template-rows: 1fr + min-height: 0 (and min-height: 0 on the three columns) so the left/right columns stretch full height instead of collapsing to a content-sized row.
  • Improved facilitator/css/facilitator.css — the Session Start review list (#start-agenda) now reuses the Planner's .agenda-item card anatomy (type-coloured .item-icon square, .item-time, .item-title, .item-duration-badge) plus the shared ws_activity_badge. Class names mirror css/planner/components/_cards.css but the rules are scoped under #start-agenda so they never leak into the rest of the facilitator. Read-only: none of the Planner card's editing chrome (drag/delete/swap/edit) is carried over.
  • New Facilitator type tint tokens --fac-type-{exercise,icebreaker,break}-very-light (#EFF6FF / #ECFDF5 / --gray-50, mirroring the Planner's --planner-*-verylight). Applied as per-type card shading on #start-agenda .agenda-item[data-type] — very-light tinted background + full-strength type-colour border, matching the Planner canvas cards.
  • Fixed .start-cta__btn--compact no longer shrinks the button — the agenda-header "Start Session" now matches the hero CTA's size (the variant only keeps its header placement, not a smaller footprint). .start-cta is now text-align: right so the hero button shares the agenda's right edge with the header button.
  • Improved --fac-hero-gradient retinted from maroon (#3D0C0C→#6B1717) to an ink gradient (var(--color-ink)→var(--color-ink-secondary), #2C2416→#5C5242) for the start screen's dark hero banner; --fac-hero-dark (live dashboard timer bar) likewise retinted from #3D0C0C to var(--color-ink). .fac-nav (live dashboard bottom bar) background changed from --bg-surface to --fac-ink-faint, with its tool buttons / note button / position / shortcuts text set to --fac-ink for contrast. Added the missing --fac-ink-faint token (var(--color-ink-faint), #A8A29E) to the facilitator ink scale. .fac-nav__tool-btn (Polls/Reactions) and .fac-nav__note-btn (Quick Note) now inherit the .fac-nav__btn (Previous) treatment — white --bg-surface pill, solid --gray-300 border, --radius-lg, --text-meta — so all nav buttons match. Free-floating bar text (.fac-nav__shortcuts, .fac-nav__position) set to white; the pill buttons keep ink labels since their backgrounds are white. Added .fac-nav__btn:not(.fac-nav__btn--primary):hover — the Previous button now hovers to the same red-accent state (--fac-purple border/text, --fac-purple-very-light fill) as the other nav pills; the primary Next button keeps its own red-fill hover.
  • Improved .fac-main (live dashboard script panel) now paints the shared graph-paper grid (ink @ 6%, 24px cells over --bg-page) from css/base/_backgrounds.css, instead of a flat --bg-page fill.
July 2026
v2.9.29 — Agenda Health Check app styles
  • New agenda-check/agenda-check.css — a deliberately thin app-shell sheet (.ac-*) covering only the topbar/shell layout, input screen, dropzone, analyzing spinner, and sign-in gate wrapper. The report itself reuses the already-loaded components.css, _agenda-generative.css, and the .energy-arc-* card from prep.css — no new CSS for the grade card, dimension bars, recommendation rows, or badges.
  • New .ac-grade__letter — an additive Fraunces letter-grade flourish layered on the reused energy-arc card, with a one-time fade/settle entrance (--duration-reveal-lg + --ease-reveal) gated behind prefers-reduced-motion.
July 2026
v2.9.28 — Icon set: calendar_view_day + right_panel_open
  • New Added calendar_view_day.svg and right_panel_open.svg to images/icons/ (official Material Symbols outlines, fill="currentColor") — the icon() helper renders an empty span for missing files, which had left the Strategist doc-bar's rail toggle blank.
  • Improved Strategist's "Open in Planner" CTA is now a ws_icon_button (primary, md) with a CSS data-tooltip + aria-label instead of a labeled ws_button.
July 2026
v2.9.27 — Strategist joins the global header
  • New ws_header gained a strategist section (Plan phase, strategy icon, "Strategist" tag) with Strategist / Plan Agenda nav, and a matching app-switcher entry under Plan.
  • Improved The Strategist page now renders ws_header('strategist') above its locked shell; the shell height accounts for the header (calc(100dvh - 65px)) so the window still never scrolls, and the header joins the app chrome hidden in print.
July 2026
v2.9.26 — Strategist app-shell rework
  • New The Strategist shell is now locked to 100dvh with a grid-paper canvas: a doc-bar (editable workshop name, readiness pill + breakdown popover, save chip, Planner CTA) pins above an always-visible phase journey, with three independently scrolling bands beneath — no window scroll at any width.
  • New Every phase (Buy-In, Scope, Details) now drives a master–detail step list from StSections beside a single-section editor, with tick marks for complete/touched sections replacing the old inline stepper.
  • New The brief rail became a tabbed collapsible panel (.st-rail__tabs — Brief / Documents) that switches to a fixed off-canvas drawer below 1100px, with roving-tabindex keyboard support (arrows/Home/End) on the tab pair.
  • New First-run guidance is now an in-shell start card rather than an overlay.
  • Improved Below 760px the step list becomes a horizontal, scrollable chip row instead of disappearing off-screen.
  • Fixed Swept the dead .st-wizard* rule family (stepper dots, footer, print hide-list entries) left over from the pre-rework layout, plus the inert .st-wizard__next--last class toggle in strategist.js — neither had any surviving markup or styling effect.
July 2026
v2.9.25 — Synthesize on the platform motion system
  • Improved Synthesize micro-motion now pulls from the shared tokens: buttons, icon buttons, action cards, add buttons, and dropzones transition with --duration-fast/base + --ease-out; modals enter with --duration-enter + --ease-spring; menus pop with --duration-fast/base. Reduced-motion zeroing comes free via the token overrides.
  • New The Synthesize landing hero uses the page-reveal regime (--ease-reveal, --duration-reveal(-lg)): title → description → CTAs cascade on load, transform+opacity only, with an animation-duration kill added to the landing's reduced-motion block.
  • New .uni-drop variant of the Synthesize dropzone — a slim horizontal drop-anything target on the input screen (token-spaced, tscale-aware).
July 2026
v2.9.24 — Synthesize landing + app fully tokenized; Red Unification comments corrected
  • Fixed The Synthesize landing (.sy-land) palette now aliases platform tokens (--phase-learn family, --color-ink(-secondary), --bg-page/surface) instead of duplicating hex values; stale "red is a deliberate exception, like Intervention" comments in synthesize/index.php and synthesize.css rewritten to reflect Red Unification.
  • Fixed Remaining off-scale values in synthesize.css moved onto tokens: shadow/radius vars alias --shadow-sm / --radius-sm/xl/2xl, off-scale 7/28/36px spacing snapped to the 4px scale, the roadmap goal-card selection ring uses --accentVL, and the roadmap detail modal's red side-tab border became a quiet bordered wash.
  • Fixed Kanban card titles reference var(--font-heading) instead of a hardcoded font-family. All three Synthesize files now pass the enforcer with zero violations.
July 2026
v2.9.23 — Footer consolidation: ws_footer(), tokenized canon, forks retired
  • New ws_footer() wraps the global footer composite (Level Up grid · Letter CTA · brand bar) and is registered in workshopr-design-system.json; the composite gained a render-once guard so any include combination yields one footer. Documented in DESIGN.md §12.7 with the footer-less app-shell exception (Planner, Strategist) and the BEM naming rule.
  • Fixed The canonical footer's 18 enforcer violations: hardcoded hexes replaced with --bg-paper, --phase-learn(-light), --bg-muted, --color-ink(-muted), --text-inverse, --bg-surface, and --mainsite-gradient; the raw Subscribe button is now ws_button().
  • Fixed Footer legal line contrast: rgba(255,255,255,.4).55 (3.67:1 → ≈5.8:1, AA pass) in both the composite and the legacy stylesheet.
  • Improved includes/footer.php now renders the composite via ws_footer(), putting all legacy-footer pages on-canon; the survey fossil footers (3 files) and the Facilitator Bootcamp footer fork were retired.
July 2026
v2.9.22 — Critique fixes: navy aliases retired, toast side-stripes removed, mobile nav
  • Fixed Red Unification: the legacy --primary-navy and --uh-color-navy aliases now resolve to warm ink (--color-ink), retiring cold navy headings platform-wide; library category chips render brand red instead of raw DB Material hexes; the header's per-phase active-link colors and the purple BETA pill collapsed to the red family.
  • Fixed Toast variants and the facilitator callout dropped the banned side-stripe accent for full light borders on semantic surface tokens.
  • New Both global headers (gn-shell, wsh-header) gained a mobile menu sheet (hamburger toggle, scroll lock, Escape/scrim dismiss, reduced-motion aware) replacing the nav that was display:none below their breakpoints.
  • New includes/library-counts.php: canonical library counts with floors — the single source for every "N+ exercises" claim.
July 2026
v2.9.21 — Strategist app CSS brought fully on-system
  • Fixed strategist/css/strategist.css referenced text-size tokens that don't exist (--text-sm, --text-lg, --text-2xl…), so its font sizing silently inherited. Rebuilt on the real scale (--text-h2/h3, --text-ui/small/meta/caption/micro) during the document-centric redesign.
  • Fixed Strategist focus styles used red outlines; now the platform --focus-ring (sky-blue), the sole sanctioned non-red accent.
  • Improved Strategist status/verdict surfaces now use the semantic surface tokens (--color-warning-surface, --color-success-surface) with text labels, per the color-has-meaning rule.
July 2026
v2.9.20 — Motion cleanup: retire the bounce curve, add press feedback, scope transitions
  • Fixed Purged the banned overshoot curve cubic-bezier(0.34, 1.56, 0.64, 1) (removed July 2026) from circulation: neutralized --transition-bounce in css/tokens/_variables.css, corrected per-app --ease-spring overrides (Tips, Podcasts, Academy, Tips v2) that had subverted the sanctioned token, and replaced the raw literals in css/styles.css, both _modals.css sheets, participant.css, and two planner pages with var(--ease-spring).
  • Improved ws_button now has a :active press scale on the base class (covers all variants); global header icon buttons gained a press scale + transition.
  • Improved Removed transition: all from the shared component library and the planner card/sidebar/modal sheets (~71 rules), scoping each to transform/opacity/color/shadow so state changes composite on the GPU.
July 2026
v2.9.19 — Fix footer overriding secondary button borders
  • Fixed Scoped the newsletter subscribe-button styles in includes/components/footer.php to .cta. An unscoped .ws-btn { border: none } was loading after components.css and stripping the red border from every .ws-btn--secondary on any page that renders the footer.
July 2026
v2.9.18 — Red Unification: retire "plan blue"
  • Fixed Repointed the legacy --planner-blue / --planner-blue-dark / --planner-blue-light aliases in platform-tokens.css to the Learn Red family, so surfaces that load only the platform tokens (e.g. planner/index.php) no longer render the retired plan-blue.
  • Fixed Cleared leftover sky-blue / violet accents in css/planner/launchpad.css, css/planner/briefing.css, and planner/email-preview.php that had only been half-converted to red.
July 2026
v2.9.17 — iOS Companion app added to the design system
  • New Added app-companion.php, the per-app guide for the Workshopr Companion iOS app (React Native / Expo) — mobile palette with swatches, type scale, an iOS component inventory grouped by primitives / Run Mode / Capture / Cards, and an iOS patterns section covering safe areas, tactile press, Dynamic Type, and the "one home, two escapes" navigation model.
  • New Added a companion entry to apps in workshopr-design-system.json, mirroring the shape of the existing app entries plus a platform note flagging it as a standalone native codebase with no CSS token.
  • New Added mobile-ios.md, a concise reference mirroring the tone of theming.md/motion-system.md, pointing to workshopr-companion/DESIGN.md and src/theme/ as the code source of truth.
July 2026
v2.9.16 — Strategist form-field borders
  • Improved Scoped a darker resting border on strategist form fields in strategist/css/strategist.css: .st-app .ws-input / .ws-select move from the platform-default --gray-200 to --gray-400, with --gray-500 on hover. Scoped to .st-app so the shared ws-input/ws-select components are unchanged platform-wide.
July 2026
v2.9.15 — Strategist Buy-In wizard
  • New Added .st-wizard stepper nav and .st-wizard__footer Back/Next bar to strategist/css/strategist.css, splitting the Buy-In phase's five .st-section blocks (in strategist/includes/phase-buyin.php) into wizard steps via .st-step / data-st-buyin-step. Active step uses --phase-learn-dark text on --phase-learn-very-light chip fill; a touched-but-inactive step gets a quieter --gray-200 chip. Step transitions are a 200ms fade+4px rise (.st-step--entering), disabled under prefers-reduced-motion. Labels hide under 860px, numbers remain. Print stylesheet forces [data-st-buyin-step] visible and hides the wizard chrome, matching the existing hidden-phase-panel print pattern.
July 2026
v2.9.14 — Strategist generated documents section
  • New Added .st-sidebar__section / .st-sidebar__heading card and .st-docs button stack to strategist/css/strategist.css, hosting the new Documents section (Executive Summary, Facilitator Briefing, Risk Report, Agenda Blueprint) in strategist/includes/sidebar.php after the Workshop Brief preview. Generated output reuses the existing .st-script / .st-skeleton patterns from the Objection Coach; the section is hidden from print via the existing print stylesheet.
July 2026
v2.9.13 — Strategist recommendation banner
  • New Added .st-reco go/caution/not-yet recommendation banner to strategist/css/strategist.css, hosted at #stRecommendation in strategist/includes/sidebar.php directly above the readiness meter. Status is conveyed by a text label plus a bordered card (--color-success / --color-warning / --gray-200), never by color alone, per Red Unification.
July 2026
v2.9.12 — Global footer Letter CTA form restyle
  • Improved Restyled .cta__input in includes/components/footer.php: solid var(--bg-surface) fields with ink text and var(--shadow-xs) replace the translucent white-on-brown inputs, and field focus now uses the platform sky-blue ring (--focus-ring-color) instead of a blush glow, per the focus-ring exception to Red Unification. Added :focus-visible ring to the footer's local .ws-btn.
  • Fixed The footer now carries a scoped .cta .sr-only fallback so the form's screen-reader labels can't render as visible text on host pages that don't define the class.
July 2026
v2.9.11 — Planner topbar Strategist readiness badge
  • New Added .topbar-strategist / .topbar-strategist-icon / .topbar-strategist-badge rules to css/planner/components/_topbar.css for the planner topbar's Strategist handoff link — a quiet white link with a light-red readiness pill (--phase-learn-very-light ground, --phase-learn-dark text) that appears when the strategist has left a readiness pointer. Tokens only (--space-1, --space-2, --text-micro, --radius-full, --planner-transition); no hardcoded values beyond the topbar's existing white-on-red link convention.
July 2026
v2.9.10 — Strategist standalone shared-brief page styles
  • New Added .st-app--brief-view and .st-brief--standalone rules to css/strategist.css for the public read-only Workshop Brief page (strategist/brief-view.php): centered 720px column with token-based spacing and an unclamped brief body. Tokens only (--space-6, --space-4); no hardcoded values.
July 2026
v2.9.9 — Tips category tints unified to brand red
  • Improved Completed Red Unification (D1) for Tips: the six --tips-cat-1..6 category tints in platform-tokens.css were changed from the legacy purple ramp (#4527A0…#B39DDB) to a brand-red ramp (#7F1D1D · #991B1B · #C73E3E · #E54D4D · #F87171 · #FCA5A5), matching the Intervention/DNA red tint ramps and the already-red app-tips.php guide. Regenerated tokens.dtcg.json; drift check passes.
  • Fixed Tips app: the local --cat-* tokens in css/tips/styles.css (half-purple, half-red) and the --tips-cat-* fallbacks in tips/index.php are now the full brand-red ramp.
  • Fixed Migrated the live category colours: the 13 rows in the tip_categories DB table stored 7 distinct purples (the source of the category card/chip colours) — these were remapped to the brand-red ramp so the Tips feed and category pages render red instead of purple.
July 2026
v2.9.8 — Launchpad choice screen heading alignment
  • Improved Launchpad heading: upgraded .launchpad-choice-screen__title (the "How would you like to build?" h1 page title) from var(--text-h2, 1.5rem) to standard var(--text-h1, 32px), matching the premium page typography of other index headings across the platform.
  • Improved Semantic Card Headings: changed launchpad choice card titles (.launchpad-choice-card__title, e.g. "Build from scratch" and "Generate with AI") from div tags to semantic h1 tags and zeroed their margin-top in CSS to maintain design consistency and search engine metadata structure.
July 2026
v2.9.7 — Saved Workshop & Schedule modal improvements
  • Improved Applied "Ink Muted" (var(--color-ink-muted, #78716C)) icon styling and neutral var(--bg-muted, #F5F5F4) backgrounds to saved workshop/agenda cards across both the Saved Workshops page (planner/saved.php) and the My Library section of MyWorkshopr (myworkshopr/myworkshops.php). Added precise CSS filters for inline img.icon elements to prevent raw black SVG fallbacks.
  • Improved Meta Pills color: set all calendar meta-pills text and icon colors in the "Schedule Workshop" slide-out modal to pure white.
  • Improved Energy Details panel: reduced the width of the expanded energy details dropdown panel (.energy-detail) by 25% (from 920px to 690px) to make the overall canvas layout feel more compact and polished.
July 2026
v2.9.6 — Red Unification cleanup (guide pages + stray gradients)
  • Fixed app-tips.php guide rewritten from the retired Facilitate purple (#7C4DFF) to the Learn Red family — identity swatches, philosophy cards, core palette, the six category tints, tab-strip demo, and Do/Don't now all read red. Notes that four legacy purple --cat-* tints still ship in css/tips/styles.css and are flagged for migration.
  • Fixed app-hybrid.php guide rewritten from the retired Plan blue (#0284C7) to the Learn Red family; the "Do" now points at var(--phase-learn), matching the shipped /toolkits/hybrid/ page.
  • Fixed Finished two half-migrated gradients — collections-tabs.css and _sidebar.css no longer blend brand red into leftover indigo (#6366F1); both are now #E54D4D → #C73E3E.
July 2026
v2.9.5 — ws_task_checklist component (activation flow)
  • New ws_task_checklist — progress-tracked task list for activation/setup flows (helper in helpers.php, template task-checklist.php, CSS in components.css, demo at components/demos/task-checklist.php). Items carry real-data done states, optional phase eyebrows, links on undone items, a dismiss hook (data-checklist-dismiss), and data-task keys for JS enhancement. Sky-blue focus ring, :active tactile feedback, 375px-safe. First consumer: the MyWorkshopr Getting Started checklist.
  • New .topbar-unsaved-pill (planner _topbar.css) — quiet "Not saved" affordance for anonymous builders.
  • Fixed JSON catalog + generated roster updated (55 components); check-drift.php passes.
July 2026
v2.9.4 — Documentation consistency pass, round 2 (canonical docs + motion pair)
  • Fixed /DESIGN.md: frontmatter elevation.colored re-synced to the all-red shipped shadows; motion frontmatter aligned to shipped easing tokens (deleted --ease-bounce removed, --ease-reveal/--ease-spring named correctly); §3 Coach moved to the Reflect row; §7 phantom --shadow-learn--{app}-shadow; §11.8 and §12.6 pre-D1 color copy corrected; §14.3 "cooler product palette" removed; §10.3 gained the scannability and inclusive-by-default rules recovered from taste.md.
  • Improved /motion.md converted from generic template to the dated rank-1 Workshopr mandate: two-regime scoping in the header and Standard Values (page reveals here; 150–300ms UI micro-motion per motion-system.md), stray wrapping code fences removed.
  • Fixed motion-system.md post-shipping prose: reveal ease is a token (--ease-reveal, P1); --ease-bounce moved out of the live spec table (deleted, P2); the --ws-ease-bounce stray corrected from "defines" to "uses — defined nowhere (dangling)"; the participant reduced-motion spin exception marked as documented-intent-only (not implemented in participant.css).
  • Improved theming.md exceptions table records the planner activity-type colors as a sanctioned functional-coding exception; ADOPTION.md font matrix corrected (Tips/Intervention/Podcasts ship Fraunces + Inter; Priority 1 closed); style guide toast snippet (--text-caption, 12px) and radius decision rule (8px buttons per DESIGN.md, shipped 10px flagged) reconciled.
July 2026
v2.9.3 — Documentation consistency pass (post-D1 reconciliation)
  • Fixed workshopr-design-system.json phase mapping: Coach moved from facilitate to reflect (it had inverted the "Coach = Reflect, never swap" rule); DNA marked standalone. check-drift.php passes (54/54 components, 9 apps).
  • Fixed workshopr-style-guide.md reconciled to rank-4: deference banner to /DESIGN.md added; stale pre-unification content corrected (per-phase blue/purple/emerald map, Tips/Podcasts/Coach app blocks, colored shadows, app-tinted focus rings → sky-blue ring tokens, z-index scale → shipped tokens, coach legacy mode tokens marked historical).
  • Fixed platform-color-system.md rewritten to post-D1 reality (was presenting blue/purple/emerald phases and a per-app "exceptions" model as live while labeled Status: Current).
  • Fixed illustration-guidelines.md: wrong ink hex (#1F2937#2C2416), per-app color-variant table collapsed to the unified red family, stale Planner-blue prompt example.
  • Fixed README.md architecture line and counts (54 ws_* helpers, 44 demos in components/demos/); ADOPTION.md internal cool-gray contradiction resolved (≈25 remain in planner trees, not "all replaced"); DOC-TEMPLATE.md stale "Tips purple" exception example; START-HERE.md counts, §15 changelog pointer, and pre-D1 "phase color discipline" wording; theming.md dead motion.md link.
  • Fixed /motion.md illustrative token block aligned to shipped tokens (--duration-reveal 800ms, --duration-reveal-lg 1200ms, --stagger-reveal; deleted-overshoot curve removed); deprecated taste.md/principles.md/forms.md reduced to pointer stubs.
July 2026
v2.9.2 — Audit B1 participant bridge + lint-blind indigo cleanup
  • Fixed Audit B1: css/facilitator/participant.css referenced 22 undefined tokens surviving on hardcoded fallbacks (a future platform token of the same name would silently restyle the page). Added a participant theme bridge (per theming.md's app-alias pattern) that defines them once — neutrals alias exact-match platform tokens, activity/status ramps are participant-specific. Zero visual change (verified via computed values).
  • Fixed Three lint-blind indigo gradients (pre-unification "AI button" styling) that ds-lint misses because indigo isn't a signature phase hex: briefing.css (#536dfe/#4338ca), _sidebar.css and _modals.css (#6366F1) — all repointed to the red family. Zero indigo/violet hexes remain in planner + facilitator CSS.
July 2026
v2.9.1 — Red Unification executed (D1) + audit decisions D2/D3 + motion tokens P1–P4
  • Improved D1 — the entire platform is red, everywhere the system speaks. Aligned the last holdouts: platform-tokens.css section comments and stale /* #7C4DFF */ annotations, the JSON phases block (was still blue/purple/emerald), and the ds-lint.mjs phase map (tips/coach/live now enforce red — lint is 0 violations across 103 files, down from 13 false positives). DESIGN.md §3 and START-HERE.md rewritten: phases are naming-only.
  • Improved D2 — the sky-blue focus ring is documented as the one intentional non-red accent (keyboard visibility against red UI). Do not "fix".
  • Improved D3 — deprecation register added to the tokens header (delete after Aug 2026: podcast platform brands, planner blues, unused gradients/shadows, cool-text tokens, transition shorthands). The semantic component layer + --animate-* are kept as theming/motion hooks.
  • Fixed Five tokens defined twice in :root deduped (warm backgrounds + status inks — audit F2); stale --z-dialog/facilitator annotations corrected (F3).
  • New Motion P1: page-reveal regime tokenized (--ease-reveal, --duration-reveal/-lg, --stagger-reveal), zeroed under reduced motion. P2: --ease-bounce deleted (zero consumers; --animate-scale repointed to --ease-spring). P3: --transition-* deprecated. P4: --ease-spring documented as non-overshooting. DTCG mirror regenerated (347 tokens); drift + DTCG + ds-lint all green.
July 2026
v2.9.0 — Consolidation pass: token audit, templates, theming + motion specs, pattern library
  • New token-audit-2026-07.md — full token audit (344 tokens, 33% unused, shadow vocabularies in participant/planner CSS, 5 duplicate definitions). Three decisions pending (phase-abstraction color, focus ring, unused-token disposition); DTCG mirror regenerated.
  • New templates/ — standard documentation templates (component page, foundation doc) based on the card.php exemplar, plus a .ds-status maturity badge in the ds-header chrome.
  • New theming.md — three-layer theming architecture, the blessed app-alias bridge pattern, and the six-step new-app checklist. D1-dependent sections marked.
  • New motion-system.md — reconciles /motion.md with the animation tokens (two regimes), catalogs the shipped named patterns (enter/exit/pop/live-pulse/progress/nudge/reveal), proposes four motion-token changes (P1–P4).
  • Improved component-library.php is now the Pattern Library: 50 components organized into five problem categories with jump links, honest "docs pending" states for the 7 helpers without demo pages, and a documented app-patterns promotion queue.
  • Improved START-HERE.md links all consolidation artifacts.
July 2026
v2.8.11 — Facilitator wrong-phase purple purge
  • Fixed css/facilitator/facilitator.css still had 78 references to the facilitate-purple tokens (var(--phase-facilitate*, #7C4DFF/#6D28D9) and stale purple fallbacks) that bypassed the app's red-resolving --fac-purple* aliases. All now route through --fac-purple* (→ --mainsite-* red) per the documented all-red exception in apps.facilitator.colorNote; the poll-bar gradient's raw #A78BFA was replaced with var(--fac-purple-dark). ds-lint is now clean for the file.
July 2026
v2.8.10 — Facilitator confirm dialog + panel typography
  • Fixed The Facilitator's reusable confirm dialog (.confirm-card) had no styles at all — it now follows the established modal anatomy (accent icon tile → Fraunces title → muted subtitle → ws_button row), matching the end-session card.
  • Improved Slide-in panel titles (Polls, Reactions) adopt the modal heading treatment (Fraunces, --text-h4); poll form validation renders as an inline .poll-form__error notice (error tokens) instead of a native alert(); native confirm() calls (session resume, agenda jump) replaced with the styled dialog.
July 2026
v2.8.9 — Audit follow-through: lint exceptions, focus traps, demo pages, facilitator red completion
  • Fixed scripts/ds-lint.mjs phase map now encodes the documented app color exceptions (Planner, DNA, Intervention, Facilitator → red; Tips → purple) instead of flagging shipped red as wrong-phase. Full-repo violations dropped from 156 (mostly false positives) to 83 genuine legacy items.
  • New Pre-commit hook (.git/hooks/pre-commit) runs ds-lint.mjs --staged so new CSS drift is blocked at commit time. Note: git hooks are per-clone — re-create after a fresh clone.
  • New Shared wsTrapFocus() / wsReleaseFocus() utility in components.js; ws_modal refactored onto it and the Coach panel + Intervention drawer wired up (guarded, no-op where components.js isn't loaded).
  • New Demo pages for ws_pill, ws_toggle, ws_cta_section, ws_filter_bar, ws_how_steps under design-system/components/demos/, registered on the component-library index (whose cards now link to demos/ — the old links 404'd).
  • Improved Facilitator red completed on remaining surfaces: css/facilitator/facilitator.css, participant.css, and summary.css now resolve accents to --mainsite-* tokens (purple fallbacks removed). Stale "facilitator = purple" comment in platform-tokens.css corrected.
July 2026
v2.8.8 — Facilitator app red confirmed + live poll cards
  • Improved Corrected apps.facilitator.colorNote in workshopr-design-system.json: the ENTIRE Facilitator app (landing and app.php dashboard) is mainsite/Learn red (#E54D4D) — the facilitate purple in the Facilitator PRD is deprecated for this app. facilitator.css keeps the legacy --fac-purple* variable names but resolves them to red. Also updated the phaseColors.facilitate note.
  • New Poll result card styles (.fac-poll, .fac-poll__bar, etc.) in facilitator/css/facilitator.css for the live Polls panel — token-based spacing/color, GPU-friendly (transform: scaleX) vote bars.
  • Improved Facilitator Reactions + Polls panels visual pass: counts and percentages adopt the timer bar's mono voice ('SF Mono', 'Fira Code', tabular numerals); reaction bubbles pulse (scale + dissipating ring, --ease-spring, reduced-motion guarded) when a count rises; poll cards gained LIVE/FINAL chips and leader emphasis — only the leading option renders a fully saturated bar (trailing fills at 30% opacity), with in-place bar updates so votes animate between refreshes.
July 2026
v2.8.7 — Generative agenda renders from ws_* components
  • Improved The Planner's AI Workshop Generator now composes its output entirely from design-system components. A new client renderer (planner/js/modules/agenda-renderer.js) mirrors the ws-card, ws-activity-badge, ws-badge (duration), ws-suggestion-card, ws-stat, ws-alert, ws-empty-state and ws-skeleton markup instead of bespoke .generator-item HTML — and drops all inline onclick in favor of delegated listeners.
  • New Layout-only stylesheet css/planner/components/_agenda-generative.css (imported by planner-new.css) — all spacing/color/radius via platform tokens, type signalled by the activity badge (no colored side-tab), with a reduced-motion-aware on-canvas assembly animation and a GPU-friendly (transform: scaleX) streaming progress bar.
  • Improved The planner's inline "Swap with AI" alternatives now render as ws-suggestion-card via AgendaRenderer.swapAltCard() — removed the bespoke .ai-swap-alt-card markup and its hardcoded inline styles.
July 2026
v2.8.6 — Self-contained global footer + facilitator landing exception
  • Improved The global footer composite (includes/components/footer.php) now self-loads its fonts (Fraunces / Inter / JetBrains Mono) and emits its semantic tokens (--paper, --ink, --display, …) as zero-specificity :where(:root) defaults. Host pages no longer need to copy-paste the token block; pages that define the tokens on :root still win.
  • Improved Documented in workshopr-design-system.json that the Facilitator landing page (/facilitator/index.php) ships mainsite/Learn red (#E54D4D) as a deliberate exception, while the Facilitator app dashboard stays Facilitate purple (#7C4DFF).
July 2026
v2.8.5 — Synthesize header section (red)
  • New Registered a synthesize section in the global header (ws_header) — icon hub, tag "Synthesize", under the REFLECT app-switcher group. The header ships the mainsite red, so Synthesize adopts red as a deliberate accent exception (like Intervention and Tips).
July 2026
v2.8.4 — Audit close-out (JSON component tiers)
  • Improved Promoted the 10 helpers that were only documented as nested convenience/alternatives/related notes to full top-level component entries in workshopr-design-system.json: ws_card_start, ws_card_end, ws_modal_start, ws_modal_end, ws_modal_footer, ws_duration, ws_category, ws_status, ws_icon_button, ws_stats. The drift check now catalogues 49/49 helpers at top level (was 39), so they're discoverable and won't get re-implemented as raw HTML. Closes the last open finding (P1-3) from AUDIT-2026-07-01-app-vs-design-system.md.
July 2026
v2.8.3 — app-tips.php color fix
  • Fixed app-tips.php still showed coral/red. Tips has shipped Facilitate purple (#7C4DFF) as a deliberate, documented exception since at least v2.8.0 (which corrected the JSON's category colors to match), but the app guide page itself still displayed the old coral/red palette. Updated all color swatches, hex displays, the gradient/shadow, the 6 category tints, and prose ("warm coral" / "coral red" / "coral") to the purple values in platform-tokens.css (#7C4DFF / #6930C3 / #E8DEFF / #F3EEFF + --tips-cat-1..6). Clarified the exception note: Tips is Learn-phase content but deliberately ships Facilitate purple, not Learn red.
July 2026
v2.8.2 — app-planner.php color fix
  • Fixed app-planner.php still showed sky blue. The v2.8.0 Planner recolor (blue → Learn red) updated platform-tokens.css and the JSON but missed the Planner app guide page itself. Updated all color swatches, hex displays, the gradient, and prose ("blue" → "red") to #E54D4D/#C73E3E/#FEE2E2/#FEF2F2. Added an explicit note that the Plan phase token (--phase-plan) stays #0284C7 — only the app itself ships red.
July 2026
v2.8.1 — Documentation cleanup
  • Fixed platform-color-system.md was badly stale (last synced Feb 2026) and contradicted the code on five apps (tips shown red, intervention purple, planner blue, dna green, training green). Rewrote it as a lean ~130-line color-architecture doc that points to platform-tokens.css for hex values instead of mirroring them (mirroring was the drift source). Fixed the same stale planner/DNA colors in workshopr-style-guide.md.
  • New DOC-TEMPLATE.md — standard copy-paste templates (Component / Pattern / Foundation) plus shared doc conventions (status + last-updated header, TOC for long docs, second person, maturity indicators) so every DS doc is structured consistently.
  • Improved De-duplicated the entry docs. START-HERE.md is the single router (source-of-truth hierarchy + on-ramps); README.md is now purely the detailed file map and no longer repeats the "what to read" guidance. Fixed stale claims (JSON is hand-maintained not generated; DESIGN.md is intent truth; 12 app guides not 8).
  • Improved Added standardized status / last-updated headers to START-HERE, README, platform-color-system, illustration-guidelines, and a Contents TOC to the long docs. Updated START-HERE's documented color exceptions to include Planner, DNA, and Intervention.
  • Fixed Refreshed the per-app guide pages to match the recolor — app-planner.php → red, app-dna.php → red, app-tips.php → purple — corrected the remaining stale app-color blocks in workshopr-style-guide.md (tips categories → purple, intervention → red, training → red), and fixed app-hybrid.php's stale "aligned with Planner" note (Hybrid uses the Plan phase blue; the Planner app is now red).
July 2026
v2.8.0 — App-vs-design-system reconciliation (audit)
  • Fixed Planner recolored blue → red. The Planner app shipped mainsite/Learn red (#E54D4D) since June 2026 but the DS still said sky blue. Updated --planner-* in platform-tokens.css, apps.planner in the JSON (with a colorNote), and the phase table + a new exception bullet in DESIGN.md. The Plan phase (--phase-plan) stays blue; only the app overrides.
  • Fixed DNA recolored green → red. Same treatment for DNA (--dna-* + 5 category tints, JSON, DESIGN.md). The Reflect phase (--phase-reflect) stays emerald because Coach still uses it.
  • Fixed Tips category colors. The JSON listed a red scale for apps.tips.categories; corrected to the purple scale that platform-tokens.css and the app actually use.
  • Fixed Motion easing contradiction. Canonical reveal/spring ease is now cubic-bezier(0.22, 1, 0.36, 1) across DESIGN.md, the JSON, and motion.md (was split between two curves). Added standard (0.4, 0, 0.2, 1) for micro UI transitions.
  • Improved motion.md rewritten for the real stack — vanilla CSS transitions/@keyframes + IntersectionObserver (it previously documented Next.js/React/Framer Motion/GSAP, none of which this platform uses).
  • New Z-index scale extended to cover what the app actually uses: --z-overlay: 1000, --z-header: 1100, and a high band --z-dialog: 10000 / -high: 10001 / -highest: 10004 (planner live-mode/kiosk). Mirrored in the JSON zIndex.
  • New Extended utility color tokens for values the app was hardcoding: --color-orange-600, --color-green-700, --color-auth-primary, --color-auth-gradient-dark, and purple/orange 100–200 pastels.
  • Improved Aligned css/facilitator/facilitator.css --phase-facilitate fallbacks from stale #7C3AED to the real #7C4DFF. Bumped DESIGN.md last-reconciled to 2026-07-01. Full audit at design-system/AUDIT-2026-07-01-app-vs-design-system.md.
June 2026
v2.7.18 — Intervention accent: purple → red
  • Improved Intervention now uses red (Learn red #E54D4D) as its accent instead of facilitate purple. Updated the --intervention-* tokens (primary/dark/light/very-light/gradient/shadow + 6 category tints) in platform-tokens.css, the apps.intervention block in workshopr-design-system.json (with a colorNote), and the app-intervention.php guide. Drift check passes. Intervention stays in the Facilitate phase but is a deliberate color exception, like Tips.
June 2026
v2.7.17 — My Workshopr header section + self-contained footer
  • New Added a myworkshopr section to ws_header (icon space_dashboard, tag “My Workshopr”, single Dashboard nav link). The title band is enlarged for this section (15px, wider) via a [data-section="myworkshopr"]-scoped rule so other apps are unaffected.
  • Improved The global footer (includes/components/footer.php) now self-loads platform-tokens.css so it renders correctly on host pages (e.g. MyWorkshopr) that don't already include the token sheet. Browsers dedupe the identical href elsewhere.
June 2026
v2.7.16 — ws_header account menu + wired controls
  • New ws_header (includes/components/header.php) gained a logged-in account dropdown (.wsh-header__account / .wsh-header__menu) with avatar, name/email, and links to My Workshopr, My Workshops, Team, Settings, and Sign out.
  • Improved Header controls are now functional: search button opens the site-search overlay (data-search-trigger), the apps grid icon became a gear linking to /myworkshopr/, and Sign In opens the auth modal in place. The component now self-loads its auth-modal and search-overlay dependencies.
June 2026
v2.7.15 — Letter CTA form polish
  • Improved Refined the footer Letter CTA form: input border-radius now matches the button (10px), inputs bumped to 16px / roomier padding (no iOS zoom), added input hover states, a .cta__note privacy line, and a single-column stack under 480px.
June 2026
v2.7.14 — Letter CTA form wired to newsletter API
  • Fixed The Letter CTA subscribe form (global footer + academy/components/letter-cta.php) now submits to /api/newsletter.php with loading/success/error states. Added a .cta__hp honeypot field and .cta__msg / .cta__msg--error status-message styles.
June 2026
v2.7.13 — Larger .section-title display headings
  • Improved .section-title display headings increased ~25%: the clamp(28px, 4vw, 38px) rule (Intervention, Podcasts, Tips) is now clamp(35px, 5vw, 48px); DNA moved --text-3xl--text-4xl; shared-components and Academy use calc(token * 1.25). Small uppercase sidebar/handout labels were intentionally left unchanged.
June 2026
v2.7.12 — Global header label left-aligned
  • Improved ws_header() app/section label (.wsh-header__tag) is now left-aligned (justify-content: flex-start; text-align: left) instead of centered, so multi-line labels like “Facilitator Style Quiz” read cleanly.
June 2026
v2.7.11 — Global header sticky to top
  • Improved ws_header() (.wsh-header) is now position: sticky; top: 0 instead of position: relative, so the global navigation pins to the top of the viewport on scroll. Existing z-index: 1050 (above planner topbar/slide panels, below modals) is unchanged.
June 2026
v2.7.10 — Section hero: book-cover carousel
  • New ws_section_hero() now supports a per-section carousel config (array of {img, href, title}) that renders an auto-advancing book-cover carousel in the visual slot instead of the static illustration. Includes clickable covers, prev/next arrows, dot navigation, pause-on-hover/focus, and prefers-reduced-motion support. CSS/JS are namespaced under .wsh-carousel and self-emitted once. First used by the Academy hero to showcase all six ebook covers. Each slide also supports an optional top-right status pill ({label, tone}available green / soon red).
June 2026
v2.7.9 — Section hero component
  • New ws_section_hero($section) (includes/components/section-hero.php) — section-aware hero from the Figma "Hero — *" frames, with a per-section registry (home, library, academy, tips, planner). Self-emits its CSS under the .wsh-hero namespace so it renders on any page regardless of bundle. Illustrations live in /images/heroes/. Placed on the home, library, academy, tips, and planner-landing pages.
  • New section-hero.php is require_once'd by both helpers.php and components/header.php, so any page with the global header can render a section hero without pulling in the full helpers.
  • Fixed ws_header now establishes a stacking context (position: relative; z-index: 1050) so its app-switcher dropdown clears app chrome (planner topbar/panels) while staying below modals.
  • Improved Tuned ws_section_hero to match the Figma "Hero — Home" frame exactly — light-weight Fraunces title in warm ink (#4D453B) with the red italic accent, bordered badge, gradient primary CTA, and dark (not red) stat figures. Pulled the exact illustration from Figma.
  • Improved Recreated the Library hero from the Figma "Hero — Library" frame — added optional imageRounded (16px) and visualChips (category quick-links under the illustration) to the component; Library now shows Workshops / Icebreakers / Collections / Goals chips.
  • New includes/page-grid.php — a reusable snippet that lays the house graph-paper grid (24px, warm ink, background-attachment: fixed) over the page body background, preserving each page's background-color. Added to the home, library, academy, tips, and planner landing index pages; ws_section_hero also carries the grid so it reads behind the hero.
  • New ws_dna_cta() (includes/components/dna-cta.php) — Facilitator-DNA quiz promo strip from the Figma "dna cta" frame: light-red bar with an overlapping circular archetype badge, red icon chip, title/subtitle, and a "Take the quiz" button linking to /dna/. Self-emits its CSS; ships with the global header.
  • Improved ws_section_hero buttons now accept an optional icon (the Academy hero's "Get the Books" uses it). Synced the Academy hero to its Figma frame (badge "Workshopr Academy", book icon on the primary CTA).
  • Fixed Hero/CTA icons rendered as literal text on pages (e.g. Academy) that use ws_header but don't load includes/icon.php — the header component now loads the inline-SVG icon() helper so component icons always render.
  • Improved The global footer's "Level Up Your Facilitation" section now animates in on scroll — each row fades + rises as it enters the viewport, with cards staggered 70ms within a row (ease-out, ~0.5s). Progressive enhancement via IntersectionObserver: no-JS and prefers-reduced-motion users get the section with no hidden state, and the revealed state is explicit so content is never stuck hidden. Doesn't interfere with the existing card hover-lift.
June 2026
v2.7.8 — Planner recolored to mainsite red
  • Improved The planner phase color is now mainsite red, not the legacy plan-phase blue. _component-bridge.css previously remapped --mainsite-primary → --planner-primary (blue), forcing the whole planner blue. It now points the planner accent tokens (--planner-primary/dark/light/very-light, plus the --planner-interactive* and --planner-blue* aliases) at the native --mainsite-* reds, scoped to body.planner-page/.planner-app-active so it beats the :root token re-imports. Note: aliases declared : var(--planner-primary) at :root are computed there and must be re-declared in the scoped block to recolor.
  • Improved AI Workshop Generator loading state (_generator-modal.css) — heading/subtitle dropped from --text-display/--text-h2 to --text-h3/--text-small, with a pulsing auto_awesome sparkle layered inside the spinner.
June 2026
v2.7.7 — Generator modal type scale
  • Improved AI Workshop Generator modal (css/planner/components/_generator-modal.css) — form labels/inputs/selects were using heading tokens (--text-h2/--text-h3); right-sized to --text-ui/--text-small/--text-meta so controls match the planner UI scale. Added a :-webkit-autofill override so autofilled fields stay on-theme instead of browser blue. Shared planner @import stamp bumped 20260610 → 20260615.
  • Improved Generator output view got the same treatment — agenda item names dropped from --text-h2 to --text-h4, start times / headers / rationale / refine input from --text-h3 to --text-ui/--text-small, the "total" badge became a pill, and the loud salmon (#FCA5A5) refine strip was softened to a light-red bar with a subtle border.
  • Improved Generator error state now matches the loading state — title/body dropped from --text-display/--text-h2 to --text-h3/--text-small, and the icon (which was targeting a never-rendered .material-symbols-rounded) is now a 44px circular error chip. All four generator states — form, output, loading, error — share one type scale and layout language.
June 2026
v2.7.6 — Versioned @imports rolled out platform-wide
  • Fixed Extended the v2.7.5 stale-cache fix to every other CSS bundle with unversioned @imports: css/styles.css (main site), css/coach/coach.css, css/dna/styles.css, css/academy/training.css, css/intervention/styles.css, css/participant/participant.css, css/facilitator/*.css, css/planner/analytics.css, plus the nested token imports (css/tokens/_variables.css, css/coach/base/_variables.css, assets/css/auth-modal.css, facilitator/css/facilitator.css). All imports now carry the shared ?v=YYYYMMDD stamp. Bump the stamp whenever any imported file changes. External imports (Google Fonts) and bundler-managed apps (dna/mobile) deliberately excluded.
June 2026
v2.7.5 — Versioned @imports in the planner bundle
  • Fixed Stale-cache breakage after CSS deploys: the ?v= stamp on planner-new.css never invalidated the files it @imports, and the server caches CSS for 7 days — so production visitors kept week-old partials (collapsed .planner-body flex layout, exposed card edit panels). Every @import in planner-new.css (and the platform-tokens.css import inside base/_variables.css) now carries a shared ?v=YYYYMMDD stamp. Bump the stamp on all imports whenever any planner CSS changes.
June 2026
v2.7.4 — Planner stylesheet consolidation
  • Fixed Made css/planner/planner-new.css the single canonical planner bundle. The partially-gutted planner-v2.css monolith (1,042 DUPLICATE/EXTRACTED markers) was retired and deleted; every selector it still held was already present in the imported component set, except the .sr-only a11y utility which was migrated into planner-new.css. planner/includes/header.php now loads the one bundle on the modular path (the dead planner.css link and the temporary standalone _modals.css patch are gone), and the kiosk/embedded headers were repointed off the retired monolith. Token values unchanged — drift check passes.
June 2026
v2.7.3 — Global 80% text scale
  • Improved Platform text now renders at 80%. Font sizes are rem and spacing is px, so a root html { font-size: 80% } shrinks all rem text while leaving layout intact. Added to platform-tokens.css and both global headers (unified-header.css, academy/global-header.css) to reach pages outside the token chain; the per-app html bases (mainsite, planner, academy, participant, facilitator) were scaled by the same 0.8 so it stays uniform. No token values changed, so the JSON mirror stays in sync (drift check passes). Inline px font sizes on bespoke landing pages are not covered.
June 2026
v2.7.2 — Invalid negative-margin token syntax
  • Fixed Repaired 8 invalid -var(--space-*) negative margins across css/detail-immersive.css, css/styles.css, css/planner/planner-v2.css, css/planner/live-mode.css, and css/academy/styles.css. -var() is not valid CSS, so browsers dropped the entire declaration — most visibly leaving the library detail-page stats card (.immersive-stats-float) fully left-aligned because its margin: -var(--space-8) auto 0 (and with it the auto centering) was ignored. The correct form for negating a token is calc(-1 * var(--space-8)).
June 2026
v2.7.1 — Planner phase-color fix
  • Fixed The planner’s component bridge (css/planner/components/_component-bridge.css) never remapped --mainsite-primary, so the global header and ws_* components fell back to mainsite red (#E54D4D) instead of the planner Plan-phase blue (#0284C7). Added a body-scoped remap (body.planner-app-active) that beats :root and survives the late platform-tokens.css @import in assets/css/auth-modal.css. Semantic danger reds use their own tokens and are unaffected.
June 2026
v2.7.0 — Portable component library (PHP + React)
  • New design-system/components/php/ and design-system/components/react/ — portable implementations of every ws_* component (49) and all 6 documented patterns. PHP ships as namespaced Workshopr\DS\* standalone functions (collision-free with the global ws_* helpers); React ships as typed .tsx function components (Ws<Name> + Ws<Name>Props).
  • Improved Both flavors render the existing ws-* class names from components.css (single source of truth) — no duplicated CSS, no hardcoded colors/sizes/fonts. Shared helpers: _icon.php (icon/cx/attrs/e) and Icon.tsx / cx.ts.
  • New Barrels: php/index.php (requires all 50 components, 59 Workshopr\DS functions) and react/index.ts (52 exports), plus a README.md with usage and a patterns-CSS caveat. All PHP verified with php -l + render smoke tests.
June 2026
v2.6.0 — AI-enablement: enforcer fix, enriched catalog, DTCG export
  • Fixed The design-system-enforcer MCP pointed at a stale /sites/newphp dir where the server file doesn’t exist, so it never launched — the AI’s real-time compliance oracle was dead. Repointed to the absolute platform path (now reads 325 tokens + 49 ws_* helpers). Wired “validate before finishing any UI work” (check_snippet / validate_file / suggest_component) into CLAUDE.md, GEMINI.md, and START-HERE.
  • New generate-catalog.php now enriches components.generated.json with a canonical example, whenToUse / whenNotToUse, an a11y note, and a derived antiPattern per ws_* helper — extracted from the components/*.php demos. It’s now the “how do I use this component” reference for AI (example 41/49, whenToUse 20/49, a11y 6/49).
  • New generate-tokens-dtcg.phptokens.dtcg.json: all tokens in W3C DTCG format with $type inference, $description from CSS comments, and var(){alias} references. Standard format for Style Dictionary, Figma, Tokens Studio, and AI tooling.
  • New design-system/llms.txt — an llms.txt-convention index pointing AI agents at the canonical files, machine-readable sources, rules, and validators. Both new generators have --check modes wired into npm, the CI ds-drift job, and the pre-commit hook.
June 2026
v2.5.2 — Display + timer tokens (font-size scale complete)
  • New Extended the type scale with five above-base tokens: --text-display-lg (3.5rem/56px), --text-display-xl (4.5rem/72px) for large hero headings, and --text-timer-sm/md/lg (4/5/6rem) for the live & run-mode countdown numerals.
  • Improved Tokenized the final 12 held hero/timer declarations. The platform now has zero bare font-size values — every font-size resolves through a --text-* token. check-drift passes; all apps verified HTTP 200.
June 2026
v2.5.1 — Off-scale font-size normalization
  • Improved Snapped 619 off-scale font-sizes to the nearest type-scale step via ds-tokenize-fontsize.mjs --snap. Each becomes var(--text-*, <token-rem>) so the fallback matches the snapped size. Most deltas are sub-pixel; the visible ones are 28px→24px (62×), 22px→20px (46×), 36px→32px, and 40px→46px.
  • Held 12 hero declarations (56–96px / 3.5–6rem) are above the top scale step (46px); the snapper holds anything whose nearest step is >8px away rather than collapsing hero text. These await dedicated display tokens.
June 2026
v2.5.0 — Type-scale tokenization
  • Improved Tokenized 1,635 hardcoded font-sizes to --text-* across 104 stylesheets. Only exact on-scale values were converted (px and rem, parsed unit-agnostically), each retaining its original value as a standalone fallback — e.g. font-size: 14pxfont-size: var(--text-small, 14px). Value-preserving, idempotent, and safe on pages that don't load platform-tokens.css.
  • New scripts/ds-tokenize-fontsize.mjs — exact-match font-size tokenizer. Reports off-scale values rather than snapping them, so no size silently changes.
  • Note ~631 off-scale sizes remain (e.g. 0.8rem, 0.85rem, 17px, 22px, 28px, hero 36–96px). These need a typography decision (snap to the nearest scale step = a visual change) and were deliberately not auto-converted.
June 2026
v2.4.0 — Compliance gate + token cleanup
  • New scripts/ds-lint.mjs — a low-noise compliance gate that fails on three high-signal regressions: cool-gray hex, banned font stacks (Source Sans 3, legacy Bitter/DM Sans), and wrong-phase brand-color leakage. Scopes phase rules carefully (purple is allowed in the blue planner for activity/icebreaker accents; green is never flagged because it doubles as success/complete). Runs via npm run lint:ds, the pre-commit hook on staged files, and a CI job.
  • New scripts/ds-tokenize.mjs — value-preserving hex→token rewriter. Maps grays (warm Stone), ink, semantic status, and brand primaries; resolves #fff by context (color:--text-inverse, else --bg-surface); and skips hexes already in var() fallback position so it never produces redundant var(--x, var(--x)).
  • Improved components.css — 55 bare hardcoded colors tokenized and all 27 cool-grays removed. Several were dead-token fallbacks (var(--color-border, #e5e7eb), var(--color-text, #111827)) referencing undefined variables, so the cool gray was actually rendering; these now resolve to warm Stone / ink tokens. Component demo page verified rendering with zero PHP errors.
  • Fixed Phase-color leakage — Coach mobile app --primary and note-icons moved off mainsite red / planner blue onto Facilitate purple; Tips footer accents moved off red onto Tips purple. Coach, Tips, and Facilitator pages verified at HTTP 200.
  • Fixed Source Sans 3 → Inter across facilitator, myworkshopr, participant, and prep stylesheets (12 declarations), enforcing the Fraunces/Inter mandate. Microsite --font-* definitions confirmed already on Fraunces/Inter (Bitter/DM Sans only survive in comments).
  • Improved Remaining cool-grays cleared from library-goals.css, the shared _sidebar.css, listing-redesign.css, and the DNA mobile reading sheet, then from the consolidated planner tree — zero cool-grays platform-wide.
  • Fixed Resolved the last 22 wrong-phase lint hits. Investigation found these were all intentional danger/CTA reds caught only by their redundant literal #E54D4D fallbacks (facilitator --fac-red = --color-error; planner --planner-red = documented mainsite-CTA alias; --phase-learn status colors). Tokenized them (drop literal, keep the var) for zero visual change; mapped the live-mode --run-danger token to --color-error; and marked the standalone-capable auth-modal CTA with a documented lint exception. ds-lint now clean across all 112 scanned files.
June 2026
v2.3.0 — Consolidation: single entry point + drift guard
  • New START-HERE.md — one entry point with a stated source-of-truth hierarchy (/DESIGN.md + /motion.md for intent; platform-tokens.css + helpers.php for implementation; JSON as a machine mirror) and audience on-ramps for designers, developers, and Claude.
  • New check-drift.php — guards the JSON mirror against drift: fails if any ws_* helper is undocumented in the JSON, or if phase-color tokens disagree between platform-tokens.css and the JSON. Known source contradictions (tips, training) are allowlisted as visible warnings pending a design decision.
  • Improved JSON meta now references the canonical /DESIGN.md and /motion.md, declares itself a hand-maintained mirror (correcting the earlier “auto-generated” claim), and points to the drift guard.
  • Improved README.md now defers to START-HERE.md and corrects the JSON provenance note.
  • Fixed Phase-color records reconciled to the shipped apps: Tips documented as a deliberate Facilitate-purple exception (Learn content, purple brand), and Academy/Training documented as Learn red (#E54D4D) — the unimplemented “training = green” mapping was retired from the JSON, DESIGN.md, and CLAUDE.md §11. Each app now carries a colorNote in the JSON, and both pass check-drift.php without an allowlist.
  • New Added the --facilitator-primary token family (aliased to --phase-facilitate, #7C4DFF) — facilitator was the only app missing its --{app}-primary token. The JSON facilitator color was corrected from the stray #7C3AED to the phase purple, matching coach/intervention.
  • Improved check-drift.php now resolves one level of CSS var() indirection, so phase-aliased app tokens validate. Wired into governance: npm run lint:ds:drift, a ds-drift PHP job in the Design System Lint GitHub workflow, and a staged-file gate in the pre-commit hook.
  • Improved check-drift.php gained a reverse check — a JSON component with no matching ws_* function (renamed/removed) now fails the build — and reports component counts. Corrected stale counts in README.md (39 demo pages, 49 helpers, not “34”/“40+”), and aligned GEMINI.md with the consolidated DESIGN.md (it still pointed at the deprecated taste/principles/forms files).
  • New generate-catalog.php + components.generated.json — a real generator that derives a ws_* component roster (signature, summary, declared options) straight from the helpers.php docblocks. The curated JSON keeps its rich hand-authored option metadata; the generated file is the can’t-drift companion. --check mode fails on a stale roster or a curated signature that no longer matches source, wired into npm run lint:ds:catalog, the CI ds-drift job, and the pre-commit hook. Also added the canonical-source banner to voice.php/philosophy.php pointing at DESIGN.md.
May 2026
v2.2.0 — Token compliance: extended accent + brand tokens
  • Improved Tokenized recurring hardcoded colors across css/academy/styles.css, css/facilitator/facilitator.css, css/planner-new/*, css/styles.css, and css/unified-header.css — raw hex values replaced with platform tokens. Exact color values preserved.
  • New Teal accent tokens for planner secondary actions: --color-teal, --color-teal-dark.
  • New Extended state shades: --color-warning-darker, --color-error-strong, --color-error-darker, --color-info-strong, --planner-accent-dark, --color-success-surface, --color-success-darker, and the soft warning callout surface (--color-warning-surface + -border / -ink).
  • New Meeting-tool brand tokens mirroring the podcast platform set: --platform-meet, --platform-zoom, --platform-inperson (each with -ink / -bg variants where used).
May 2026
v2.1.1 — Planner sidebar: prototype-matched toolbar + flat suggested list
  • New .sidebar-toolbar component for the planner's activity sidebar — sticky search input + pill-style type tabs. Lives in css/planner/components/_sidebar.css (mirrored at css/components/_sidebar.css).
  • New .side__group / .side__group-title / .side__list / .side__more / .side__group-empty structural classes for the flat "Suggested for X" list and "Browse all activities" footer link.
  • New .library-card--lib variant — full-bleed flat draggable card with .lib-card__top (dot + title), .lib-card__meta (pill + duration), .lib-card__dot--{exercise|icebreaker|break}, and .lib-card__pill--{exercise|icebreaker|break} type modifiers. Scoped under .activity-sidebar so it doesn't affect other surfaces that use .library-card.
  • Improved All new card / pill colors pull from existing planner tokens (--planner-exercise-color, --planner-icebreaker-color, --planner-break-color) via color-mix() for tinted backgrounds — no new hex values introduced.
  • New Utility hide rules: .sidebar-section[hidden], .library-card[hidden], .sidebar-sections[hidden], .sidebar-custom-activity-section[hidden], .side__group[hidden], .side__group-empty[hidden]. Lets the filter module + legacy-section wrapper hide nodes via the native HTML hidden attribute.
May 2026
v2.1.0 — Level Up Grid: phase grouping + button --half
  • New ws_level_up_grid() now supports an optional group_by_phase => true mode that renders Learn / Plan / Facilitate / Reflect as their own sub-sections, each with an italic phase title and a half-paragraph intro in Workshopr voice (overridable via phase_copy).
  • New CSS classes for the grouped layout: .ws-level-up__phase, .ws-level-up__phase-head, .ws-level-up__phase-title, .ws-level-up__phase-desc, plus phase-color modifiers (--learn / --plan / --facilitate / --reflect) on the phase block itself.
  • New .ws-level-up__grid--3 modifier for 3-up phase rows (used by Learn and Plan phases). Existing 4-up grid stays the default.
  • New .ws-btn--half width modifier (50%) — sized for narrow card columns where --full reads as too dominant.
  • Improved Responsive overrides updated so both .ws-level-up__grid and .ws-level-up__grid--3 step down 4 → 3 → 2 → 1 column, and .ws-level-up__phase-head stacks below 1024px.
  • Improved Demo: /design-system/components/demos/level-up-grid.php now showcases the grouped variant. Button demo cheatsheet lists --half.
April 2026
v2.0.0 — Planner ws_* Component Migration (Phase 5)
  • Improved Migrated planner/edit.php buttons and form inputs from raw HTML to ws_button() and ws_input()
  • Improved Migrated remaining raw inputs in planner/includes/modals.php — invite email, role select, search inputs, category filter
  • Improved Replaced raw type badges and duration spans in planner/view.php with ws_badge() and ws_duration()
  • Improved Standardized planner/saved.php buttons with ws_button()
  • Improved Migrated schedule panel in planner/planner.php — date/time inputs, timezone select, footer buttons
  • Improved Tokenized inline checkbox styles in save modal to use var(--space-*) and var(--text-sm)
April 2026
v1.9.1 — Planner Brand Color Tokenization
  • New Added --planner-overlay-light/medium/heavy and --planner-backdrop/backdrop-light tokens to planner variables
  • Improved Tokenized standalone #E54D4D (red), #22C55E (green), #9333EA (purple), #F59E0B (warning) hex values across planner CSS to use var(--planner-*) and var(--color-*) tokens
  • Improved Replaced standalone #2563EB in planner cards and generator modal with var(--planner-exercise-color)
  • Improved Tokenized status/semantic colors (warning, error, success backgrounds) in planner-v2.css, analytics.css, exercise-builder.css, briefing.css, live-mode.css
April 2026
v1.9.0 — Cool Gray Eradication & Structural Adoption
  • Improved Eradicated all 354 hardcoded cool gray hex values across 23 CSS files. Entire platform now uses warm stone grays (--gray-50 through --gray-900) exclusively
  • Improved Added helpers.php to global header.php — all pages now have automatic access to ws_* component helpers without manual includes
  • Improved Migrated /contact/ form from raw HTML to ws_input(), ws_select(), ws_button() components
  • Fixed Replaced 6 hardcoded font-family: 'Bitter' in MyWorkshopr CSS with var(--font-heading)
  • Fixed Cleaned up 2 DM Sans font fallbacks in Academy CSS
  • Improved Coach variables now reference var(--gray-*) tokens for text colors instead of cool gray hex values
  • Improved Landing page, auth modal, content gating, and typography base CSS fallbacks updated from cool to warm stone
  • New Added "Touch It, Fix It" convention to Conventions.md for ongoing adoption enforcement
April 2026
v1.8.4 — Live-Mode Font-Size Normalization
  • Improved Normalized all hardcoded font-size values in live-mode.css to design system tokens: compact type scale variables for UI chrome, rem units for presentation display elements, icon sizes preserved as px
April 2026
v1.8.3 — Planner Font-Weight & Font-Size Normalization
  • Improved Converted 9 hardcoded font-size px values to rem units in run-mode.css and workshop-studio.css
  • Improved Replaced all hardcoded font-weight numeric values (400/500/600/700) with design tokens (--font-normal, --font-medium, --font-semibold, --font-bold) across 7 planner CSS files
April 2026
v1.8.2 — Facilitator CSS Hardening
  • Improved Replaced ~25 instances of transition: all in facilitator.css with specific property transitions using design tokens (--duration-fast, --duration-base)
  • Improved Tokenized ~40 standalone hardcoded hex colors to CSS variables with fallbacks (--color-warning-*, --color-error-*, --color-success-*, --color-info-*, --phase-facilitate-*, --gray-*)
  • New Added :focus-visible outlines to all 45+ interactive elements in the Facilitator app (buttons, tabs, cards, inputs)
  • New Added @media (prefers-reduced-motion: reduce) block scoped to .facilitator-app
March 2026
v1.8.1 — Secondary Button Refinement
  • Improved .ws-btn--secondary hover style updated: now uses a subtle background tint (--phase-learn-very-light) and darker text instead of a full solid color fill, matching the academy pattern for a more refined interaction
  • Improved .ws-btn--secondary default background changed from opaque white (--bg-surface) to transparent for better layering on non-white backgrounds
March 2026
v1.8.0 — Semantic Tokens & Cross-App Polish
  • New 40+ semantic component tokens added to platform-tokens.css — buttons, cards, inputs, modals, badges, tooltips, toasts, links, and dividers now have dedicated token sets
  • New Standard suffix convention documented in style guide: -dark, -light, -very-light, -secondary, -muted, -faint
  • New Semantic component token documentation in tokens.php with full usage tables for each category
  • Improved Google Font imports cleaned across 24 PHP templates — removed unused Bitter and DM Sans font families from <link> tags
  • Improved Coach landing pages updated: inline CSS font references migrated from DM Sans to Inter
  • Improved Token count updated from 180+ to 280+ across documentation and hero stats
  • Improved Changelog CSS now uses design tokens instead of hardcoded hex values
  • Fixed Audit score: 87 → 92/100
March 2026
v1.7.0 — Comprehensive Audit & Migration
  • New 6 overlay/backdrop tokens added: --backdrop-light, --backdrop-medium, --backdrop-heavy, --backdrop-brand, --overlay-white, --overlay-surface
  • New Accessible contrast alternatives: --color-primary-on-white (#C73E3E, 5.02:1) and --color-ink-faint-accessible (#78716C, 4.6:1)
  • New Contrast reference table added to colors.php documentation
  • New Deprecation timeline for legacy v1.x tokens — v2.0 sunset plan with console warning phase
  • New components/components.css — dedicated component stylesheet separated from token file
  • Improved Full ARIA + keyboard navigation documentation added to Tabs, Accordion, Tooltip, and Modal components
  • Improved Font migration complete across all microsites: Bitter → Fraunces, DM Sans → Inter in 9 CSS files
  • Improved Planner hardcoded color sweep: 156 hex → token replacements across 9 CSS files (86% reduction, 361 → 49)
  • Improved Modal component documentation now includes parameters table
  • Improved Breakpoint documentation updated from 4 to 6 breakpoints matching platform-tokens.css
  • Fixed 8 cool gray hex values in overview.php and 4 in changelog.php replaced with warm stone palette
  • Fixed --phase-reflect-light corrected to #A7F3D0; missing -very-light variants added
  • Fixed Hero stats corrected: "15 Components" → "33", "100+ Design Tokens" → "280+"
  • Fixed Missing half-step spacing tokens and typography tokens added to documentation
  • Fixed .app-badge component CSS moved out of platform-tokens.css into components.css
  • Deprecated Legacy token aliases (v1.x names) — scheduled for removal in v2.0
  • Fixed Audit score: 62 → 78 → 87/100
March 2026
v1.6.0 — Specialized Components
  • New ws_chip_input() — Multi-value input with removable chip tokens for email lists and tag inputs
  • New ws_radio_card_group() — Single-select card grid with icon, title, and description per option
  • New ws_checkbox_card_group() — Multi-select card grid for choosing multiple options
  • New ws_suggestion_card() — Action-oriented card for AI suggestions and contextual prompts
  • New ws_modal_footer() — Standardized modal footer with primary/secondary action buttons
  • New ws_activity_badge() — Color-coded activity type badge (exercise/icebreaker/break)
  • New ws_results_overlay() — Full-screen/inline overlay for results, scores, and completion states
  • New .ws-toggle — Reusable toggle switch CSS extracted from planner patterns
  • New .ws-chip — Chip markup pattern with remove button for JS-rendered chips
  • Improved Component library grid updated with all 7 new component entries and demo pages
  • Improved CSS uses platform tokens with hardcoded fallbacks so components work outside the planner
  • Improved CLAUDE.md updated with mandatory design system usage rules and full component list
February 2026
v1.5.0 — Developer Experience
  • New Copy-to-clipboard button on all .ds-code blocks (hover to reveal)
  • New Responsive preview toolbar (Desktop / Tablet / Mobile) on component demos
  • New Live demo for ws_hero() — full, minimal, and accent variants with rendered output
  • New Live demo for ws_listing_card() — workshop, exercise, and icebreaker card examples
  • Improved Code blocks now show copy button on hover with "Copied" confirmation feedback
  • Improved Demo containers auto-wrap with responsive preview controls via shared DS footer
February 2026
v1.4.0 — New Components
  • New ws_section_header() — Page/section titles with subtitle, label, and alignment options
  • New ws_feature_card() — Icon + title + description cards with compact and coming-soon variants
  • New ws_stat() / ws_stats() — Single metric display and grouped stat rows with dividers
  • New ws_checklist() — Icon-prefixed lists with custom icons and compact variant
  • New ws_empty_state() — Placeholder with icon, description, and CTA button
  • New ws_breadcrumb() — Accessible navigation trail with separator icons
  • Improved Added animation/transition tokens to platform-tokens.css
  • Improved Swept index.php and about/index.php to use component library
  • Improved Added storybook-style documentation pages for all new components
  • Improved Updated component library grid and sidebar navigation
January 2026
v1.3.0 — Component Library
  • New ws_badge(), ws_duration(), ws_category(), ws_status() — Badge system
  • New ws_button(), ws_icon_button() — Button with variants, sizes, loading state
  • New ws_input(), ws_select() — Form field components
  • New ws_card_start() / ws_card_end() — Card containers
  • New ws_avatar() — User avatars with initials fallback
  • New ws_modal_start() / ws_modal_end() — Dialogs, drawers, bottom sheets
  • New ws_tabs() — Tab navigation
  • New ws_tooltip() — Contextual hints on hover/focus
  • New ws_accordion() — Collapsible content sections
  • New ws_progress() — Progress bars and indeterminate loading
  • New ws_alert() — Notification banners with actions
  • New ws_skeleton() — Loading placeholder system
  • New Toast notification JS (wsToastSuccess, wsToastError, etc.)
  • New Component documentation pages for each component
December 2025
v1.2.0 — Design System Foundations
  • New Design system microsite with sidebar navigation
  • New Color palette, typography, spacing, and elevation documentation
  • New Platform design tokens (platform-tokens.css)
  • New Philosophy, voice & tone, and design decisions pages
  • New Icons reference and accessibility guidelines