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 itsAsktoggle, the thread's two bubble sides (.cv-find__msg--agenton white behind avar(--gray-200)edge,.cv-find__msg--userright-aligned onvar(--phase-learn-light)), the starter and answer chips, a three-dot.cv-find__typingindicator that holds still underprefers-reduced-motion, and the result group: a.cv-find__bestlabel, an italic.cv-find__whyline, 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-chiprather than a new numeral button; the current page is that chip's.is-activestate. The one addition is.cv-chip--disabled, 0.45 opacity withpointer-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 anow carriesvar(--phase-learn-dark)andvar(--font-semibold). The attribution footnote isvar(--gray-100)undervar(--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 invar(--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:hoverkeeps 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/tocss/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 tovar(--text-display)on one line, and closes the gap below tovar(--space-5). The 48px above is deliberately untouched:.app-railopens level with the hero at that same value, and moving one without the other steps the two columns apart. - New
chipsoption onws_workshop_list(): whether to draw the stage filter chips, default true, withfilterskept as its older name rather than added as a second switch that could disagree. With the chips off,filterstops 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. Thefooteroption gained avariantkey, 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, andworkshop-list-menu.jsclones 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_extraon 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. Alsocounts, carried through from the Workshop Record so a page can build its own line without a second query. - New
badgeon aws_task_checklist()row (includes/components/task-checklist.php): a short count rendered as a quietws_badgepill 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
shorton aws_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--shortis hidden by default and the layout picks which one shows, and the item takes the full label as itsaria-labelso 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-cardand their two media queries were the last hand-rolled workshop card on the platform; Prove rendersws_workshop_list()now, so they went, and the card-border rule inapp-shell.cssstopped 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--primarynow has its own hover and focus state. Rest,:hoverand:focus-visibleshared one declaration inapp/css/dashboard.css, so the one emphatic button on the app home page never moved under the cursor or the keyboard. Hover and focus takevar(--phase-learn-very-light)as the fill and keepvar(--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-shotprompt(); either outcome retires the button for the visit,appinstalledretires 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 beforeDOMContentLoaded); 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-wrapperhad noposition, 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 ahiddenclass 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.hiddenutility.
August 2026
v2.14.7: ws_choice_cards counts columns from its container
- Fixed
ws_choice_cardsis no longer laid out against the window. The set wasrepeat(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 atzoom: 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 nowmax(240px, (100% - 2 * gap) / 3)withauto-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.--2and--1keep 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_barnow carry a tooltip. The component's docblock has always claimed tooltip behaviour, but an icon action shipped witharia-labelalone: named for assistive technology, silent to a pointer. Each one now also getsdata-tooltipfrom the same label plusdata-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 owndata-tooltipinattrskeeps it, and labelled actions are untouched (their text is already the name). No CSS was needed: the bar was alreadyoverflow: visibleand 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 atimages/icons/settings_light.svg. The stock weight-400settingsglyph 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__labeldropstext-decoration: line-throughand keeps the--text-mutedshift; 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'stext-decoration: nonereset went with it, being redundant now. - Improved The card's action right-aligns.
.ws-task-checklist__ctabecomes a flex row justified toflex-end, so the button lands under the rows' arrows at the card's trailing edge. Consumers that ship nocta(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-stepdescriptionorphase, andprogress => 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-artcarries 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 inimages/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 goes200x150fixed to216pxwide withheight: 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__textgoes 14px/17px to 16px/20px semibold on a single line. "Your DNA Profile" and "Discover your DNA Profile" lose their<br>and gainwhite-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_ctamedallions are transparent. The fiveimages/dna/profile/medallion-*.pngexports carried an opaque#F5F5F5plate, 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'sfilemtimecache-bust carries the new files. - Improved
.ws-dna-profile-cta__textgoes 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
smtodefault(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 originalsm.
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 arews_selectandws_input(textarea type included), footed byws_modal_footer; it posts JSON to the existingapi/contact.phpwith 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, noaria-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, anddata-app-footer-launcherplus thelauncheroption 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 toimages/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-lightfill,--phase-learn-darkborder and type,--radius-lg. New demo page atdesign-system/components/demos/dna-profile-cta.php. - Fixed The workshop listing's
[hidden]attribute now always wins..wl-card'sdisplay: 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 inworkshop-list.csscloses 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
footerlink onws_workshop_list()now renders throughws_button()as a small secondary variant with a right-handarrow_forwardicon, 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__allsurvives 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
filteroption onws_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 ownstagesscope 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
hiddenon the same rows is how a card goes missing; the replacement is single ownership: on a paged listing,workshop-list-pages.jsis 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 withpageSizerows 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): thelinear-gradientfrom--phase-learn-very-lightdown to--card-bgis gone; the card now paints a flatvar(--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 setsmargin-bottom: var(--space-6), replacing an inlinestyle="margin-bottom:0"inapp/views/plan/home.phpthat left the choice cards sitting directly under the subtitle. The standalone planner home keeps--space-10there, which is a page-opening gap rather than a heading-to-cards one. Note the shell renders underzoom: 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__bodypaints the drafting paper (app/css/app-shell.css):--bg-paperplus--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 reasonplatform-tokens.cssgives at the token: thebackground:shorthand wipesbackground-image, which is how five pages have silently lost the grid before. Scoped to this dialog, not to.ws-modal__bodyat large: the other Plan dialogs are forms, and a form on grid paper reads as a mistake. - Improved
.ac-sample-chipis 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 rendersws-btn ws-btn--secondary ws-btn--md, the same vocabulary as the "Check my agenda" primary two rows above it. The✨became aws-btn__iconholding Material Symbolsauto_awesome, since a raw emoji inside a design-system button was the tell that it was not one.#acSampleChipstays:agenda-check.jstoggles itshiddenas 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 inimages/dna/profile/. Deliberately chrome-free: the artwork carries its own border, padding and cream ground, so aws_cardwrapper would have drawn a second frame around the first. The image holds its slot withaspect-ratio: 27 / 10rather 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__dnarow 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_ctastill 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 afullvariant: 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$appFooterStylesOnlyso a documentation page can borrow the styles without consuming its one footer. - New
ws_task_checklist()gainsctaand a designed complete state. The in-card action renders throughws_buttonbelow 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'sbrandedvariant renders. The JSON has documentedvariant: brandedsince the catalog was written, but only the per-callheaderColorpath 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 beforeheaderColor. - New The workshop listing pages.
ws_workshop_list()acceptspageSize: rows past page one shiphidden,workshop-list-pages.jsdraws 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-herocopy, 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-bulkbarignored its ownhiddenattribute. The block setsdisplay: 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, becauseworkshop-list-select.jsreturns 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.
hiddensits on the list and confirmation wrappers, not on the buttons within them, so the popover-wide focus query inworkshop-list-menu.jswalked 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 inapi/plans.php, every one of them scoping its query touser_id = ?, so the menu now omits them on a shared card and offers Remove from my workshops instead, backed byremove_collaborator, which gained a plan-scoped self-service branch: one account can hold more than one accepted row on a plan (the unique key isplan_id+email), and dropping a single row would leave the workshop on the list. A row with noownedkey reads as not owned, matchingws_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__groupsis a five-column grid. The launcher now lists Learn, Strategize, Plan, Facilitate and Synthesize, and the grid was stillrepeat(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__panelwidens 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 atzoom: 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__namenow reservesvar(--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 inworkshopr-design-system.jsonwere all removed with the MyWorkshopr consolidation. It shipped as a second, older answer to a questionws_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-lifecycleCSS incomponents.cssstays —planner/includes/views/saved-body.phpstill 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 ofmyworkshopr/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_inputfor the eight fields,ws_radio_card_groupfor difficulty and the category picker,ws_chip_inputfor materials,ws_icon_buttonfor the duration stepper and the step deletes, and the platform Toast in place of a hand-built.ws-alerttemplate string. New sheet atapp/css/stuff-contribute.css, scoped to.app-contributeand painting entirely from platform tokens — the retired--exercise-accentorange is the one Learn red now, per Red Unification. The fixed right-hand drawer became a sticky column: the shell draws atzoom: 0.8inside its own scroll region, where a viewport-anchored drawer lands against the wrong edges. - Improved The
myworkshoprheader section is retired._wsh_sections()inincludes/components/header.phplost the key and its[data-section="myworkshopr"]tag-width rule,WS_APP_PATHSinhelpers.phplost the path, and the nav inventory atincludes/nav-example.phplost the row. Every/myworkshopr/URL is a 301 that renders no header at all, and an unknown section key already falls back tohome, 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-inputnow 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-gradientstarts 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-gradientand--planner-topbar-bgnow 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.cssrebuilt the gradient from--mainsite-primaryrather 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__btnhovered purple. It readvar(--color-primary-dark, #651FFF), and neither--color-primarynor--color-primary-darkexists inplatform-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 intocomponents.cssand was tokenised. It also referencedvar(--text, ...)andvar(--display, ...), neither of which is a token, so both always rode their literal fallbacks; they now read--font-bodyand--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
#FFFFFFliterals 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-standaloneand the.pv-toggleblock, 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_recorderandws_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-visibleindicator inplatform-tokens.css(WCAG 2.2 SC 2.4.7, Level AA). The file defined--focus-ringand documented at length why it is sky blue, but nothing applied it: the site's only global rule lives incss/styles.css, which a page gets only throughincludes/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-optionand.ws-choice-carddrew no focus ring at all. Both wroteoutline: 2px solid var(--focus-ring, #38BDF8), but--focus-ringis the whole shorthand, so the value expanded to2px solid 2px solid #0284C7and was discarded as invalid. Both now readoutline: 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__removewas 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__closewent from 22x22 to 24x24.--target-min: 24pxalready existed, annotated with the SC number, and nothing was using it. - Improved
css/tips/styles.cssimportsplatform-tokens.css, ascss/dna/styles.cssand 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--primaryis 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 inindex.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 withws_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#7C4DFFand Reflect emerald#10B981survived as hardcoded fallbacks behind--phase-plan,--phase-facilitateand--phase-reflect. Those tokens are#E54D4Dtoday 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
bodytakes--paper-grid-image/--paper-grid-sizeinstead of re-typing the two gradients.--grid-line-colorisrgba(44, 36, 22, 0.06)and the size is24px 24px, exactly what the page had, so this is identical pixels; the token also resolves tononeunder 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-cardonto the card tokens. Moving the CSS into a.cssfile made it visible tocheck-drift.phpfor the first time, which immediately failed it onpadding: 24px. It now reads--card-padding(20px, a real 4px change), plus--card-radiusand--card-shadowin place of--radius-xland--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 usingcomponents.css(footer.php269 lines,header.php159,section-hero.php109,dna-cta.php39,app-footer.php5), 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 ofapp/views/*.phpintoapp/css/app-shell.css: the.modal-overlayFOUC guard,.lp2-nudge*,.prvh-*and.pwa-install-banner*. Inline CSS in a PHP file is invisible tocheck-drift.php, which filters on the.cssextension, so the card rules in those blocks had drifted toborder-radius: 12px; padding: 18pxwith nothing to catch them. They now read--card-radiusand--card-padding, and the card hover reads--card-shadow-hoverinstead 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 ofapp/css/synthesize-home.css, anddashboard.css's.dash__rowsection were each orphaned by the same event: the page moved tows_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-pagedeleted 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 elevenhardcoded-hexviolations the file carried inpage-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 ownclamp()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.phpreads<style>blocks but only scans them for hex literals, so card geometry written inside a PHP file is still ungoverned. SeeAUDIT-2026-08-22-app-css-localization.md.
August 2026
v2.9.93: The 6C sequence strip and step chips
- New
.agenda-gen-6cincss/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::beforehairline 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-darkdot with a--planner-bg-whitering punching it out of the spine; a missing step gets a hollow dashed--planner-gray-400dot and its minutes column turns red. Grid is16px 1fr autoso the minutes stay right-aligned andtabular-numskeeps them from dancing. - New
.agenda-gen-c: the per-row step chip (Connect, Collect, Choose, Create, Commit, Celebrate) sitting next tows_activity_badgein the generated agenda row head. Brand red on--phase-learn-very-lightwith a--phase-learn-lightborder. - 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-tabsincss/planner/components/_modals.css: the modal'sws_tabsnav, flush under the modal header with a--planner-borderrule beneath it. The panes are stock.ws-tab-pane, and switching runs on the existingwsTabsInit/wsTabsSwitchpair inincludes/components/components.jsrather than a second implementation. Note thatcomponents.jsalready auto-runswsTabsInitover every.ws-tabsonDOMContentLoaded, so callers should only ever callwsTabsSwitch; callingwsTabsInitagain double-binds the click handlers. - Fixed Ghost icon buttons in an app-bar trail were reading dark on the red bar.
ws_icon_buttonrenders a.ws-btn--ghost, which inherits the default warm ink, while every.ws-app-bar__actionbeside it is--text-inversewhite. Added.workshop-planner .ws-app-bar__trail .ws-btn--ghostto_component-bridge.cssgiving 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-sectionand.planner-settings-section__titlefromcss/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-notestays (it still styles the signed-out and no-workshop captions) and.deck-settings-hintis 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-fieldelements 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-borderrule),.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 standardmodal-planner.phpshell withws_input/ws_select/ws_buttonfields, and the topbar gear is a stockws_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 vocabularyws_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 (designed→plan,delivered→synthesize,synthesized→synthesize), 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-lifecycleand.ws-lifecycle__stepsgainedflex-wrap: wrap, amax-width: 100%ceiling, and arow-gaptoken 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 atindex.html) rehearsing the unified app shell: white global header, dark icon rail, red per-app bar, and a bottom action rail. Every value is avar()againstplatform-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 tows_*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()andws_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 passhref, and brings the behaviour with it. - Improved
--card-paddingnow 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-cardand.ws-card--contentnow read the card tokens instead of hardcoding their own geometry. - New Check 4 in
check-drift.php: a card rule that setspaddingorborder-radiuswith a literal instead of a token fails the build. Ratcheted againstcard-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,__elementclasses and.card-shelfdo 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 throughws_button()rather than hand-cut. Built from thesynth-action-barframe, 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-motionnothing moves at all. Pass your ownnotes, 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-lightpaper,--color-warning-lightedge,--color-ink-secondaryink,--color-ink-faintrail.
August 2026
v2.9.86 — The app bar learns which app it is in
- New
'state'option onws_app_bar():strategize,plan,facilitateorsynthesize. 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 matchingstatevariant. - New
.ws-app-bar__controland.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
centeroractionsand 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 onws_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 bysyncRail()insynthesize.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 thedata-actvalues the delegated handler already had, so each action still has exactly one implementation. - Fixed A surface outside
#synth-appneeds its owndata-actlistener.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-shellis 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 asnull, 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 borrowingincludes/data.php'sformatDuration()— that file callsloadFromDatabase()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 inapp/css/workshop-list.css, behaviour inapp/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.phprow pluslifecycle_statusand 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-testruns the parser fixtures, including the historicalvar(--space-7)case that shipped a collapsed padding; accepted one-offs go in$KNOWN_DRIFTascss-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/xsscale (29 declarations were inheriting instead of sizing), the orange card/badge family plus two legacy surface and ink names instyles.css, admin's--admin-accent-*variants, MyWorkshopr v3's neutral vocabulary,--planner-primary-dark→--planner-red-dark,--color-amber-700defined beside its existing--color-amber-100, the unused.mw-svg-iconmask block removed, and the bounce-in animation riding--ease-outnow 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-tokenws_*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 incss/stsh-shell.css, and a document-head layout atincludes/components/stsh-header.php(registered in check-page's LAYOUT_FILES). Born in the Strategist; extracted when Synthesize adopted it. The Strategist'sst_*helpers now delegate here, and the/app/frame deliberately never loads it — its own sidenav is the chrome there. - New
.stsh-canvas--appvariant for viewport-locked tools: the canvas stops scrolling and hands its box to the app inside, keeping the app's own#main-contentsizing 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 standalonesynthesize/app.phpentry's legacy/anonymous landing and boot-failure fallback. What's left carrying the in-shell screen is entirely shared platform pattern: thews_app_bar, the.ws-tabscomponent 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-themeand 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.phpwith 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 realws_header(),ws_button(), andws_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-400rail (templateincludes/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 loadcomponents.css. Carriesaria-hidden="true": an emptycontentinfolandmark 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 directcomponents/footer.phpinclude and the legacyincludes/footer.phpall funnel through the composite template, soif (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 againstWS_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 ownsSCRIPT_NAME, so the real page is only visible viaREQUEST_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-shelltakeszoom: 0.8. This is the second attempt at a smaller frame and it is deliberately the other mechanism.zoomscales 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, 128var()fallbacks and 45 literals put back. - Fixed The body height is
125dvh, not100dvh.zoomscales the box after layout, so100dvhis 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
#A8A29Eborder in the shell —--gray-400, four steps up from the--gray-200it 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-cardand.synh-pickupare one object seen from five places, so they are named together inapp-shell.cssrather than edited in four files — which also leaves the standalone pages on the--gray-200they share with the rest of the site..dash__viz,.dash__ftrackand.dash__emptykeep 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-200to--gray-300; against a--gray-400rest state the same rule lightens the edge, so the card receded at the moment it lifted..app-shell .dash__row:hovertakes--gray-500and 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__eyebrowpill 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-darkcarries 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-headeris 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.cssrather than the component becausews_headerdraws 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-shelltoken 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-bodyresolved to 0.67rem against the platform's 80% root, so body copy painted at 8.6px. The tokens are inherited fromplatform-tokens.cssagain, which restores everyws_*component rendered inside the app at once. - Fixed The literals that were scaled to match are back too — 128
var()fallbacks acrossapp-shell.cssanddashboard.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_headeroverride 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-herotakespadding-top: 48px, and Strategize's.st-herotakes 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-heroonto.app-heroinherits it from the one rule.
August 2026
v2.9.69 — The sentence step opts out of the shell's reading measure
- Improved
.app-shell__contentwidens 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__ledeuses — 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 forcedzoomwas 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. Insteadbody.app-shellredeclares the spacing, radius, type and icon tokens at 0.67, which carries everyvar(--space-4, …)inapp/cssand everyws_*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_headeris 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.cssrestates 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-indicatormoves from--phase-learnto--phase-learn-lightand--ws-sidenav-on-indicatorfrom--phase-learn-lightto--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 inapp-shell.css—ws_sidenavitself 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-learnthe Strategist passes toheaderColor, same white title, same translucent close. It is bespoke markup rather than aws_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__groupsso a long list travels under a header that stays put, which is the splitws_modalmakes with aflex-shrink:0header 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__closeon a coloured header exactly: no border, no fill at rest, a 70% white glyph, and a 10% white wash on hover. The quarter turn isws_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__rowis 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__listis a flex column, the row is one line of icon → body → pill with the pill taking the slack onmargin-left:auto,.dash__phasesruns 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__ctarule now matches the one-line rule's specificity — an auto margin beatsjustify-selfand had stranded the pill on the far right of its own row. - Improved Every dashboard surface moves to
--bg-paperfrom--bg-surface—.dash__row,.dash__vizand.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_checklistcarries 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__journeyand.dash__firstrunand 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__ctareads "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
donein journey order, not its lifecycle stage. Marks are the shell rail's own, so a row and the rail agree: Strategizeknight, Plancalendar_view_day, Facilitateco_present, Synthesizelightbulb. Strategize waslightbulb_2in the rail, a near-twin of Synthesize's bulb; it is the knight in both now, which also picks up theknight-fill.svgactive state added in v2.9.64. - Fixed Facilitate rows rendered an empty icon tile.
co_presentis font-only andicon()returns an empty placeholder for it — the same trapws_buttonhit in v2.9.65.appDashboardIcon()inapp/includes/dashboard.phptakes the SVG when there is one and lets the font draw the ligature otherwise. - Retired
.dash__qis 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 arews_buttonprimary, so the dashboard carries no bespoke button CSS at all..dash__quickstays as the flex row that lays them out; the promotion ledger ondesign-system/redesign/dashboard.phprecords 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 thanimages/icons/, andicon()returns an empty<span>placeholder for anything it cannot find — soco_present,lightbulb_2and 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 fallbackws_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.svgasset wins when one exists, and everything else falls back to the Material Symbols font glyph with theFILLaxis on, which the platform's font request already loads.images/icons/knight-fill.svgships for the custom knight. Template:$renderActiveIconinincludes/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 aws_*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: 0because::afterotherwise 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 fromincludes/components/helpers.phplike every otherws_*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 atincludes/components/sidenav.php, styles under.ws-sidenav, documented at Redesign › Side Navigation. It pairs withws_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.cssoutrank 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) andexpanded(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
onSecondaryContainerwas 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-inkat 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-learnat 3.82:1 against white; it is now--phase-learn-darkat 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 aforced-colorsmapping toHighlight. - New
images/icons/knight.svg— a chess knight for the Strategize entry, drawn to the house 24px /0 -960 960 960convention and checked for legibility down to 20px. The platform's own Strategist glyph isstrategy, 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 setsdisplayon a element it also toggles withhidden. - 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=expandedor?both=1. - Fixed The rail clipped its own tooltips and grew a stray horizontal scrollbar. The list was set to
overflow-y: autowith a comment claimingoverflow-xstayed visible — CSS does not allow that, and computes the second axis toautoas soon as the first scrolls. Only the expanded variant scrolls now; the rail isoverflow: 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/lgrather 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 inimages/hero/under the<app>_hero.pngconvention. 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-2xlwith 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
#FAFAF9ground, it returned a warm cream#FBEDD9every 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__herobecomes a two-column grid (minmax(0,1fr)+minmax(0,26rem)); the oldmax-widthmoved 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 carriesalt="": it restates theh1, 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__closenow twists its icon a quarter turn on hover —--duration-base/--ease-outmicro-motion, guarded underprefers-reduced-motion. Applies to everyws_modalplatform-wide. - New The Strategist header now loads
components.js— it always hadcomponents.cssbut not the runtime, sowsModalOpen/wsModalClose(focus trap, inert background) didn't exist there. Its "Other…" sentence modal is the first consumer, using theheaderColorred header per the modal demo. - Improved
strategist.csspolish pass: panel hints and the practice page's reading text sit at--text-body(labels stay small);.st-toolnavretired with its markup; the exit strip steps up to--text-h2with a--mainsite-darkborder; the generated 60-second pitch renders as a--mainsite-very-lightcard; tool-card CTAs anchor bottom-right;.st-panel__hint--widelets 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.cssgains a marketing block (.st-land) for the new overview page, plus.st-page--tool,.st-subhead,.st-navlinkand.st-toolnavfor the tool pages. Platform tokens throughout; the only functional color is the unified Learn red. - Fixed A
max-width: 40chon the landing hero crushed the whole column to roughly 320px.chresolves 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, wherechmeasures 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__titleis theh1on 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-widtharrived with the app-shell metrics and was consumed bare —grid-template-columns: var(--app-rail-width) 1fr 300px. A barevar()that resolves to nothing is invalid at computed-value time, so the whole declaration is dropped andgrid-template-columnsreverts tonone; 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 to320px 804px 300pxwith all three columns side by side. - Fixed The same bare use existed in
synthesize.cssandcss/planner/base/_variables.css, so Synthesize and Planner carried the identical latent collapse. All four uses now carry the, 320pxfallback. - 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 withmax-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 withfilemtime()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.phpis included on every design-system page, they were dead from every page, not just one. The nav listed each component ascomponents/<name>while the pages actually live atcomponents/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 oncomponent-library.phpwas unaffected — it already used thedemos/path, which is why the break was easy to miss. - Improved The nav
slugnow 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_pageupdated 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 dialoginert— 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 intests/modal-focus.spec.js. - Improved
ws_alert()/ws_banner()now document their HTML-slot contract:$messagerenders unescaped by design (callers pass<strong>, links) — user-supplied data must be escaped withhtmlspecialchars()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.phpandadmin/system/banners.php(6 each),facilitator/app.php(6). Noteicon()emits a bare<svg>with no<title>, so<?= icon('close') ?>inside a button leaves it unnamed. - Fixed
check-page.phpcountedvar(--token, #hex)fallbacks as hardcoded hex — the same defectds-lint.mjshad 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 carriesalt=""(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 anyaltthat follows a PHP attribute. Real numbers: 1,133<img>, 40 withoutalt, 39 of them in ad creative, ebook HTML, or regex literals that merely contain the text<img. - Improved All eight
TODO: no token matchmarkers cleared fromcomponents.csswith 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#FFFBEBvs 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 underws_headeracross Planner, Facilitator, and Synthesize; tokens--app-bar-height,--app-rail-width(320px unified rails),--header-bg,--paper-grid-image;--bg-pagemoved to#FAFAF9with the drafting grid; ~575 lines of shell CSS incomponents.css. - New DESIGN.md §12.8 written.
synthesize.cssand thews_app_bardocblock 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 onews_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), andplatform-tokens.css. - Fixed Stale references to the deleted
global-header.phpremoved 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 oneenterResults()— 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()calledfocus()in the same tick as the.is-openclass, but.ws-modal-overlayanimates in fromvisibility: hiddenand 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. NewwsFocusWhenVisible()waits for the transition. One frame isn't enough: the overlay reportsvisibleat frame 2 while still atopacity: 0.01and only becomes focusable once painted. Confirmed in headless Chromium, not the preview pane — the pane is itselfvisibility:hiddenand can't measure this. - Fixed
wsModalClose()now restores focus to the element that opened the modal, guarded onisConnected. Previously focus dropped to<body>. - Fixed
closable: falsenow blocks Escape — the template emitsdata-closable="false"and the document handler honours it. Backdrop dismissal stays governed bybackdrop. - Fixed
wsFilterBarRemove()is now defined incomponents.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, resetspage, preserves the rest of the query string and the hash. - Fixed
ws_progressomitsaria-valuenowwhenindeterminate, per the ARIA spec — it was announcing a definite percentage for work of unknown length. Both progress animations now honourprefers-reduced-motion, matchingws-skeleton. - Fixed
aria-atomicmoved 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_selectrenders 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 anaria-labelwhenaddTextis omitted, so the icon-only form isn't an unnamed button. - New
tests/modal-focus.spec.jscovers all three modal fixes. Notestrategist/tests/sections.spec.jsis separately broken — it requiresjs/modules/sections.js, retired in v2.9.51 — so a barenpx playwright testfails before reaching anything else. - Improved Corrected a claim in the previous entry's doc pass:
ws_chip_input's label is properly bound viafor. 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 intemplates/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, andhelper— none are read byinput.php. Its select example also passed flatvalue => labelpairs, throwing Cannot access offset of type string on string;ws_input(type: 'select')wants a list of arrays, unlikews_select(). - Fixed Navigation described a
data-propertyattribute and.property-switcher-*/.ps-*classes that exist nowhere inheader.php. Rewritten againstws_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
clickonly. 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, oraria-labelledby, leaving the control unnamed. - Fixed Retired phase hexes removed from Level Up Grid (
#0284C7,#7C4DFF,#10B981), Navigation (plus#F87171,#BE123C), Button'svar()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:
showHeaderandscoreBadge.varianton Energy Arc Card,group_by_phaseandphase_copyon Level Up Grid,metaon Planner Card, and the phase variants on Banner. Energy Arc Card'sthemedefault corrected fromdarktolight.
July 2026
v2.9.51 — Strategist madlib rebuild
- New
strategist.cssrewritten 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-blankis a documented strategist-local control (likest-tabswas): an inline sentence blank thatws_input/ws_selectcan't express — they render labeled block fields. Baselined incheck-page; if a second app ever needs inline blanks, promote it to aws_*component. - Fixed Retired the three-phase shell CSS (docbar, phase tabs, steplist, wizard nav, readiness meter, rail). The
st-brief/st-docdocument blocks stay —brief-view.phprenders 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_rightandmore_vert— 22 of the 58 controls on the results screen, all sharing two names. NewdisclosureAttrs(what, open)emits the label andaria-expandedtogether, 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 isicon('close')announces as "close" — the wrong verb for a destructive control, and identical on every row of a list. NewremoveLabel(kind, text)insynthesize.jsgives 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()insynthesize.jsnow emitsrole="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 viaaria-labelledbyrather 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 becauserender()replacesroot.innerHTMLwholesale, 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-hintincss/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-onlyspoken form, since⌘Zdoes 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
#agendaItemsnow carriesrole="list"+aria-label, and each item anaria-describedbynaming 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, viaparse_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/synthesizeand anactiveHrefof/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
#anchorhrefs 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
activeHrefmust 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()emitsaria-current="page"on the nav item matching the current path or the page'sactiveHref. 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' => trueflag paints a section's call to action red — it is not a location marker, andaria-currentdeliberately does not follow it. Setting it on a plain destination lights two items with two different meanings, which is what happened ontemplates.php. UseactiveHreffor location. - Improved The
plannersection'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 tobrief/.
July 2026
v2.9.44 — Planner: named the Energy score, retired the mixed badge row
- New
.energy-arc__score-labelincss/planner/components/_canvas.css— a quiet caption naming the score pill, which otherwise rendered as a bare number discoverable only on hover. Carriesaria-hidden, since the button's ownaria-labelalready names the score; without it screen readers hear "Energy" twice. - Improved
.lp2-option__badgenow 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-btnincss/planner/components/_modals.css— the Undo control inside the agenda delete toast. Inherits the toast's white-on-blue (~8.6:1), and usesdisplay: inline-flexsomin-height/min-width: 24pxactually applies; on a plaininlinebutton the min-height is ignored and the target lands under the WCAG 2.5.8 floor. - Improved
modal-planner.phpnow emitsaria-label="Close {title}"andtype="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 carriedrole="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()inplanner-home.jsmirrors it exactly rather than inventing a second error style.
July 2026
v2.9.42 — Synthesize modals: entry animation belongs to opening
- Fixed
.synth-overlay'ssynth-fadeand.synth-modal'ssynth-poprestarted on every state change behind an open modal, because Synthesize re-renders by replacing the whole ofroot.innerHTML— the modal visibly flashed and reopened. New.synth-overlay--settledsetsanimation: noneon an overlay that was already on screen when the render started (snapshotOverlays()insynthesize.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 .menuand 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-raildrops its border and radius and stretches to the full height of the frame (.sy-appmoves fromalign-items: starttostretch). It is no longersticky: 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-heightresolves against nothing when every ancestor isheight: auto, so.synth-stage .synthis now a flex column and.wrap--app/.sy-apppass height down withflex: 1. - Improved
.wrap--apploses its top padding and trims the inherited 110px bottom pad to--space-6;.sy-maingoes topadding: var(--space-6) 0so 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'spaddingshorthand resets it at equal specificity. - Improved
.prio-traymoved 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.csswas already on every page viaincludes/header.php— the components were present all along, just unused. - New
wsAlert,wsEmpty,wsSkel,wsBadge,wsPillinsynthesize.js, beside the existingicon(). They mirror the markupincludes/components/*.phpproduces, because the PHP helpers can't be called from a client-rendered body. Keep them in step if those templates change. - Fixed The
.tabcollision is gone rather than worked around. Synthesize needed an explicit reset of the global pill rule's radius, shadow, shimmer::beforeand hover lift — same specificity, decided only by source order. Adopting.ws-tabs--underlineremoved 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-badgeand.cat-pillset a background inline from app state, so the rules that remain do nothing but keep the text legible on top of it. - Fixed
.ws-skeletonsets nodisplay, 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 nows_*helpers to demo; the page renders its class names directly againstsynthesize/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.phpgains 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 realws_segmented.
July 2026
v2.9.38 — Synthesize app frame: rail, working surface, AI panel
- New
.sy-appreplaces.sy-workas the shell every Synthesize working screen wears:var(--app-rail-width)rail +minmax(0, 1fr)surface, widening to a third340pxcolumn for the AI panel above1440px..sy-work__mainis now.sy-mainand keeps the drafting grid onvar(--bg-page). - New
.sy-ai— a panel that is a grid column on wide screens, a fixed right-edge sheet below1440px, and a bottom sheet below1080px. 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--addcolumn. - Fixed
.synth .tabnow resets the global.tabrule'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-rowin 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__mainkeeps the drafting grid but takesvar(--bg-page)(#FAFAF9) instead of the warmervar(--bg-paper), so the centre column and the page behind it read as one sheet rather than two paper stocks. - New
.sy-rail--addis now a panel onvar(--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),--rCardradius,--space-5padding — 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 fromvar(--planner-very-light)instead of a hardcodedrgba(229, 77, 77, 0.08). Same red family, now a solid#FEE2E2that matches the soft fills used elsewhere on the platform. - Improved Shared
?v=stamp on every planner@importbumped to20260726a, per the rule inplanner-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: #F0EBE3inplatform-tokens.css(mirrored asbackgrounds.headerin the JSON). The global header now sits a step deeper than the#FEF7F1page 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-headerrule and the inlinestyleattribute the component emits (the inline one wins, so they have to move together). Thebgoption 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.countemits sibling cards so it drops straight into an app's own grid. Adopted onplanner/planner-home.php,facilitator/facilitator-home.php, andsynthesize/synthesize-home.php, replacing three "Loading your workshops…" text lines. New.ws-skeleton-gridhelper 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 underws_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 theleadSlot,center,trailLead, andtrailSlotslots. Template inincludes/components/app-bar.php, CSS inincludes/components/components.css, registered inworkshopr-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_actionsblock fromfacilitator/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 thatws_lifecyclerenders, 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(compactfor card badges,fullwith an optional next-stepcta). Template inincludes/components/lifecycle.php, CSS inincludes/components/components.css, demo atdesign-system/components/demos/lifecycle.php, registered inworkshopr-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-skeletonshimmer 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-linewidth helpers. Emitted by the sharedFacilitatorSkeletonsJS builders so the live app and the/facilitator/loading-statesgallery render identical markup. - Fixed
.fac-live__bodygainedgrid-template-rows: 1fr+min-height: 0(andmin-height: 0on 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-itemcard anatomy (type-coloured.item-iconsquare,.item-time,.item-title,.item-duration-badge) plus the sharedws_activity_badge. Class names mirrorcss/planner/components/_cards.cssbut the rules are scoped under#start-agendaso 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--compactno 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-ctais nowtext-align: rightso the hero button shares the agenda's right edge with the header button. - Improved
--fac-hero-gradientretinted 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#3D0C0Ctovar(--color-ink)..fac-nav(live dashboard bottom bar) background changed from--bg-surfaceto--fac-ink-faint, with its tool buttons / note button / position / shortcuts text set to--fac-inkfor contrast. Added the missing--fac-ink-fainttoken (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-surfacepill, solid--gray-300border,--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-purpleborder/text,--fac-purple-very-lightfill) 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) fromcss/base/_backgrounds.css, instead of a flat--bg-pagefill.
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-loadedcomponents.css,_agenda-generative.css, and the.energy-arc-*card fromprep.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 behindprefers-reduced-motion.
July 2026
v2.9.28 — Icon set: calendar_view_day + right_panel_open
- New Added
calendar_view_day.svgandright_panel_open.svgtoimages/icons/(official Material Symbols outlines,fill="currentColor") — theicon()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 CSSdata-tooltip+aria-labelinstead of a labeledws_button.
July 2026
v2.9.27 — Strategist joins the global header
- New
ws_headergained astrategistsection (Plan phase,strategyicon, "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
100dvhwith 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
StSectionsbeside 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--lastclass toggle instrategist.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 ananimation-durationkill added to the landing's reduced-motion block. - New
.uni-dropvariant 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-learnfamily,--color-ink(-secondary),--bg-page/surface) instead of duplicating hex values; stale "red is a deliberate exception, like Intervention" comments insynthesize/index.phpandsynthesize.cssrewritten to reflect Red Unification. - Fixed Remaining off-scale values in
synthesize.cssmoved 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 inworkshopr-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 nowws_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.phpnow renders the composite viaws_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-navyand--uh-color-navyaliases 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 wasdisplay:nonebelow 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.cssreferenced 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-bounceincss/tokens/_variables.css, corrected per-app--ease-springoverrides (Tips, Podcasts, Academy, Tips v2) that had subverted the sanctioned token, and replaced the raw literals incss/styles.css, both_modals.csssheets,participant.css, and two planner pages withvar(--ease-spring). - Improved
ws_buttonnow has a:activepress scale on the base class (covers all variants); global header icon buttons gained a press scale + transition. - Improved Removed
transition: allfrom the shared component library and the planner card/sidebar/modal sheets (~71 rules), scoping each totransform/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.phpto.cta. An unscoped.ws-btn { border: none }was loading aftercomponents.cssand stripping the red border from every.ws-btn--secondaryon 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-lightaliases inplatform-tokens.cssto 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, andplanner/email-preview.phpthat 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
companionentry toappsinworkshopr-design-system.json, mirroring the shape of the existing app entries plus aplatformnote flagging it as a standalone native codebase with no CSS token. - New Added
mobile-ios.md, a concise reference mirroring the tone oftheming.md/motion-system.md, pointing toworkshopr-companion/DESIGN.mdandsrc/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-selectmove from the platform-default--gray-200to--gray-400, with--gray-500on hover. Scoped to.st-appso the sharedws-input/ws-selectcomponents are unchanged platform-wide.
July 2026
v2.9.15 — Strategist Buy-In wizard
- New Added
.st-wizardstepper nav and.st-wizard__footerBack/Next bar tostrategist/css/strategist.css, splitting the Buy-In phase's five.st-sectionblocks (instrategist/includes/phase-buyin.php) into wizard steps via.st-step/data-st-buyin-step. Active step uses--phase-learn-darktext on--phase-learn-very-lightchip fill; a touched-but-inactive step gets a quieter--gray-200chip. Step transitions are a 200ms fade+4px rise (.st-step--entering), disabled underprefers-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__headingcard and.st-docsbutton stack tostrategist/css/strategist.css, hosting the new Documents section (Executive Summary, Facilitator Briefing, Risk Report, Agenda Blueprint) instrategist/includes/sidebar.phpafter the Workshop Brief preview. Generated output reuses the existing.st-script/.st-skeletonpatterns 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-recogo/caution/not-yet recommendation banner tostrategist/css/strategist.css, hosted at#stRecommendationinstrategist/includes/sidebar.phpdirectly 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__inputinincludes/components/footer.php: solidvar(--bg-surface)fields with ink text andvar(--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-visiblering to the footer's local.ws-btn. - Fixed The footer now carries a scoped
.cta .sr-onlyfallback 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-badgerules tocss/planner/components/_topbar.cssfor the planner topbar's Strategist handoff link — a quiet white link with a light-red readiness pill (--phase-learn-very-lightground,--phase-learn-darktext) 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-viewand.st-brief--standalonerules tocss/strategist.cssfor 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..6category tints inplatform-tokens.csswere 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-redapp-tips.phpguide. Regeneratedtokens.dtcg.json; drift check passes. - Fixed Tips app: the local
--cat-*tokens incss/tips/styles.css(half-purple, half-red) and the--tips-cat-*fallbacks intips/index.phpare now the full brand-red ramp. - Fixed Migrated the live category colours: the 13 rows in the
tip_categoriesDB 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) fromvar(--text-h2, 1.5rem)to standardvar(--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") fromdivtags to semantich1tags 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 neutralvar(--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 inlineimg.iconelements 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.phpguide 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 incss/tips/styles.cssand are flagged for migration. - Fixed
app-hybrid.phpguide rewritten from the retired Plan blue (#0284C7) to the Learn Red family; the "Do" now points atvar(--phase-learn), matching the shipped/toolkits/hybrid/page. - Fixed Finished two half-migrated gradients —
collections-tabs.cssand_sidebar.cssno 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 inhelpers.php, templatetask-checklist.php, CSS incomponents.css, demo atcomponents/demos/task-checklist.php). Items carry real-data done states, optional phase eyebrows, links on undone items, a dismiss hook (data-checklist-dismiss), anddata-taskkeys for JS enhancement. Sky-blue focus ring,:activetactile 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.phppasses.
July 2026
v2.9.4 — Documentation consistency pass, round 2 (canonical docs + motion pair)
- Fixed
/DESIGN.md: frontmatterelevation.coloredre-synced to the all-red shipped shadows; motion frontmatter aligned to shipped easing tokens (deleted--ease-bounceremoved,--ease-reveal/--ease-springnamed 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.mdconverted 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 permotion-system.md), stray wrapping code fences removed. - Fixed
motion-system.mdpost-shipping prose: reveal ease is a token (--ease-reveal, P1);--ease-bouncemoved out of the live spec table (deleted, P2); the--ws-ease-bouncestray corrected from "defines" to "uses — defined nowhere (dangling)"; the participant reduced-motion spin exception marked as documented-intent-only (not implemented inparticipant.css). - Improved
theming.mdexceptions table records the planner activity-type colors as a sanctioned functional-coding exception;ADOPTION.mdfont 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.jsonphase mapping: Coach moved fromfacilitatetoreflect(it had inverted the "Coach = Reflect, never swap" rule); DNA markedstandalone.check-drift.phppasses (54/54 components, 9 apps). - Fixed
workshopr-style-guide.mdreconciled to rank-4: deference banner to/DESIGN.mdadded; 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.mdrewritten 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.mdarchitecture line and counts (54ws_*helpers, 44 demos incomponents/demos/);ADOPTION.mdinternal cool-gray contradiction resolved (≈25 remain in planner trees, not "all replaced");DOC-TEMPLATE.mdstale "Tips purple" exception example;START-HERE.mdcounts, §15 changelog pointer, and pre-D1 "phase color discipline" wording;theming.mddead motion.md link. - Fixed
/motion.mdillustrative token block aligned to shipped tokens (--duration-reveal800ms,--duration-reveal-lg1200ms,--stagger-reveal; deleted-overshoot curve removed); deprecatedtaste.md/principles.md/forms.mdreduced to pointer stubs.
July 2026
v2.9.2 — Audit B1 participant bridge + lint-blind indigo cleanup
- Fixed Audit B1:
css/facilitator/participant.cssreferenced 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 (pertheming.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-lintmisses because indigo isn't a signature phase hex:briefing.css(#536dfe/#4338ca),_sidebar.cssand_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.csssection comments and stale/* #7C4DFF */annotations, the JSONphasesblock (was still blue/purple/emerald), and theds-lint.mjsphase 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
:rootdeduped (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-bouncedeleted (zero consumers;--animate-scalerepointed to--ease-spring). P3:--transition-*deprecated. P4:--ease-springdocumented 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-statusmaturity 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.phpis 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.cssstill 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 inapps.facilitator.colorNote; the poll-bar gradient's raw#A78BFAwas replaced withvar(--fac-purple-dark).ds-lintis 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_buttonrow), 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__errornotice (error tokens) instead of a nativealert(); nativeconfirm()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.mjsphase map now encodes the documented app color exceptions (Planner, DNA, Intervention, Facilitator → red; Tips → purple) instead of flagging shipped red aswrong-phase. Full-repo violations dropped from 156 (mostly false positives) to 83 genuine legacy items. - New Pre-commit hook (
.git/hooks/pre-commit) runsds-lint.mjs --stagedso 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 incomponents.js;ws_modalrefactored 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_stepsunderdesign-system/components/demos/, registered on the component-library index (whose cards now link todemos/— the old links 404'd). - Improved Facilitator red completed on remaining surfaces:
css/facilitator/facilitator.css,participant.css, andsummary.cssnow resolve accents to--mainsite-*tokens (purple fallbacks removed). Stale "facilitator = purple" comment inplatform-tokens.csscorrected.
July 2026
v2.8.8 — Facilitator app red confirmed + live poll cards
- Improved Corrected
apps.facilitator.colorNoteinworkshopr-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.csskeeps the legacy--fac-purple*variable names but resolves them to red. Also updated thephaseColors.facilitatenote. - New Poll result card styles (
.fac-poll,.fac-poll__bar, etc.) infacilitator/css/facilitator.cssfor 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 thews-card,ws-activity-badge,ws-badge(duration),ws-suggestion-card,ws-stat,ws-alert,ws-empty-stateandws-skeletonmarkup instead of bespoke.generator-itemHTML — and drops all inlineonclickin favor of delegated listeners. - New Layout-only stylesheet
css/planner/components/_agenda-generative.css(imported byplanner-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-cardviaAgendaRenderer.swapAltCard()— removed the bespoke.ai-swap-alt-cardmarkup 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:rootstill win. - Improved Documented in
workshopr-design-system.jsonthat 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
synthesizesection in the global header (ws_header) — iconhub, 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/relatednotes to full top-level component entries inworkshopr-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) fromAUDIT-2026-07-01-app-vs-design-system.md.
July 2026
v2.8.3 — app-tips.php color fix
- Fixed
app-tips.phpstill 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 inplatform-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.phpstill showed sky blue. The v2.8.0 Planner recolor (blue → Learn red) updatedplatform-tokens.cssand 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.mdwas 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 toplatform-tokens.cssfor hex values instead of mirroring them (mirroring was the drift source). Fixed the same stale planner/DNA colors inworkshopr-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.mdis the single router (source-of-truth hierarchy + on-ramps);README.mdis 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 inworkshopr-style-guide.md(tips categories → purple, intervention → red, training → red), and fixedapp-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-*inplatform-tokens.css,apps.plannerin the JSON (with acolorNote), and the phase table + a new exception bullet inDESIGN.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 thatplatform-tokens.cssand the app actually use. - Fixed Motion easing contradiction. Canonical reveal/spring ease is now
cubic-bezier(0.22, 1, 0.36, 1)acrossDESIGN.md, the JSON, andmotion.md(was split between two curves). Addedstandard(0.4, 0, 0.2, 1) for micro UI transitions. - Improved
motion.mdrewritten 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 JSONzIndex. - 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-facilitatefallbacks from stale#7C3AEDto the real#7C4DFF. BumpedDESIGN.mdlast-reconciledto 2026-07-01. Full audit atdesign-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) inplatform-tokens.css, theapps.interventionblock inworkshopr-design-system.json(with acolorNote), and theapp-intervention.phpguide. 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
myworkshoprsection tows_header(iconspace_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-loadsplatform-tokens.cssso 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-radiusnow matches the button (10px), inputs bumped to 16px / roomier padding (no iOS zoom), added input hover states, a.cta__noteprivacy 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.phpwith loading/success/error states. Added a.cta__hphoneypot field and.cta__msg/.cta__msg--errorstatus-message styles.
June 2026
v2.7.13 — Larger .section-title display headings
- Improved
.section-titledisplay headings increased ~25%: theclamp(28px, 4vw, 38px)rule (Intervention, Podcasts, Tips) is nowclamp(35px, 5vw, 48px); DNA moved--text-3xl→--text-4xl; shared-components and Academy usecalc(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 nowposition: sticky; top: 0instead ofposition: relative, so the global navigation pins to the top of the viewport on scroll. Existingz-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-sectioncarouselconfig (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, andprefers-reduced-motionsupport. CSS/JS are namespaced under.wsh-carouseland self-emitted once. First used by the Academy hero to showcase all six ebook covers. Each slide also supports an optional top-right statuspill({label, tone}—availablegreen /soonred).
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-heronamespace 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.phpisrequire_once'd by bothhelpers.phpandcomponents/header.php, so any page with the global header can render a section hero without pulling in the full helpers. - Fixed
ws_headernow 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_heroto 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) andvisualChips(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 pagebodybackground, preserving each page'sbackground-color. Added to the home, library, academy, tips, and planner landing index pages;ws_section_heroalso 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_herobuttons now accept an optionalicon(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_headerbut don't loadincludes/icon.php— the header component now loads the inline-SVGicon()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 andprefers-reduced-motionusers 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.csspreviously 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 tobody.planner-page/.planner-app-activeso it beats the:roottoken re-imports. Note: aliases declared: var(--planner-primary)at:rootare 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-h2to--text-h3/--text-small, with a pulsingauto_awesomesparkle 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-metaso controls match the planner UI scale. Added a:-webkit-autofilloverride so autofilled fields stay on-theme instead of browser blue. Shared planner@importstamp bumped20260610 → 20260615. - Improved Generator output view got the same treatment — agenda item names dropped from
--text-h2to--text-h4, start times / headers / rationale / refine input from--text-h3to--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-h2to--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=YYYYMMDDstamp. 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 onplanner-new.cssnever invalidated the files it@imports, and the server caches CSS for 7 days — so production visitors kept week-old partials (collapsed.planner-bodyflex layout, exposed card edit panels). Every@importinplanner-new.css(and theplatform-tokens.cssimport insidebase/_variables.css) now carries a shared?v=YYYYMMDDstamp. 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.cssthe single canonical planner bundle. The partially-guttedplanner-v2.cssmonolith (1,042DUPLICATE/EXTRACTEDmarkers) was retired and deleted; every selector it still held was already present in the imported component set, except the.sr-onlya11y utility which was migrated intoplanner-new.css.planner/includes/header.phpnow loads the one bundle on the modular path (the deadplanner.csslink and the temporary standalone_modals.csspatch 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
remand spacing ispx, so a roothtml { font-size: 80% }shrinks all rem text while leaving layout intact. Added toplatform-tokens.cssand 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 same0.8so it stays uniform. No token values changed, so the JSON mirror stays in sync (drift check passes). Inlinepxfont 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 acrosscss/detail-immersive.css,css/styles.css,css/planner/planner-v2.css,css/planner/live-mode.css, andcss/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 itsmargin: -var(--space-8) auto 0(and with it theautocentering) was ignored. The correct form for negating a token iscalc(-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 andws_*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:rootand survives the lateplatform-tokens.css@importinassets/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/anddesign-system/components/react/— portable implementations of everyws_*component (49) and all 6 documented patterns. PHP ships as namespacedWorkshopr\DS\*standalone functions (collision-free with the globalws_*helpers); React ships as typed.tsxfunction components (Ws<Name>+Ws<Name>Props). - Improved Both flavors render the existing
ws-*class names fromcomponents.css(single source of truth) — no duplicated CSS, no hardcoded colors/sizes/fonts. Shared helpers:_icon.php(icon/cx/attrs/e) andIcon.tsx/cx.ts. - New Barrels:
php/index.php(requires all 50 components, 59Workshopr\DSfunctions) andreact/index.ts(52 exports), plus aREADME.mdwith usage and a patterns-CSS caveat. All PHP verified withphp -l+ render smoke tests.
June 2026
v2.6.0 — AI-enablement: enforcer fix, enriched catalog, DTCG export
- Fixed The
design-system-enforcerMCP pointed at a stale/sites/newphpdir 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 + 49ws_*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.phpnow enrichescomponents.generated.jsonwith a canonicalexample,whenToUse/whenNotToUse, ana11ynote, and a derivedantiPatternperws_*helper — extracted from thecomponents/*.phpdemos. 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.php→tokens.dtcg.json: all tokens in W3C DTCG format with$typeinference,$descriptionfrom CSS comments, andvar()→{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--checkmodes wired into npm, the CIds-driftjob, 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-sizeresolves 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 becomesvar(--text-*, <token-rem>)so the fallback matches the snapped size. Most deltas are sub-pixel; the visible ones are28px→24px(62×),22px→20px(46×),36px→32px, and40px→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: 14px→font-size: var(--text-small, 14px). Value-preserving, idempotent, and safe on pages that don't loadplatform-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, hero36–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 vianpm 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#fffby context (color:→--text-inverse, else--bg-surface); and skips hexes already invar()fallback position so it never produces redundantvar(--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
--primaryand 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
#E54D4Dfallbacks (facilitator--fac-red=--color-error; planner--planner-red= documented mainsite-CTA alias;--phase-learnstatus colors). Tokenized them (drop literal, keep the var) for zero visual change; mapped the live-mode--run-dangertoken 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.mdfor intent;platform-tokens.css+helpers.phpfor 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 anyws_*helper is undocumented in the JSON, or if phase-color tokens disagree betweenplatform-tokens.cssand the JSON. Known source contradictions (tips, training) are allowlisted as visible warnings pending a design decision. - Improved JSON
metanow references the canonical/DESIGN.mdand/motion.md, declares itself a hand-maintained mirror (correcting the earlier “auto-generated” claim), and points to the drift guard. - Improved
README.mdnow defers toSTART-HERE.mdand 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 acolorNotein the JSON, and both passcheck-drift.phpwithout an allowlist. - New Added the
--facilitator-primarytoken family (aliased to--phase-facilitate,#7C4DFF) — facilitator was the only app missing its--{app}-primarytoken. The JSON facilitator color was corrected from the stray#7C3AEDto the phase purple, matching coach/intervention. - Improved
check-drift.phpnow resolves one level of CSSvar()indirection, so phase-aliased app tokens validate. Wired into governance:npm run lint:ds:drift, ads-driftPHP job in the Design System Lint GitHub workflow, and a staged-file gate in the pre-commit hook. - Improved
check-drift.phpgained a reverse check — a JSON component with no matchingws_*function (renamed/removed) now fails the build — and reports component counts. Corrected stale counts inREADME.md(39 demo pages, 49 helpers, not “34”/“40+”), and alignedGEMINI.mdwith the consolidatedDESIGN.md(it still pointed at the deprecated taste/principles/forms files). - New
generate-catalog.php+components.generated.json— a real generator that derives aws_*component roster (signature, summary, declared options) straight from thehelpers.phpdocblocks. The curated JSON keeps its rich hand-authored option metadata; the generated file is the can’t-drift companion.--checkmode fails on a stale roster or a curated signature that no longer matches source, wired intonpm run lint:ds:catalog, the CIds-driftjob, and the pre-commit hook. Also added the canonical-source banner tovoice.php/philosophy.phppointing atDESIGN.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, andcss/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/-bgvariants where used).
May 2026
v2.1.1 — Planner sidebar: prototype-matched toolbar + flat suggested list
- New
.sidebar-toolbarcomponent for the planner's activity sidebar — sticky search input + pill-style type tabs. Lives incss/planner/components/_sidebar.css(mirrored atcss/components/_sidebar.css). - New
.side__group/.side__group-title/.side__list/.side__more/.side__group-emptystructural classes for the flat "Suggested for X" list and "Browse all activities" footer link. - New
.library-card--libvariant — 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-sidebarso 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) viacolor-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 HTMLhiddenattribute.
May 2026
v2.1.0 — Level Up Grid: phase grouping + button --half
- New
ws_level_up_grid()now supports an optionalgroup_by_phase => truemode 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 viaphase_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--3modifier for 3-up phase rows (used by Learn and Plan phases). Existing 4-up grid stays the default. - New
.ws-btn--halfwidth modifier (50%) — sized for narrow card columns where--fullreads as too dominant. - Improved Responsive overrides updated so both
.ws-level-up__gridand.ws-level-up__grid--3step down 4 → 3 → 2 → 1 column, and.ws-level-up__phase-headstacks below 1024px. - Improved Demo:
/design-system/components/demos/level-up-grid.phpnow showcases the grouped variant. Button demo cheatsheet lists--half.
April 2026
v2.0.0 — Planner ws_* Component Migration (Phase 5)
- Improved Migrated
planner/edit.phpbuttons and form inputs from raw HTML tows_button()andws_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.phpwithws_badge()andws_duration() - Improved Standardized
planner/saved.phpbuttons withws_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-*)andvar(--text-sm)
April 2026
v1.9.1 — Planner Brand Color Tokenization
- New Added
--planner-overlay-light/medium/heavyand--planner-backdrop/backdrop-lighttokens to planner variables - Improved Tokenized standalone
#E54D4D(red),#22C55E(green),#9333EA(purple),#F59E0B(warning) hex values across planner CSS to usevar(--planner-*)andvar(--color-*)tokens - Improved Replaced standalone
#2563EBin planner cards and generator modal withvar(--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-50through--gray-900) exclusively - Improved Added
helpers.phpto globalheader.php— all pages now have automatic access tows_*component helpers without manual includes - Improved Migrated
/contact/form from raw HTML tows_input(),ws_select(),ws_button()components - Fixed Replaced 6 hardcoded
font-family: 'Bitter'in MyWorkshopr CSS withvar(--font-heading) - Fixed Cleaned up 2
DM Sansfont 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.mdfor ongoing adoption enforcement
April 2026
v1.8.4 — Live-Mode Font-Size Normalization
- Improved Normalized all hardcoded
font-sizevalues inlive-mode.cssto design system tokens: compact type scale variables for UI chrome,remunits for presentation display elements, icon sizes preserved aspx
April 2026
v1.8.3 — Planner Font-Weight & Font-Size Normalization
- Improved Converted 9 hardcoded
font-sizepx values toremunits inrun-mode.cssandworkshop-studio.css - Improved Replaced all hardcoded
font-weightnumeric 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: allin 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-visibleoutlines 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--secondaryhover 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--secondarydefault background changed from opaque white (--bg-surface) totransparentfor 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.phpwith 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.phpdocumentation - 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.phpand 4 inchangelog.phpreplaced with warm stone palette - Fixed
--phase-reflect-lightcorrected to #A7F3D0; missing-very-lightvariants 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-badgecomponent CSS moved out ofplatform-tokens.cssintocomponents.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-codeblocks (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.phpandabout/index.phpto 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