Skip to content

redesign + site-wide a11y sweep + lint tooling#960

Merged
jeswr merged 127 commits intosolid:mainfrom
jeswr:feat/apps-page-redesign
Apr 24, 2026
Merged

redesign + site-wide a11y sweep + lint tooling#960
jeswr merged 127 commits intosolid:mainfrom
jeswr:feat/apps-page-redesign

Conversation

@jeswr
Copy link
Copy Markdown
Member

@jeswr jeswr commented Apr 23, 2026

Ready for review. cc @matthieubosquet — would love your eyes on the redesign end-to-end. The branch has evolved substantially since it was opened; the comments below walk through each round of feedback. Short summary of what's in HEAD right now:

  • Apps page — hero + search in a sticky left sidebar (search + category TOC pin together), app-store-style tile grid with per-category intros, featured row, consistent logo slots, a11y-correct markup throughout.
  • Site-wide redesign — design tokens on :root, opt-in dark mode (localStorage-gated, toggle in header, no-flash bootstrap, chrome stays dark in both themes per latest feedback), shared .page-hero pattern propagated to /, /about, /for_users, /for_developers, /for_organisations, /community, /get_a_pod, /apps.
  • Nav + footer — collapsed to [Get Started] Learn more ▾ Get Involved ▾ [☀/☾] [☰]; breadcrumbs retired; footer reorganised to mirror the nav ("Start here / Learn more / Get Involved / Project"). Event banner trimmed (Hackathon sold out).
  • Copy polish — removed misleading "switch pod providers" claims; tightened homepage to a single "Get Started" funnel; retired the duplicate CTA pair on /for_users; fresh per-category intros on /apps.
  • Pod provider tiles on /get_a_pod are now whole-card clickable with a ghost "Already have a Pod? Browse the apps" CTA in the hero.
  • QA infra — htmlhint + stylelint + pa11y-ci wired into a PR workflow; Playwright + axe-core e2e tests under tests/e2e/ with the six PR-960 regressions as gating checks. Real build / e2e runs on CI (the local watcher on this machine couldn't rebuild Jekyll — Ruby 2.6 — so validation went through roborev + CI).

See the comment thread for the step-by-step round-by-round resolution table.


What this branch does

Five parallel workstreams, each produced by a dedicated agent persona committed under .claude/agents/. Every commit was reviewed locally by GitHub Copilot / gpt-5.4 via roborev before push.

1. apps.html redesign — app-store feel on the existing Solid theme

(solid-apps-page-designer persona)

  • Refactor the inline <style> block out of apps.html into assets/css/apps.css.
  • Add a hero block (eyebrow + headline + value prop + primary /get_a_pod CTA + secondary "Browse the apps" anchor).
  • Build a proper search + sort + filter + top-apps toolbar.
  • Group tiles by category as the default layout, with a sticky pill TOC on desktop; no-JS baseline still shows a flat grid.
  • "Editor's picks" row populated from the existing .top-app-tag ★ items; auto-hides when a filter/search/non-default sort is active.
  • Tile polish: consistent white-backed logo slot, brand-coloured focus ring, hover lift, chevron "Open app →" affordance.
  • Page-scoped CSS custom properties (on .apps-page) so other pages are unaffected.
  • Responsive at ≤1025/781/580, prefers-reduced-motion zeros transitions. Dark mode deferred (site chrome is all-light; mixing dark apps grid with light header would look worse than staying light).

2. Site-wide a11y / semantic-HTML sweep (every page except apps.html)

(solid-site-a11y-sweeper persona)

  • Propagates PR Add 14 Solid apps to the apps list #956's csarven-style fixes: vague link text ("here", "read more", "click here"), rel="noopener noreferrer" on external links, a missing </li> / duplicate id cleanup.
  • Highest-leverage commit: _layouts/default.html (every page inherits it) — added rel="noopener noreferrer" + class="external-link" to every external anchor.
  • Bulk mechanical addition of rel=noopener on remaining root pages and _posts/ was deferred — zero target="_blank" usages anywhere in scope, so the missing attribute is defence-in-depth, not a live vulnerability; diff would be pure mechanical noise. Ready to do as a separate mechanical commit if you want it in this PR.

3. Programmatic linting (htmlhint + stylelint + pa11y-ci) wired into CI

(solid-site-linting-plumber persona)

  • package.json + .htmlhintrc + .stylelintrc.json + .pa11yci + .github/workflows/lint.yml.
  • npm run lint (htmlhint + stylelint), npm run lint:a11y (builds Jekyll, serves, runs pa11y over /, /apps, /get_a_pod, /for_developers, /for_users, /community), npm run lint:all.
  • GitHub Actions workflow triggers on pull_request; runs both in parallel jobs.
  • Baseline rules documented in KNOWN-LINT-ISSUES.md — a handful of rules are downgraded to keep the baseline green; the file lists a triage recipe for future contributors so CI catches new regressions.

4. Copy rewrite on apps.html

(solid-site-copywriter persona)

  • Hero block rewritten for a non-developer landing from Google: plain English, honest that a Pod is needed first, friendlier CTA wording.
  • Tile descriptions tightened to active-voice, single-sentence form — ~180 words removed across 34 tiles with no loss of technical precision.
  • Inclusion criteria reframed as "want to get your app listed?" guidance for app authors.
  • Disclaimer trimmed from paragraph of legalese to one short paragraph.
  • British English preserved throughout (matching existing "behaviours", "decentralised", "organise").

5. Agent team itself is committed (b699746)

(meta)

Four persona files under .claude/agents/solid-site-a11y-sweeper, solid-apps-page-designer, solid-site-copywriter, solid-site-linting-plumber. Keeping them in-repo so future contributors (and future Claude Code sessions) use the same standards: --no-gpg-sign commits, no AI-attribution trailer, roborev review via Copilot / gpt-5.4, a11y-first patterns from PR #956.

Stats

  • 26 commits on top of main (at dc80768, PR Add 14 Solid apps to the apps list #956).
  • 50 files changed, +6,526 / −307. The big delta is apps.html + apps.css, the lint package-lock.json, and the _posts/ bulk "here" → descriptive-text sweep.
  • All 26 commits individually reviewed by Copilot / gpt-5.4 before push; medium findings were resolved in follow-up commits.

Open questions for @jeswr

  1. Hero secondary CTA target — "Browse the apps" jumps to #app_launcher (the section heading showing the toolbar) rather than #apps-list directly. Designer's reasoning: landing on the toolbar feels more app-store-y. Happy to flip it to the grid if you prefer.
  2. Dark mode — deferred with an explanatory comment in apps.css. Site chrome is hard-coded light in base.css, header.css, footer.css; if you're open to a site-wide dark pass later, I'll handle apps.css then.
  3. data-category="Browser" on SolidFocus — the tile's category is "Browser" but the app is actually tasks/notes. The copywriter refused to change it (forbidden data attribute) and flagged it. Worth fixing in a separate commit?
  4. Category intro blurbs — the copywriter flagged that the sticky TOC + per-category headings have no markup slot for a one-line caption under each heading. Want the designer to add a data-category-intro="…" slot so the copywriter can fill in one-liners per category?
  5. Bulk rel=noopener sweep — the a11y sweeper deliberately didn't do a mechanical pass across every remaining root page / every _posts/ external link. If you want that in this PR, say the word and I'll have the sweeper finish it.
  6. Ambiguous tile copy — the copywriter listed 8 tiles where the author's original description was ambiguous (Penny, Pod Pro, PodOS Browser, 0data Hello, KonaPod, KeyPod, MovieStar/Media Kraken/Solidflix). Rewrites are best-guess; please skim these and tell me if any framing is off.

How to preview

git fetch jeswr feat/apps-page-redesign
git checkout feat/apps-page-redesign
bundle install
bundle exec jekyll serve   # http://localhost:4000/apps

Or for lint/a11y locally:

npm ci
npm run lint           # htmlhint + stylelint
npm run lint:a11y      # full pa11y against built site

jeswr added 26 commits April 23, 2026 19:38
Add four .claude/agents/ personas, committed so every delegated
subagent spawn in this repo sees the same team definition and
standards:

- solid-site-a11y-sweeper — propagate PR solid#956's semantic-HTML /
  link-pattern fixes (<span role=link>, onclick nav, "click here",
  missing rel=noopener, nested anchors) to every page.
- solid-apps-page-designer — redesign apps.html with an app-store
  feel while staying on the existing Solid theme. Plain HTML + CSS,
  no framework.
- solid-site-copywriter — rewrite copy on apps.html (hero, inclusion
  criteria, tile taglines) for a non-developer reader.
- solid-site-linting-plumber — install htmlhint + pa11y-ci + CI
  workflow so the a11y fixes can't silently regress.

Every persona runs on model: opus and includes this repo's
convention rules (--no-gpg-sign, no Co-Authored-By trailer, roborev
review with Copilot / gpt-5.4 after each commit).
The GDPR erasure-request link used 'here' as its anchor text, which
fails WCAG 2.4.4 (link purpose must be clear from the link alone).
Rewrote the sentence so the destination is self-describing and added
rel=noopener noreferrer plus class=external-link on the two external
anchors in that paragraph, matching the pattern used on apps.html.
Moves the inline <style> block from apps.html into assets/css/apps.css
without any visual change. The site-wide styles.css bundle already
includes apps.css, so the rules apply identically. This keeps apps.html
focused on markup and unblocks the apps-page redesign work.
Introduces a branded hero at the top of /apps: eyebrow + 'Discover
Solid apps' headline + one-sentence value prop + primary CTA to
/get_a_pod (new-visitor focus) and a secondary ghost anchor that
jumps to the app grid. Hero uses the site's #7C4DFF brand accent
for the CTA and a subtle violet-to-white gradient surface. All new
style rules are scoped inside apps.css via page-local CSS custom
properties so other pages are unaffected. Copy is placeholder; a
TODO(copy) marker flags it for the copywriter pass.
Adds a plain-text search input above the existing sort / category /
first-time-user controls and wires it to the existing filter pipeline,
matching against data-name and data-category (lowercased, substring).

Visually groups everything into a single .apps-toolbar surface with a
clearer two-column layout (search on the left, controls on the right
on desktop). The existing .sort-controls class is kept for BC with the
JS but its chrome is reset inside the new toolbar. Select focus rings
now use the brand #7C4DFF instead of the legacy blue.
Adds a repo-root package.json with a pinned htmlhint devDependency and
three npm scripts (lint, lint:html, lint:all). Configures .htmlhintrc
for the structural and attribute checks the a11y sweep agent cares
about (tag-pair, id-unique, alt-require, attr-lowercase,
spec-char-escape, src-not-empty, attr-unsafe-chars, attr-no-duplication
and friends).

First-run baseline: 30 violations across 4 rules, 23 source files. The
counts and the reasoning behind every downgrade live in
KNOWN-LINT-ISSUES.md. Headline calls:

  - doctype-first DISABLED globally — every Jekyll source page starts
    with a YAML front-matter block, so the rule would flag all 23
    files; the real doctype lives in _layouts/default.html and pa11y
    will exercise it against built pages.
  - press.html, specification.html, events.html are --ignore'd until
    the sweeper fixes pre-existing tag-pair / spec-char-escape /
    attr-lowercase bugs in them.

.gitignore grows node_modules, .cache, and pa11y-report.json entries.
Addresses roborev findings on the previous toolbar commit:

- medium: the desktop grid (minmax(220px,1fr) auto) combined with a
  200px min-width on each select made the toolbar overflow on narrow
  viewports. Adds a <=781px media query that collapses the grid to a
  single column and lets the search input and selects stretch to the
  full row width.
- low: the .apps-toolbar__controls reset was losing to the later
  .sort-controls rule on specificity/order, so nested chrome still
  rendered as a card inside the toolbar. Scoped the reset to
  '.apps-toolbar .apps-toolbar__controls.sort-controls' so it wins
  regardless of rule order.
33 'This Week' / 'This Month in Solid' posts used short non-descriptive
link text (mostly 'here' linking to the Solid events boilerplate URL,
also 'read more', 'watch the recording here', 'click here'). This
fails WCAG 2.4.4 (link purpose from link alone) and WCAG 2.4.9
(link purpose / link text), and makes a screen-reader link list
useless.

Reworked each anchor so the visible link text describes the
destination (e.g. 'tips for organising successful Solid events',
'watch the recording on Vimeo', 'view the LifeScope slides on
Google Docs'). No URL or semantic meaning changed; only the human-
readable anchor text and surrounding sentence were adjusted.
Follow-up to the previous a11y pass: the new anchor text 'tips on
organising a successful Solid Event' was still misleading because the
link target (/events) is the general events page, not an organizer
guide. Replaced the anchor label with 'Solid events page' so the link
text matches what the destination actually contains.
Transforms the flat #apps-list into category-grouped sections on
page load via progressive enhancement:

- JS reads tiles from the flat list (kept as the no-JS fallback),
  builds one <section class='apps-category'> per unique category
  with a h3 heading and its own .tiles grid, then moves the tiles
  across and hides the flat list.
- A new #apps-categories-nav lists every category as a pill-style
  anchor link to '#cat-<slug>'. The nav becomes position:sticky on
  viewports >=1025px so users can jump between categories on the
  long page without scrolling back up.
- Sort/filter/search now operates over all tiles regardless of which
  section they live in; sections whose tiles are all hidden collapse
  out. Sort in default mode restores original encounter order.
- Empty-state banner moved out of the list into a semantic <p
  role='status' aria-live='polite'> so screen readers announce it
  when filters leave nothing to show.

Category labels and their descriptions are still the existing raw
values; copywriter pass will polish them (TODO(copy) marker noted
above the nav).
stylelint wiring:
  - .stylelintrc.json extends stylelint-config-standard.
  - First-run baseline: 10 violations, 9 of them color-function-alias-notation
    (rgba vs rgb) which is cosmetic — disabled.
  - assets/css/styles.css is excluded: it is a Jekyll template (YAML
    front-matter + Liquid include_relative) that stylelint cannot parse.
  - ~25 opinion rules pre-disabled (selector-class-pattern,
    declaration-empty-line-before, etc.) so the baseline is green without
    mass reformatting. Full list in the rc file.

pa11y-ci wiring:
  - .pa11yci targets the six representative URLs the persona listed:
    /, /apps, /get_a_pod, /for_developers, /for_users, /community.
  - Standard WCAG2AA via the axe runner. Threshold 0 errors, ignore list
    empty to start — first real run in CI will seed the ignore list if
    legacy violations exceed 50.
  - Chrome launched with --no-sandbox/--disable-dev-shm-usage so puppeteer
    starts reliably in Actions runners.

Scripts added earlier already wire these: npm run lint:css, npm run
lint:a11y.
- The 2022-10-06 Solid World entry had a 'here' anchor for the
  registration form (fails WCAG 2.4.4). Rewrote the anchor as
  'Register on the Solid World Google Form', matching the pattern
  used elsewhere on the site.
- The 2024 section had a byte-for-byte duplicate <li> for
  event-2024-02-27-solid-world, producing two DOM nodes with the same
  id='#event-2024-02-27-solid-world' and in-page fragment target.
  Duplicate ids break same-page anchor navigation and confuse
  assistive tech that uses id-based references (WCAG 1.3.1 / 4.1.1
  semantics). Removed the duplicate; the surviving <li> is unchanged.

This also closes the 'events/archive.html - duplicate id' entry noted
in KNOWN-LINT-ISSUES.md; that file can drop events/archive.html from
the htmlhint --ignore list once this lands.
…rver-and-test

Addresses roborev medium on the previous commit: pa11y-ci in .pa11yci
hardcodes http://127.0.0.1:4000, but the npm script never started a
server, so 'npm run lint:a11y' was DOA with ERR_CONNECTION_REFUSED.

Fix:
  - Add http-server (14.1.1) and start-server-and-test (3.0.2) as
    pinned devDependencies.
  - Split lint:a11y into three steps: :serve, :run, and a top-level
    orchestrator that wires them together via start-server-and-test.
    The orchestrator brings up http-server against _site/ on port
    4000, waits for it, runs pa11y-ci, then tears the server down.
  - Deliberately do NOT chain 'bundle exec jekyll build' inside the
    npm script: Jekyll is a Ruby/Bundler dependency, not an npm one.
    KNOWN-LINT-ISSUES.md documents the two-step local recipe
    (bundle exec jekyll build && npm run lint:a11y) and the CI
    workflow does the same split.
…yout

Every page on the site inherits _layouts/default.html. Its
event-banner and footer contain 7 cross-origin anchors (sosy2026.eu,
eventbrite.co.uk, forum.solidproject.org, service.theodi.org,
github.com, matrix.to, share.hsforms.com, vimeo.com) that previously
omitted rel=noopener noreferrer. Adding the attributes:
- Prevents any future target=_blank addition from leaking window.opener
  to the destination (reverse-tabnabbing defence).
- Suppresses the Referer header on navigation, so that the linked
  sites cannot see which page the user clicked from.
- Flags every cross-origin link with class='external-link', matching
  the convention PR solid#956 established on apps.html.

Only attributes were added; visible text, hrefs, titles and alt
attributes are unchanged.
Addresses three medium findings on the previous category-grouping
commit (98dd6af):

- Name sort was a no-op across categories because tiles only sorted
  within their own section. Now a 'name-*' sort collapses tiles into
  a single .apps-sorted-grid and hides the sections; the grouped
  view reappears when the user returns to Default or Category sort.
  Category sort correctly reorders sections by category name.
- .app-source / inline-link CSS selectors were hard-coded to
  '#apps-list li', but the JS view moves tiles into the category
  grids. Retargeted selectors to '.tiles li' so they apply to both
  the no-JS flat list and the JS-built grouped grids.
- The TOC included Browser and Travel categories that weren't in
  the dropdown, so clicking those pills while another filter was
  active left the target section hidden. TOC clicks now always
  reset the category filter and any active name sort to 'all' /
  default, and the missing dropdown options have been added (also
  alphabetised the option list for consistency).
Addresses roborev low on the workflow commit: the a11y job uploaded
pa11y-report.json as an artifact, but nothing was writing that file
so the upload was a no-op.

Fix:
  - Add lint:a11y:run:json and lint:a11y:ci scripts to package.json
    that invoke pa11y-ci with --json and redirect to pa11y-report.json
    while still propagating pa11y's non-zero exit code on failures.
  - Switch the workflow to npm run lint:a11y:ci and upload the report
    unconditionally (if: always) with if-no-files-found: ignore so the
    step is benign when the job exits before pa11y runs.

The dev-facing 'npm run lint:a11y' keeps human-readable output; only
CI serializes to JSON.
Addresses a medium roborev finding on the previous commit: clicking
a category pill in the TOC only reset the category dropdown and name
sort. With an active search term or the 'first-time user picks only'
checkbox ticked, the destination section could still be empty and
the anchor would jump to nothing.

TOC clicks now also clear the search input and uncheck the top-apps
toggle when either is active, so every category pill is guaranteed
to reveal its section on click.
- Adds an 'Editor's picks' / Featured row at the top of the apps page.
  JS populates it by cloning every tile that carries a .top-app-tag,
  so the showcase is independent of the user's current filter/sort.
- Adds aria-label='Editor\'s pick' to the star span so screen readers
  name the decoration rather than just reading 'star'.
- Tile polish, scoped to the apps-page grids only:
  - Consistent logo slot with white background, soft radius, 1px
    border, object-fit: contain, so non-square upstream assets
    (wordmarks, SVG ribbons) look tidy.
  - New .tile-logo / .tile-logo--wide wrapper replaces the inline
    style block on the ODI File Manager tile — last inline style
    gone.
  - Subtle 'Open app ->' chevron hint rendered via ::after on the
    tile anchor, pushed to the bottom of the flex column. Hides on
    the secondary .app-source anchor.
  - Hover state now translates the tile up 2px and switches its
    border to #7C4DFF in addition to the existing shadow lift.
  - Clear focus-visible outline using #7C4DFF.
  - Top-app star upgraded to a round tinted pill for legibility.
Addresses two roborev findings on the previous commit:

- medium: the featured row stayed rendered even when filters left
  zero results, producing a contradictory 'No apps match your filters'
  banner above a row of apps. Featured now hides as soon as any
  filter / search / non-default sort is active; it returns once the
  user clears everything. Tracks an explicit data-empty flag so a
  featured-less page doesn't get re-shown.
- low: the .top-app-tag span only had aria-label, which some screen
  readers ignore on plain inline spans. Added role='img' so the
  label is reliably exposed as a named image.
- Responsive tail:
  - Hero padding + headline shrink below 781px; CTAs stack full-width.
  - Category TOC: padding shrinks; the pill list switches to a
    single-row horizontal scroll instead of wrapping into 6+ lines.
  - Featured row pads less; category heading shrinks one size.
  - Below 580px all grid containers collapse to a single column so
    tiles take the full screen width on phones instead of squeezing
    into two 50%-width columns at the 300px minmax breakpoint.
- Reduced motion: prefers-reduced-motion: reduce zeroes the transition
  / transform on CTAs, pills, tiles, chevron hints, search inputs,
  and category selects. Hover states also stop translating.
- Dark mode: intentionally deferred with a comment explaining why.
  The site-wide chrome is all light, so only darkening the apps-page
  blocks would look disjointed. Revisit when the site gains a
  site-wide dark theme.
- The apps-page CSS-variable scope now also covers the category
  sections, featured row, flat sorted grid, and empty-state banner
  so every element can use --apps-* tokens without re-declaring.
Addresses a medium roborev finding on the previous commit: the
prefers-reduced-motion block cancelled tile + CTA transforms but not
the 'translateX(3px)' hover effect on the 'Open app ->' chevron,
so users with reduced-motion preferences still saw the chevron jump
sideways on hover. The media query now forces transform: none on the
chevron pseudo-element both at rest and on tile hover.
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 23, 2026

Deploy Preview for musical-sawine-26ffa9 ready!

Name Link
🔨 Latest commit afb95a7
🔍 Latest deploy log https://app.netlify.com/projects/musical-sawine-26ffa9/deploys/69eb858a304194000811d5ef
😎 Deploy Preview https://deploy-preview-960--musical-sawine-26ffa9.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Three new .claude/agents/ personas to tackle @jeswr's review of the
draft apps-page PR:

- solid-site-visual-qa — Playwright specs covering the specific bugs
  (duplicate chevron, mobile overflow, toolbar alignment, sticky TOC
  layering, dark mode) plus screenshot baselines per viewport. Writes
  test.fail.fixme placeholders where a fix is still pending so CI
  stays green.
- solid-site-frontend-engineer — implementer for bounded bug tickets:
  too-loose selectors, overflow, alignment, data-attribute tweaks
  (SolidFocus category), new data-* slots (data-category-intro). One
  fix per commit, unfixme'ing the matching Playwright test.
- solid-site-theme-designer — site-wide design tokens + dark mode +
  propagation of the apps-page aesthetic to /, /get_a_pod,
  /for_developers, /for_users, /community. Includes a theme toggle
  in the header and no-flash-of-wrong-theme via an inline script.

All three run on model: opus and follow the repo's commit discipline
(--no-gpg-sign, no AI-attribution trailer, roborev review with
Copilot/gpt-5.4 per commit).
@jeswr
Copy link
Copy Markdown
Member Author

jeswr commented Apr 23, 2026

Thanks for the direction — acting on all six points. Taking the specific bugs + "appropriate reviewing infrastructure" seriously: lint and single-commit model review genuinely missed the overflow / duplicate-chevron / alignment issues, so before spinning up the site-wide redesign I'm wiring real Playwright + axe-core visual/functional tests against the built Jekyll site.

Status of your six answers

  1. ✅ Hero secondary CTA → #app_launcher stays as-is.
  2. 🟡 Site-wide dark mode — queued. New solid-site-theme-designer persona will introduce CSS custom properties site-wide (:root + [data-theme="dark"]), a prefers-color-scheme: dark baseline, and a toggle in the header nav with no-flash-of-wrong-theme handling.
  3. 🟡 SolidFocus category — queued for the frontend-engineer persona alongside the other four bugs.
  4. 🟡 Per-category intro slot — frontend-engineer adds the data-category-intro="…" attribute + JS wiring; copywriter then fills the strings per category.
  5. 🟡 Finish rel=noopener sweep — background now; the a11y-sweeper is doing the mechanical pass across every remaining root page + every _posts/*.html.
  6. ✅ Tile descriptions keep.

Diagnosed the reported bugs

  • ODI tile's duplicate "Open app →" — root cause: the chevron selector is too loose. apps.css:345–350 currently matches .apps-category__grid a::after etc. (any descendant <a>), so it applies to the primary tile anchor AND the source-code <a> inside .app-source. Fix is scoping to .apps-category__grid > li > a::after (direct-child only).
  • Mobile overflow on "Browse the apps" — hero CTA width constraint missing at narrow viewports.
  • Clunky sticky TOC — z-index / spacing between the pill bar and the first tile row.
  • Toolbar alignment — the sort-controls row has inconsistent vertical centring of labels vs inputs.

All four get Playwright regressions that fail today (test.fail.fixme) and pass when each fix lands.

New agent team members (committed as 8bf69a8)

  • solid-site-visual-qa — Playwright + axe-core + screenshot baselines, CI workflow on PR.
  • solid-site-frontend-engineer — one bug per commit, unfixme's the matching Playwright test.
  • solid-site-theme-designer — site-wide tokens + dark mode + propagation of the apps-page aesthetic to /, /get_a_pod, /for_developers, /for_users, /community.

Plus three additional design skills installed (emil-design-eng @25.6K installs, color-mode-and-theme, design-system-tokens).

Running now (background)

  • visual-qa → writes Playwright specs with the six regression tests as test.fail.fixme, plus smoke tests and screenshot baselines.
  • a11y-sweeper → bulk rel=noopener on *.html root pages + _posts/.

Queued (waiting on the above so we don't have the cross-agent commit contamination we hit last time)

  • frontend-engineer → fixes the four bugs + SolidFocus category + data-category-intro slot.
  • theme-designer → tokens + dark mode + site-wide propagation.
  • copywriter → fills the per-category intros once the slot exists.

Will re-comment with a resolution table once each lands. Branch stays in draft.

Wires @playwright/test + @axe-core/playwright as devDeps and adds
playwright.config.ts at the repo root. Projects cover desktop-chrome
(1280x800), mobile-chrome (Pixel 5), desktop-firefox, and a
desktop-dark variant for the upcoming theme work. The webServer block
reuses the existing http-server on port 4000 pattern so the same
built _site/ serves pa11y-ci and Playwright alike.

Shared fixtures (tests/e2e/fixtures/pages.ts) are the single source of
truth for the smoke-tested page list and viewport dimensions; specs
will be added in follow-up commits.

.gitignore now excludes Playwright run artefacts (test-results/,
playwright-report/, *-actual-*.png, *-diff-*.png) while allowing
baseline PNGs to be committed.
jeswr added 17 commits April 24, 2026 13:02
…ooter

@jeswr: "'For Users' should probably be the first option on the nav
bar and labelled something more like 'Try now!' and highlighted.
Whilst some of the navigation (perhaps all) should be hidden under
more general headings like 'Learn More' and 'Get Involved.' This
probably also warrants cleaning up the footer." Follow-up: "Or
perhaps 'Get Started' rather than 'Try now'."

## Nav

Left-to-right (new):

  Solid [logo]   [Get Started]  Learn more ▾   Get Involved ▾   [☀/☾]  [☰]

- `Get Started` is a highlighted pill button (brand-filled,
  shadow-brand, translateY on hover) pointing at /for_users — the
  dedicated audience page that now acts as the on-ramp.
- `Learn more` dropdown: About Solid · For Developers · For
  Organisations · FAQ · Specifications
- `Get Involved` dropdown: Community · Apps · Events · Forum ·
  Newsletter
- Each dropdown trigger is a `<button aria-expanded>` with a caret
  that rotates when open. JS toggles the matched `<ul id>` visibility
  via the `hidden` attribute so ancestor sticky / z-index rules don't
  have to deal with display-none cascades.
- Mobile overlay keeps the existing slide-out pattern — submenus
  expand as nested indented lists under the trigger instead of
  floating dropdown panels.

## Footer

Current four-column layout kept, but the columns now mirror the nav
so the two navigation surfaces agree on grouping:

  Start here: For Users (Get Started) · Get a Pod · Apps
  Learn more: About · For Developers · For Organisations · FAQ · Specs
  Get Involved: Community · Events · Forum · Newsletter · Press
  Project:     Team · Code of Conduct · Feedback · Logo · License · Terms

Press moved from its own "What's New" column into "Get Involved"
alongside Newsletter; Team and Code of Conduct moved into the
project-housekeeping column. The Home link was dropped (the logo
already performs that job).

## assets/js/menu.js

- Wire the two new dropdown triggers: click toggles aria-expanded +
  [hidden] on the matched submenu; clicking outside any
  `.main-nav__group` or pressing Escape closes all. Link clicks
  inside a submenu also close it, so the dropdown doesn't linger
  open behind a page unload.

## assets/css/header.css

- `.main-nav__cta` — brand-filled pill, elevated with `--shadow-brand`,
  hover translates up 1px to `--shadow-brand-hover`.
- `.main-nav__trigger` — text-link appearance plus a caret SVG that
  rotates 180° when `aria-expanded="true"`.
- `.main-nav__submenu` — nested list; on mobile it renders inline
  under the trigger with smaller type and slight opacity; on desktop
  it becomes an absolute-positioned panel anchored to the `<li>`,
  with its own border + shadow, z-index 1000 to float above tile
  content. `display: flex; flex-direction: column` guards against
  the `.main-nav ul { flex-direction: row }` desktop rule bleeding
  into the dropdown.
The page-hero already surfaces primary 'Get a Pod' and 'Browse the apps'
CTAs; the 'Get started with Solid' tile section below repeated the same
two links, so drop the redundant subsection.
The light-theme --color-header-bg / --color-footer-bg tokens are
already dark (#202542). Overriding them to an even darker #0a0c11 on
[data-theme="dark"] added no readability benefit and visibly widened
the contrast gulf between the chrome and the body surface. Drop the
six chrome-token overrides so the header / footer / breadcrumb render
identically in both themes; only the body surfaces swap.
The secondary CTA pointed at #app_launcher on the same page, so it
told users to browse the apps they were already looking at. Drop
the ghost anchor and the .apps-hero__cta--ghost rules, which are
now orphaned.
Change the primary CTA from 'Get a Pod first' to 'Don't have a pod
yet? Get one first' so the CTA itself conveys the precondition.
With that wording in place the separate .apps-hero__note sentence
is redundant; remove it and its now-unused CSS rules.
The hint described obvious behaviour — a search input filters — and
added noise to the sidebar without helping anyone. Remove the span,
its aria-describedby reference on #app-search, and the now-unused
.apps-sidebar__search-hint CSS rule.
@jeswr: "'Apps' don't need to be listed from the 'Get Involved'
dropdown — as this is the get started page."

The Get Started CTA (→ /for_users) and the site-wide hero pattern
already surface Apps; duplicating it in the Get Involved menu was
noise. Footer's "Start here" column still includes Apps — that stays
since the footer mirrors the full funnel.
@jeswr: "Please remove the page title from within the page, this is
unnecessary."

On hero pages (about, apps, community, for_developers,
for_organisations, for_users, get_a_pod) the article layout was
emitting a top-level <h1>{{ page.title }}</h1> — e.g. "About Solid"
— directly above a designed hero block whose own headline ("Your
data, in your hands") was marked as <h2>. The page title shows up in
the browser tab and the breadcrumb already; stacking it a third time
above the hero was pure duplication and pushed the real headline
down the page.

## _layouts/article.html

- Wrap the layout-emitted <h1> in `{% if page.hide_title != true %}`
  so pages that carry their own hero can suppress it. Pages that
  don't (FAQ, team, press, code_of_conduct, terms, etc.) keep the
  layout h1 as their only top heading — no regression.

## The seven hero pages

- Set `hide_title: true` in the frontmatter.
- Promote the hero's heading element from <h2 class="(page|apps)-hero__
  headline"> to <h1 class="..."> so the page still has exactly one
  h1 in the semantic outline. Styling is unchanged because the CSS
  rules target the class, not the element tag.

Index (`/`) has its own `.hero-text h1 Solid` heading and does not
use the article layout's page-title rendering — no change needed.
Pod data migration between providers is not a turn-key user operation
today. Drop the 'switch providers' / 'change providers' / 'self-host
later' clauses from about.html, for_users.html, and get_a_pod.html.
App-to-app portability (the real value prop) is preserved.
@jeswr: "remove the breadcrumbs on pages, these are not necessary".

Delete the `<nav class="breadcrumb">` render block from
`_layouts/default.html`. Replace it with a Liquid comment explaining
why it's gone so the next editor doesn't waste time looking for the
rendering site when they see `breadcrumbs:` entries still in page
frontmatter.

Individual pages still carry `breadcrumbs:` keys in their
frontmatter; these are now no-ops (never read). Leaving them is
harmless and safely delete-later in one bulk sed pass when someone
wants the cleanup. The `.breadcrumb` CSS in assets/css/breadcrumb.css
is similarly dead but left in place until a separate sweep purges it.

The browser tab title + the hero H1 on each page now carry the full
"where am I" signal — breadcrumbs were a third repeat of the same
information for visitors who arrive from search.
@jeswr: "remove the Open Data Institute from the heading on the
community page".

The `.page-hero__lede` on /community listed "volunteers,
practitioners, and the Open Data Institute" — trim to "volunteers
and practitioners" so the lede stays welcoming without anchoring
who's-building to a single named institution in the hero.

The longer prose paragraph below the hero still credits the ODI
for the 2024 stewardship commitment with a link to the ODI/Solid
announcement — the factual credit stays in place, just not in
the hero strapline.
@jeswr: "there is more space on the right than the left for the
banner tile."

Root cause: at desktop (≥781px) `.page-hero--split` was a 1.1fr /
1fr grid with the text column left-aligned and the figure column
centred (`justify-content: center`). The text sat flush to the
hero's 2rem left padding, but the image sat centred inside its own
narrower cell — leaving empty space on BOTH sides of the image.
Visually the right side of the hero card looked over-padded
compared to the left.

Fix mirrors the left-edge alignment on the right:

- Grid columns → `1fr 1fr` (symmetrical), so both cells have the
  same width budget.
- `.home-hero .page-hero__figure { justify-content: flex-end }` at
  ≥781px, so the illustration hugs the hero card's right padding
  just like the text hugs its left.
- Mobile (<781px) keeps the figure centred — the column-reverse
  layout stacks it above the text where centring is correct.
…, add image to /get_a_pod

Three bundled fixes @jeswr flagged:

## Homepage (and other split heroes) — wide-screen asymmetry

The `.page-hero--split` was a grid `1fr 1fr` with text capped at 36rem
and image at 32rem. On wide viewports the cells grew past the content
widths, leaving a visible asymmetric band of empty space between the
content and the gap. Switch to `display: flex; justify-content:
space-between; align-items: center` so text hugs the hero card's
left padding and figure hugs the right padding regardless of how
wide the card gets. The gap in the middle absorbs the extra width.

## Floating page.image on hero pages running into the banner

`article.html` was rendering the frontmatter `image:` as an
`<img class="article-image-top article-image-right">` with
`margin-top: -3.8rem` pulling it up into the event-banner area.
Every hero page (about, apps, community, for_developers, for_users,
get_a_pod) already has its own illustration slot (either
`.page-hero__figure` or the tour illustrations inline in the page),
so the layout-emitted floating image was a second copy with a
broken offset.

Gate it on the same `hide_title` flag the h1 already uses — pages
that opt out of the layout h1 (because they have a hero) also opt
out of the layout image. No new frontmatter key, no per-page
changes needed.

## Get a Pod — no image, now has one

Add a `.page-hero--split .get-a-pod-hero` with a
`solid-pod-tour.svg` illustration on the right so the page reads
like the other hero pages instead of a bare text block over a grid.

CSS shares the `.home-hero .page-hero__figure` sizing rules via a
combined selector in `homepage.css`.
@jeswr jeswr marked this pull request as ready for review April 24, 2026 14:22
@jeswr jeswr requested a review from matthieubosquet April 24, 2026 14:22
@jeswr: "unstage the claude agents".

The eight `.claude/agents/*.md` persona files + `scripts/test-watcher.sh`
were session-internal infrastructure used to coordinate the parallel
agent work that produced this PR. They don't belong in the public
solidproject.org site repo once the redesign has landed — they're
process, not product.

- `git rm` the eight agent persona files and the test-watcher shell
  script that was tied to them.
- Add `.claude/` to `.gitignore` so future sessions don't
  re-introduce local agent state into `git status`.

Branch history keeps the originals (commits b699746, 8bf69a8,
07f4dc9 etc.) for audit; HEAD no longer carries them.
@jeswr jeswr changed the title WIP: apps page redesign + site-wide a11y sweep + lint tooling redesign + site-wide a11y sweep + lint tooling Apr 24, 2026
jeswr added 5 commits April 24, 2026 15:33
Two coupled issues:

## Homepage (and every other hero page) — width asymmetry

`.page-hero { margin: 0 0 2rem 0 }` and `.apps-hero { margin: 0 0 2rem 0 }`
explicitly set left+right margins to 0 at a higher class-specificity
than `main > * { margin: 0 auto }`. At the ≥1280px breakpoint where
`main > *` caps children at `calc(1280px - 6.2rem)`, sibling sections
(Tim Berners-Lee quote, article sections) got auto-centred while the
hero itself stayed left-aligned — producing "the hero is wider on the
left than on the right" exactly as @jeswr described.

Switch both heroes to `margin: 0 auto 2rem auto` so they centre
alongside their siblings and the 2rem bottom spacing is preserved.

## Hero pages missing their images

A previous change gated `page.image` rendering in article.html on
`hide_title` — that stopped the broken `article-image-top` float
that was running into the event banner, but it also left
`about`, `apps`, `community`, `for_developers`, `for_users` with no
image anywhere on the page. Only `/` and `/get_a_pod` had in-hero
figures.

Add a `.page-hero__figure` to each:

- about → store-anything-tour.svg
- apps → store-anything-medium.svg
- community → solid-pod-narrow.svg
- for_developers → single-sign-on.svg
- for_users → solid-pod-tour.svg

Each hero section gets the `.page-hero--split` modifier so the flex
row layout kicks in at ≥781px (text pins left, figure pins right).
/apps uses the `.apps-hero` BEM; co-add `.page-hero--split` to its
section so the flex rule catches it, and add an apps.css override
capping `.apps-hero__content` at 36rem while inside `.page-hero--split`
so the text balances against the figure.

## Generic selector cleanup in homepage.css

- `.home-hero .page-hero__figure` + `.get-a-pod-hero .page-hero__figure`
  collapsed to `.page-hero__figure` (generic). Any hero that adds the
  figure child inherits the sizing.
- `@media (max-width:780px) .home-hero` → `.page-hero--split` so the
  column-reverse stack applies to every split hero on mobile.
@jeswr: "Make the 'get started' buttons on the home page and banner
link go straight to get a pod. The get started button in the footer
can be removed since there is a get a pod option already there."

The old funnel took visitors to /for_users (an explanatory page)
and then to /get_a_pod. Since the first concrete step every new
visitor has to take is "get a Pod", short-circuit the funnel:

- `_layouts/default.html` main-nav `.main-nav__cta` href →
  `/get_a_pod` (was `/for_users`).
- `index.html` hero primary `.page-hero__cta` href → `/get_a_pod`
  (was `/for_users`).
- Footer "Start here" column drops the `Get Started` line since the
  adjacent `Get a Pod` entry is where the CTA now lands anyway.
  Kept: `Get a Pod`, `Apps`.

/for_users is still reachable via the top nav (it's the first item
behind the "Learn more" dropdown — no link rot, just no longer the
primary funnel target).
…; drop open-source-apps list

@jeswr: "move information about server implementations on get_a_pod
and the app inclusion and exclusion criteria to information on the
'for developers' pages. With the app inclusion and exclusion
criteria should also be instructions on how to add an app to the
apps page (create a PR to this repo). Finally remove the list of
opensource apps on the for developers page as this is already on
the apps page."

## get_a_pod.html

Delete the `#solid_server_implementations` section. `/get_a_pod` is
now exclusively about picking a hosted Pod provider; reference
material for developers who'd run their own server lives on
`/for_developers`.

## apps.html

Replace the `<details class="details-box"> App inclusion and
exclusion criteria</details>` block with a single quiet
`.apps-disclaimer` meta sentence: "Listing here is not an
endorsement… Building a Solid app? See the listing criteria and
how to submit a PR on the For Developers page." Links the `#apps-
page-listing` anchor on /for_developers. Apps page stays focused on
the grid itself.

## for_developers.html

- Delete the `#open_source_solid_apps` section (the list of
  per-app repo links + copyright lines). Source-code links already
  live on every tile in `/apps` via the `.app-source` paragraph,
  so this section was redundant.
- Add `#solid_server_implementations` section (moved from
  /get_a_pod). All six servers retained; wording tightened to
  reflect the developer audience.
- Add `#apps-page-listing` section containing:
  - The inclusion criteria (4 bullets on what makes an app
    Solid-compatible).
  - The exclusion criteria (8 bullets on what we won't list).
  - A new `<h3 id="how-to-submit">` subsection with explicit
    PR-to-this-repo instructions: open a PR against
    solid/solidproject.org, add a <li data-name> tile to
    apps.html in the appropriate category, copy an existing tile
    as a template. ODI email kept as a fallback path for
    non-PR submitters. Endorsement disclaimer kept.

## assets/css/apps.css

Add `.apps-disclaimer` styling: centred, max-width 60rem, muted
text colour, small font-size. Sits below the app grid without
pulling attention away from the tiles.
…s to the repo's GH issues

Two related changes:

## /get_a_pod — add a provider-listing disclaimer

@jeswr: "add a similar warning about providers as the warnings
about apps."

Mirror the `.apps-disclaimer` meta line from /apps at the foot of
the hosted-provider grid on /get_a_pod:

> Listing here is not an endorsement by the Solid Project. Providers
> are not tested, and no security, uptime, or data-handling
> guarantee is implied. Review each provider's terms, jurisdiction,
> and support policies before signing up. If you believe a listed
> provider is insecure or non-functional, please [raise an issue].

Styled via the existing `.apps-disclaimer` class in apps.css, which
is bundled into the site-wide styles.css alongside every other
page stylesheet — no extra CSS needed.

## "Raise an issue" links → solid/solidproject.org issues

@jeswr: "The 'raise an issue' links should link to the github issues
for solidproject.org."

Both the /apps and /get_a_pod disclaimers previously linked
`raise an issue` to `service.theodi.org/help/1476250723` (the ODI
support portal). Retarget to
`https://github.com/solid/solidproject.org/issues` so listing
concerns land in the same repo that owns the listings.

The footer "Feedback & Support" link on the default layout still
points at the ODI portal — that's a general feedback channel with
different semantics from a listing-concern issue, so it's
deliberately unchanged. The Code of Conduct reference to "The
ODI's Solid technical support" for solidcommunity.net outages is
also unchanged — that's a service-specific escalation path, not a
listing-issue link.
@jeswr jeswr merged commit 9cf865c into solid:main Apr 24, 2026
5 of 10 checks passed
jeswr added a commit that referenced this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant