redesign + site-wide a11y sweep + lint tooling#960
Conversation
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.
✅ Deploy Preview for musical-sawine-26ffa9 ready!
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).
|
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
Diagnosed the reported bugs
All four get Playwright regressions that fail today ( New agent team members (committed as
|
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.
…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: "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.
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.
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:
:root, opt-in dark mode (localStorage-gated, toggle in header, no-flash bootstrap, chrome stays dark in both themes per latest feedback), shared.page-heropattern propagated to /, /about, /for_users, /for_developers, /for_organisations, /community, /get_a_pod, /apps.[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).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.htmlredesign — app-store feel on the existing Solid theme(
solid-apps-page-designerpersona)<style>block out ofapps.htmlintoassets/css/apps.css./get_a_podCTA + secondary "Browse the apps" anchor)..top-app-tag ★items; auto-hides when a filter/search/non-default sort is active..apps-page) so other pages are unaffected.prefers-reduced-motionzeros 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-sweeperpersona)rel="noopener noreferrer"on external links, a missing</li>/ duplicateidcleanup._layouts/default.html(every page inherits it) — addedrel="noopener noreferrer"+class="external-link"to every external anchor.rel=noopeneron remaining root pages and _posts/ was deferred — zerotarget="_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-plumberpersona)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.pull_request; runs both in parallel jobs.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-copywriterpersona)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-signcommits, no AI-attribution trailer, roborev review via Copilot / gpt-5.4, a11y-first patterns from PR #956.Stats
main(atdc80768, PR Add 14 Solid apps to the apps list #956).apps.html+apps.css, the lintpackage-lock.json, and the_posts/bulk "here" → descriptive-text sweep.Open questions for @jeswr
#app_launcher(the section heading showing the toolbar) rather than#apps-listdirectly. Designer's reasoning: landing on the toolbar feels more app-store-y. Happy to flip it to the grid if you prefer.apps.css. Site chrome is hard-coded light inbase.css,header.css,footer.css; if you're open to a site-wide dark pass later, I'll handle apps.css then.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?data-category-intro="…"slot so the copywriter can fill in one-liners per category?rel=noopenersweep — 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.How to preview
Or for lint/a11y locally: