From 4890516919124aec56908913c5874570f45225f5 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:04:20 +0000 Subject: [PATCH 1/7] Add Jekyll scaffolding: config, Gemfile, gitignore, navigation data --- .gitignore | 16 ++++++++++++++++ .rspec | 2 ++ .ruby-version | 1 + Gemfile | 29 +++++++++++++++++++++++++++++ _config.yml | 37 +++++++++++++++++++++++++++++++++++++ _data/navigation.yml | 7 +++++++ 6 files changed, 92 insertions(+) create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 _config.yml create mode 100644 _data/navigation.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4022b42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Jekyll build output +_site/ +.jekyll-cache/ +.jekyll-metadata +.sass-cache/ + +# Test output and screenshots +tmp/ + +# Ruby dependencies installed locally +vendor/ +.bundle/ + +# Editor / OS noise +.DS_Store +*.swp diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..5be63fc --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--require spec_helper +--format documentation diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..3b47f2e --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.9 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..1177a6b --- /dev/null +++ b/Gemfile @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Use the Ruby version pinned in .ruby-version. +ruby file: ".ruby-version" + +# Gems used to build the website. +gem "jekyll", "~> 4.3" +gem "kramdown-parser-gfm" +gem "webrick" + +# Jekyll plugins. +group :jekyll_plugins do + gem "jekyll-sitemap" +end + +# Tools used to run the accessibility tests. These are not needed just to edit +# the site, only to run `bundle exec rspec` locally or in CI. +group :development, :test do + gem "axe-core-capybara" + gem "axe-core-rspec" + gem "capybara" + gem "capybara-screenshot" + gem "rack" + gem "rackup" + gem "rspec" + gem "selenium-webdriver" +end diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..5e9798d --- /dev/null +++ b/_config.yml @@ -0,0 +1,37 @@ +# Site settings — edit the values below to change site-wide text. +# After editing this file you must stop and restart the local server. +title: Teach-Net +tagline: A teaching forum for Berkeley faculty and staff +description: >- + Teach-Net is a moderated email forum open to UC Berkeley faculty and staff to + share ideas and resources and communicate about teaching. +author: UC Berkeley Center for Teaching & Learning + +# The web address where the site lives. Update these when you publish. +# For a GitHub Pages "project" site the URL is https://.github.io and +# the baseurl is "/". For a custom domain, set baseurl to "". +url: "" +baseurl: "" + +# Build settings — you normally do not need to change anything below this line. +markdown: kramdown +kramdown: + input: GFM + hard_wrap: false + +sass: + style: compressed + +plugins: + - jekyll-sitemap + +# Files and folders that should not be copied into the published site. +exclude: + - Gemfile + - Gemfile.lock + - README.md + - vendor + - spec + - .devcontainer + - .github + - script diff --git a/_data/navigation.yml b/_data/navigation.yml new file mode 100644 index 0000000..50df523 --- /dev/null +++ b/_data/navigation.yml @@ -0,0 +1,7 @@ +# The links shown in the top navigation bar. +# To add a page to the menu, copy one block and change the title and url. +# The url should match the page's `permalink` (or its path). +- title: Teach-Net + url: / +- title: Teaching resources + url: /resources/ From d614033522aaddbfb8c49ca6f152344f3d44a753 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:05:15 +0000 Subject: [PATCH 2/7] Add layouts and small, reusable includes (header, nav, footer, callout, button) --- _includes/button.html | 12 ++++++++++++ _includes/callout.html | 18 ++++++++++++++++++ _includes/footer.html | 12 ++++++++++++ _includes/head.html | 22 ++++++++++++++++++++++ _includes/header.html | 10 ++++++++++ _includes/nav.html | 14 ++++++++++++++ _layouts/default.html | 18 ++++++++++++++++++ _layouts/page.html | 13 +++++++++++++ 8 files changed, 119 insertions(+) create mode 100644 _includes/button.html create mode 100644 _includes/callout.html create mode 100644 _includes/footer.html create mode 100644 _includes/head.html create mode 100644 _includes/header.html create mode 100644 _includes/nav.html create mode 100644 _layouts/default.html create mode 100644 _layouts/page.html diff --git a/_includes/button.html b/_includes/button.html new file mode 100644 index 0000000..f4a5550 --- /dev/null +++ b/_includes/button.html @@ -0,0 +1,12 @@ +{%- comment -%} + A prominent link styled as a button. + + Use it inside any Markdown page like this: + + {% include button.html text="Subscribe to Teach-Net" url="mailto:teach-net+subscribe@lists.berkeley.edu" %} + + Options: + text - the words shown on the button. + url - where the button links to (a web address or mailto: link). +{%- endcomment -%} +{{ include.text }} diff --git a/_includes/callout.html b/_includes/callout.html new file mode 100644 index 0000000..0ebe546 --- /dev/null +++ b/_includes/callout.html @@ -0,0 +1,18 @@ +{%- comment -%} + A highlighted box for tips, notes, or warnings. + + Use it inside any Markdown page like this: + + {% include callout.html title="Good to know" content="Reply goes to everyone." %} + + Options: + title - optional heading shown in bold at the top of the box. + content - the text to show. Plain text or simple Markdown links work. + type - optional style: "note" (default), "tip", or "warning". +{%- endcomment -%} + diff --git a/_includes/footer.html b/_includes/footer.html new file mode 100644 index 0000000..db71c34 --- /dev/null +++ b/_includes/footer.html @@ -0,0 +1,12 @@ + diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000..2ab9f84 --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,22 @@ + + + + + {%- if page.title -%} + {{ page.title }} | {{ site.title }} + {%- else -%} + {{ site.title }} + {%- endif -%} + + + + + + + + + + diff --git a/_includes/header.html b/_includes/header.html new file mode 100644 index 0000000..5e0573f --- /dev/null +++ b/_includes/header.html @@ -0,0 +1,10 @@ + diff --git a/_includes/nav.html b/_includes/nav.html new file mode 100644 index 0000000..9ad32d5 --- /dev/null +++ b/_includes/nav.html @@ -0,0 +1,14 @@ + diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..a2ca65f --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,18 @@ + + + {% include head.html %} + + + + + {% include header.html %} + +
+
+ {{ content }} +
+
+ + {% include footer.html %} + + diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..1a4aab3 --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,13 @@ +--- +layout: default +--- +
+ + + {{ content }} +
From 25cf303ba3071b2f3901ceb338c158e229f3a18f Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:06:12 +0000 Subject: [PATCH 3/7] Add Berkeley-branded, accessible SCSS (brand colors, fonts, layout, components) --- _sass/_base.scss | 128 +++++++++++++++++++++++++++++++++++++++++ _sass/_components.scss | 46 +++++++++++++++ _sass/_layout.scss | 127 ++++++++++++++++++++++++++++++++++++++++ _sass/_variables.scss | 32 +++++++++++ assets/css/style.scss | 9 +++ 5 files changed, 342 insertions(+) create mode 100644 _sass/_base.scss create mode 100644 _sass/_components.scss create mode 100644 _sass/_layout.scss create mode 100644 _sass/_variables.scss create mode 100644 assets/css/style.scss diff --git a/_sass/_base.scss b/_sass/_base.scss new file mode 100644 index 0000000..79c5535 --- /dev/null +++ b/_sass/_base.scss @@ -0,0 +1,128 @@ +// Base element styles and accessibility helpers. + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-size: 100%; // respect the reader's browser default (16px). +} + +body { + margin: 0; + font-family: $font-sans; + font-size: 1.0625rem; + line-height: 1.65; + color: $text-color; + background-color: $white; + // Lay the page out as a column so the footer sits at the bottom. + display: flex; + flex-direction: column; + min-height: 100vh; + -webkit-font-smoothing: antialiased; +} + +.container { + width: 100%; + max-width: $content-width; + margin: 0 auto; + padding: 0 1.25rem; +} + +// Headings use the serif brand face. +h1, +h2, +h3, +h4 { + font-family: $font-serif; + line-height: 1.2; + color: $founders-rock; + margin: 2rem 0 0.75rem; +} + +h1 { + font-size: 2.25rem; +} +h2 { + font-size: 1.6rem; +} +h3 { + font-size: 1.25rem; +} + +p, +ul, +ol { + margin: 0 0 1rem; +} + +a { + color: $link-color; + text-decoration: underline; + text-underline-offset: 2px; + + &:hover, + &:focus { + color: $link-hover-color; + } +} + +// A clearly visible focus ring for keyboard users on every interactive element. +a:focus-visible, +button:focus-visible, +.button:focus-visible, +.skip-link:focus { + outline: 3px solid $california-gold; + outline-offset: 2px; + border-radius: 2px; +} + +code, +pre { + font-family: $font-mono; + font-size: 0.95em; +} + +code { + background: $cloud; + padding: 0.15em 0.35em; + border-radius: 3px; +} + +pre { + background: $cloud; + padding: 1rem; + border-radius: $radius; + overflow-x: auto; +} + +img { + max-width: 100%; + height: auto; +} + +hr { + border: none; + border-top: 1px solid $line; + margin: 2rem 0; +} + +// Visible only when focused: lets keyboard users skip the navigation. +.skip-link { + position: absolute; + left: 0.5rem; + top: -3rem; + z-index: 100; + background: $white; + color: $berkeley-blue; + padding: 0.6rem 1rem; + border-radius: $radius; + text-decoration: none; + transition: top 0.15s ease-in-out; + + &:focus { + top: 0.5rem; + } +} diff --git a/_sass/_components.scss b/_sass/_components.scss new file mode 100644 index 0000000..a65308d --- /dev/null +++ b/_sass/_components.scss @@ -0,0 +1,46 @@ +// Reusable pieces used inside Markdown pages (see the matching includes). + +// ---- Button ------------------------------------------------------------- +.button { + display: inline-block; + background: $berkeley-blue; + color: $white; + text-decoration: none; + font-weight: 600; + padding: 0.7rem 1.4rem; + border-radius: $radius; + margin: 0.25rem 0; + + &:hover, + &:focus { + background: $founders-rock; + color: $white; + } +} + +// ---- Callout ------------------------------------------------------------ +.callout { + background: $cloud; + border-left: 5px solid $berkeley-blue; + border-radius: 0 $radius $radius 0; + padding: 1rem 1.25rem; + margin: 1.5rem 0; +} + +.callout__title { + font-weight: 700; + margin: 0 0 0.4rem; + color: $founders-rock; +} + +.callout__body > :last-child { + margin-bottom: 0; +} + +.callout--tip { + border-left-color: #1c7c54; // accessible green. +} + +.callout--warning { + border-left-color: #9a3b1b; // accessible amber/brown. +} diff --git a/_sass/_layout.scss b/_sass/_layout.scss new file mode 100644 index 0000000..f91ac8c --- /dev/null +++ b/_sass/_layout.scss @@ -0,0 +1,127 @@ +// Header, navigation, main content and footer layout. + +// ---- Header ------------------------------------------------------------- +.site-header { + background: $header-bg; + border-bottom: 4px solid $california-gold; // signature Berkeley gold bar. + color: $white; +} + +.site-header__inner { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.5rem 1.5rem; + padding-top: 0.85rem; + padding-bottom: 0.85rem; +} + +.site-brand { + display: flex; + flex-direction: column; + text-decoration: none; + color: $white; + line-height: 1.1; + + &:hover, + &:focus { + color: $white; + } +} + +.site-brand__cal { + font-family: $font-serif; + font-weight: 700; + font-size: 1.5rem; + letter-spacing: 0.01em; +} + +.site-brand__name { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.12em; + color: $california-gold; +} + +// ---- Navigation --------------------------------------------------------- +.site-nav__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 0.25rem 1.25rem; +} + +.site-nav__link { + color: $white; + text-decoration: none; + font-weight: 500; + padding: 0.35rem 0; + border-bottom: 2px solid transparent; + + &:hover, + &:focus { + color: $white; + border-bottom-color: $california-gold; + } + + &[aria-current="page"] { + border-bottom-color: $california-gold; + } +} + +// ---- Main content ------------------------------------------------------- +.page-content { + flex: 1 0 auto; // pushes the footer down on short pages. + padding: 2.5rem 0 3.5rem; +} + +.page-header { + margin-bottom: 1.5rem; + border-bottom: 1px solid $line; +} + +.page-header h1 { + margin-top: 0; +} + +.page-subtitle { + font-size: 1.2rem; + color: $muted-color; + margin-top: -0.25rem; +} + +// Keep reading lines comfortable for long blocks of text. +.page-content article { + max-width: 42rem; +} + +// ---- Footer ------------------------------------------------------------- +.site-footer { + background: $footer-bg; + color: $white; + padding: 2rem 0; +} + +.site-footer__inner { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 1rem 2rem; +} + +.site-footer a { + color: $california-gold; +} + +.site-footer__org { + font-weight: 600; + margin: 0; +} + +.site-footer__note { + margin: 0; + max-width: 32rem; +} diff --git a/_sass/_variables.scss b/_sass/_variables.scss new file mode 100644 index 0000000..9761b73 --- /dev/null +++ b/_sass/_variables.scss @@ -0,0 +1,32 @@ +// UC Berkeley brand colours and shared design values. +// Change a value here and it updates everywhere on the site. + +// Primary palette (from the UC Berkeley visual identity). +$berkeley-blue: #002676; +$founders-rock: #003262; +$california-gold: #fdb515; + +// Neutrals chosen to meet WCAG AA contrast on a white background. +$ink: #1a1a1a; // body text +$slate: #46535e; // muted/secondary text +$line: #d3dae0; // hairline borders +$cloud: #f2f5f7; // soft background fill +$white: #ffffff; + +// Roles — reference these in components so meaning stays clear. +$text-color: $ink; +$muted-color: $slate; +$link-color: $berkeley-blue; +$link-hover-color: $founders-rock; +$accent-color: $california-gold; +$header-bg: $berkeley-blue; +$footer-bg: $founders-rock; + +// Typography. +$font-sans: "Inter", system-ui, -apple-system, "Segoe UI", roboto, helvetica, arial, sans-serif; +$font-serif: "Source Serif 4", georgia, "Times New Roman", serif; +$font-mono: "Source Code Pro", "SFMono-Regular", menlo, consolas, monospace; + +// Layout. +$content-width: 960px; +$radius: 6px; diff --git a/assets/css/style.scss b/assets/css/style.scss new file mode 100644 index 0000000..640298c --- /dev/null +++ b/assets/css/style.scss @@ -0,0 +1,9 @@ +--- +# This empty front matter tells Jekyll to process this file and turn the +# @import lines below into a single CSS file. Do not remove these dashes. +--- + +@import "variables"; +@import "base"; +@import "layout"; +@import "components"; From 316e2f53d1fc63ca1e46b73123a2db1793e17706 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:06:38 +0000 Subject: [PATCH 4/7] Add Teach-Net home page and Teaching resources page (Markdown) --- index.md | 39 +++++++++++++++++++++++++++++++++++++++ resources.md | 26 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 index.md create mode 100644 resources.md diff --git a/index.md b/index.md new file mode 100644 index 0000000..a80c32b --- /dev/null +++ b/index.md @@ -0,0 +1,39 @@ +--- +layout: page +title: Teach-Net +subtitle: A teaching forum for Berkeley faculty and staff +permalink: / +--- + +**Teach-Net** is a moderated email forum open to UC Berkeley faculty and staff +to share ideas and resources, ask questions, and communicate about teaching. +Subscribers send messages to a single address, and — once approved by the list +moderator — everyone on the list receives a copy. + +{% include button.html text="Subscribe to Teach-Net" url="mailto:teach-net+subscribe@lists.berkeley.edu" %} + +## How to subscribe + +There are two ways to join the list: + +1. Find and join the **teach-net** list from the + [bConnected Lists home page](https://bgroups.berkeley.edu), or +2. Send a subscription request by email to + [teach-net+subscribe@lists.berkeley.edu](mailto:teach-net+subscribe@lists.berkeley.edu). + +## How to post a message + +Once you are subscribed, you can share a question, a comment, or a teaching tip. +Simply send it by email to +[teach-net@lists.berkeley.edu](mailto:teach-net@lists.berkeley.edu). +All subscribers will receive a copy of your message after it is approved by the +list moderator. + +{% include callout.html type="warning" title="Be careful with “Reply”" content="Replying to a Teach-Net message will generally send your response back to the **entire** mailing list. If you want to reach just one person, send your message to that individual's email address instead." %} + +## Questions? + +Teach-Net is offered as part of the campus teaching community. For other ways to +connect and for teaching support, see the +[Teaching resources](/resources/) page or visit the +[Center for Teaching & Learning](https://teaching.berkeley.edu/). diff --git a/resources.md b/resources.md new file mode 100644 index 0000000..f22853d --- /dev/null +++ b/resources.md @@ -0,0 +1,26 @@ +--- +layout: page +title: Teaching resources +subtitle: Ways to connect and find support at Berkeley +permalink: /resources/ +--- + +Teach-Net is one of many ways the campus community supports great teaching. +The links below point to other resources for Berkeley instructors. + +## Connect with colleagues + +- [Teach-Net](/) — the moderated email forum for sharing teaching ideas. +- [bConnected Lists](https://bgroups.berkeley.edu) — find and join other + campus mailing lists. + +## Support for your teaching + +- [Center for Teaching & Learning](https://teaching.berkeley.edu/) — + consultations, programs, and teaching guides. +- [Teaching guides & resources](https://teaching.berkeley.edu/teaching-guides-resources) + — practical guidance on course design, assessment, and inclusive teaching. +- [Campus resources](https://teaching.berkeley.edu/campus-resources) — + a directory of teaching-related offices and services. + +{% include callout.html type="tip" title="Want to add a resource?" content="This page is plain Markdown. Open `resources.md`, copy one of the list items above, and change the link text and address. See the README for step-by-step instructions." %} From 21afd5eb57f4e62cc5eb72efc2973fcc412733d3 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:06:54 +0000 Subject: [PATCH 5/7] Add devcontainer for one-click local preview (Codespaces / VS Code) --- .devcontainer/devcontainer.json | 27 +++++++++++++++++++++++++++ .devcontainer/post-create.sh | 14 ++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/post-create.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ee8d77e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +{ + "name": "Teach-Net (Jekyll)", + "image": "mcr.microsoft.com/devcontainers/jekyll:2.2.1-3.3-bookworm", + + "features": {}, + + // 4000 = the local website, 35729 = live-reload when you save a file. + "forwardPorts": [4000, 35729], + "portsAttributes": { + "4000": { "label": "Website preview", "onAutoForward": "openPreview" } + }, + + "postCreateCommand": "sh .devcontainer/post-create.sh", + + "customizations": { + "codespaces": { + "openFiles": ["README.md", "index.md"] + }, + "vscode": { + "settings": {}, + "extensions": [ + "ms-ruby.ruby", + "deque-systems.vscode-axe-linter" + ] + } + } +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 0000000..d86be21 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Runs once when the dev container (or GitHub Codespace) is first created. +# It installs the site's dependencies and starts a live preview. + +set -e + +# Install the Ruby gems listed in the Gemfile. +if [ -f Gemfile ]; then + bundle install +fi + +# Start the local website. It rebuilds automatically when you save a file. +# Open the forwarded "Website preview" port to see it. +bundle exec jekyll serve --livereload --force-polling --host 0.0.0.0 From 4559036c753f49c08fb6a715debbe232c44a0f14 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:07:45 +0000 Subject: [PATCH 6/7] Add accessibility specs (from berkeley-class-site) and CI workflows --- .github/workflows/accessibility.yml | 31 ++++++++ .github/workflows/jekyll.yml | 52 +++++++++++++ .rspec | 1 + spec/accessibility_spec.rb | 64 ++++++++++++++++ spec/spec_helper.rb | 115 ++++++++++++++++++++++++++++ spec/support/jekyll.rb | 63 +++++++++++++++ spec/support/spec_summary.rb | 69 +++++++++++++++++ 7 files changed, 395 insertions(+) create mode 100644 .github/workflows/accessibility.yml create mode 100644 .github/workflows/jekyll.yml create mode 100644 spec/accessibility_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 spec/support/jekyll.rb create mode 100644 spec/support/spec_summary.rb diff --git a/.github/workflows/accessibility.yml b/.github/workflows/accessibility.yml new file mode 100644 index 0000000..45c3eb4 --- /dev/null +++ b/.github/workflows/accessibility.yml @@ -0,0 +1,31 @@ +# Runs the accessibility checks (spec/) on every push and pull request. +name: Accessibility tests + +on: + - push + - pull_request + - workflow_dispatch + +jobs: + a11y: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - name: Run accessibility tests + run: bundle exec rspec + - name: Summary + if: failure() + run: ruby spec/support/spec_summary.rb + - name: Keep screenshots from failed tests + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots + path: ${{ github.workspace }}/tmp/capybara + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml new file mode 100644 index 0000000..8bcd729 --- /dev/null +++ b/.github/workflows/jekyll.yml @@ -0,0 +1,52 @@ +# Builds the Jekyll site and publishes it to GitHub Pages on every push to main. +# +# One-time setup in the repository: go to Settings -> Pages and set +# "Build and deployment" -> Source to "GitHub Actions". +name: Deploy site to GitHub Pages + +on: + push: + branches: ["main"] + workflow_dispatch: + +# Permissions needed to publish to GitHub Pages. +permissions: + contents: read + pages: write + id-token: write + +# Allow only one deployment at a time. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.rspec b/.rspec index 5be63fc..2d91ffd 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --require spec_helper --format documentation +--format json --out tmp/rspec_output.json diff --git a/spec/accessibility_spec.rb b/spec/accessibility_spec.rb new file mode 100644 index 0000000..4252632 --- /dev/null +++ b/spec/accessibility_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# Run accessibility checks for every page on the website. +# This builds the site, then runs the axe-core accessibility checker against +# each page in a headless browser. +# +# Adapted from berkeley-cdss/berkeley-class-site. This site uses a single +# (light) theme, so the per-theme light/dark passes from the original have been +# removed; the axe-core checks themselves are unchanged. + +# spec_helper ensures the website is built and can be served locally. +require 'spec_helper' + +# Axe-core test standards groups. +# See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#axe-core-tags +# In most places WCAG 2.1 AA is the minimum requirement, but 2.2 is the current +# WCAG standard. +REQUIRED_A11Y_STANDARDS = %i[wcag2a wcag2aa wcag21a wcag21aa].freeze +COMPLETE_A11Y_STANDARDS = %i[wcag22aa best-practice section508].freeze + +# axe-core rules that are not required / do not apply. +# You may temporarily want to add rules here during development. +# See: https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md +SKIPPED_RULES = [].freeze + +# Elements that are not required to be accessible. It should be rare to add to +# this list. This disables all rules for an element, e.g. +# would pass even without alt text. +EXCLUDED_ELEMENTS = [ + '[data-a11y-errors="true"]' +].freeze + +# Pages that do not need a11y tests run. It should be rare to add to this array. +SKIPPED_PAGES = [].freeze + +# Build the site so the checks run against the latest output. +build_jekyll_site! +ALL_PAGES = load_sitemap + +PAGES_TO_TEST = ALL_PAGES - SKIPPED_PAGES + +RSpec.shared_examples 'a11y tests' do + it 'meets WCAG 2.1' do + expect(page).to be_axe_clean + .according_to(*REQUIRED_A11Y_STANDARDS) + .skipping(*SKIPPED_RULES) + .excluding(*EXCLUDED_ELEMENTS) + end + + it 'meets WCAG 2.2' do + expect(page).to be_axe_clean + .according_to(*COMPLETE_A11Y_STANDARDS) + .skipping(*SKIPPED_RULES) + .excluding(*EXCLUDED_ELEMENTS) + end +end + +PAGES_TO_TEST.each do |path| + describe "#{path} is accessible", :js, type: :feature do + before { visit(path) } + + include_context 'a11y tests' + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..5d03097 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration + +require 'rspec' +require 'rack' +require 'yaml' +require 'webrick' + +require 'capybara/rspec' +require 'capybara/dsl' +require 'capybara-screenshot/rspec' +require 'capybara/session' + +require 'rack/test' +require 'axe-rspec' +require 'axe-capybara' + +require_relative 'support/jekyll' + +# Used to set the path for a local webserver. +# Update this if you move this file. +REPO_ROOT = File.expand_path('../', __dir__) + +Capybara.register_driver :chrome_headless do |app| + options = Selenium::WebDriver::Chrome::Options.new + options.add_argument('--headless') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + # MacBook Air ~13" screen size, with an absurd height to capture more content. + options.add_argument('--window-size=1280,4000') + + Capybara::Selenium::Driver.new(app, browser: :chrome, options:) +end + +# Change default_driver to :selenium_chrome if you want to actually see the tests running in a browser locally. +# Should be :chrome_headless in CI though. +Capybara.default_driver = :chrome_headless +Capybara.javascript_driver = :chrome_headless + +Capybara::Screenshot.register_driver(:chrome_headless) do |driver, path| + driver.save_screenshot(path) +end + +Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| + # Highly specific to a11y specs: path-mode-wcag-version + # TODO: Find a nice way to name "index" pages, or consider using Capybara.page.title + page = example.example_group.top_level_description.gsub(' is accessible', '') + mode = example.example_group.description # i.e. light mode / dark mode + standard = example.description.split.last # i.e "meets WCAG 2.1" + test_case = "#{page}_#{mode}_#{standard}" + test_case = test_case.gsub(%r{^/}, '').gsub(%r{[/\s+]}, '-') + + "tmp/capybara/screenshot_#{test_case}" +end + +Capybara::Screenshot.autosave_on_failure = true +Capybara::Screenshot.append_timestamp = false +Capybara::Screenshot.prune_strategy = :keep_last_run + +# Setup for Capybara to serve static files served by Rack +Capybara.server = :webrick +Capybara.app = Rack::Builder.new do + use Rack::Lint + run StaticSite.new(REPO_ROOT) +end.to_app + +RSpec.configure do |config| + # Allow rspec to use `--only-failures` and `--next-failure` flags + # Ensure that `tmp` is in your `.gitignore` file + config.example_status_persistence_file_path = 'tmp/rspec-failures.txt' + + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + config.include Capybara::DSL +end diff --git a/spec/support/jekyll.rb b/spec/support/jekyll.rb new file mode 100644 index 0000000..c9f3b46 --- /dev/null +++ b/spec/support/jekyll.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'jekyll' + +# Tools to build / compile the Jekyll site and extract the sitemap +def site_config + # TODO(template): We should standardize the build for specs + # Consider simplifying baseurl + # Consider forcing the desination folder + # Override the local URL too? Would it break the sitemap? + # Note: Config keys must be strings and thus use => style hashes. + @site_config ||= Jekyll.configuration({ 'sass' => { 'quiet_deps' => true } }) +end + +def build_jekyll_site! + @site = Jekyll::Site.new(site_config) + @site.process +end + +def load_sitemap + # Ensure that you have called + sitemap_text = File.read('_site/sitemap.xml') + sitemap_links = sitemap_text.scan(%r{.+}) + sitemap_links.filter_map do |link| + link = link.gsub("#{site_config['url']}", '').gsub('', '') + # Skip non-html pages + # (FUTURE?) Are there other pages that should be audited for accessibility? + # (e.g. PDFs, documents. They'd need a different checker.) + next unless link.end_with?('.html') || link.end_with?('/') + + link + end.sort +end + +# Start a local Rack server +# https://nts.strzibny.name/how-to-test-static-sites-with-rspec-capybara-and-webkit/ +class StaticSite + attr_reader :root, :server + + def initialize(root) + @root = root + @server = Rack::Files.new(root) + end + + def call(env) + # Remove the /baseurl prefix, which is present in all URLs, but not in the file system. + path = "_site#{env['PATH_INFO'].gsub(site_config['baseurl'], '/')}" + + env['PATH_INFO'] = if path.end_with?('/') && exists?("#{path}index.html") + "#{path}index.html" + elsif !exists?(path) && exists?("#{path}.html") + "#{path}.html" + else + path + end + + server.call(env) + end + + def exists?(path) + File.exist?(File.join(root, path)) + end +end diff --git a/spec/support/spec_summary.rb b/spec/support/spec_summary.rb new file mode 100644 index 0000000..07e7480 --- /dev/null +++ b/spec/support/spec_summary.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# Summarize the axe rspec failures into aggregate counts +# TODO: This should be an RSpec formatter + +require 'json' + +RESULTS_PATH = File.join(File.dirname(__FILE__), '..', '..', 'tmp/rspec_output.json') +AXE_CASE_TITLE = /\n\s*\n\s*\d+\)\s+([-\w]+):/ + +def failing_specs(results_data) + results_data['examples'].filter do |ex| + ex['status'] == 'failed' + end +end + +def summarize_results(results) + failing_specs(results).map do |ex| + ex['exception']['message'].scan(AXE_CASE_TITLE) + end.flatten.tally +end + +def group_results(results) + all_cases_list = failing_specs(results).map do |ex| + msg = ex['exception']['message'] + msg.gsub!(/\nInvocation:.*;/, '') + cases = msg.split(AXE_CASE_TITLE) + cases.delete_at(0) + Hash[*cases].transform_values { |v| { page: ex['full_description'], message: v } } + end + results = {} + results.default = [] + all_cases_list.each do |test_hash| + test_hash.each do |axe_name, failure| + if results.key?(axe_name) + results[axe_name] << failure + else + results[axe_name] = [failure] + end + end + end + results +end + +# rubocop:enable Metrics/AbcSize +def test_failures_with_pages(summary_group) + summary_group.transform_values { |list| list.map { |h| h[:page] } } +end + +def nicely_print(hash) + hash.each_value do |values| + # rubocop:disable RSpec/Output + values.each { |item| puts item } + # rubocop:enable RSpec/Output + end +end + +def print_summary + results_data = JSON.parse(File.read(RESULTS_PATH)) + failing_tests_by_type = summarize_results(results_data) + total_failures = failing_tests_by_type.values.sum + + return if total_failures == 0 + + summary_group = group_results(results_data) + nicely_print(test_failures_with_pages(summary_group)) +end + +print_summary From 5d8de44c77a64f464be7900593f20b0db1017a40 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 24 Jun 2026 03:11:03 +0000 Subject: [PATCH 7/7] Add plain-language README for non-technical maintainers --- README.md | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/README.md b/README.md index e69de29..f6ac125 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,134 @@ +# Teach-Net website + +This is the website for **Teach-Net**, the moderated email forum for UC Berkeley +faculty and staff. It is a small [Jekyll](https://jekyllrb.com/) site that is +published with **GitHub Pages**, and it follows UC Berkeley's brand colours and +fonts. + +You do **not** need to be a programmer to update this site. Most changes are as +simple as editing a text file and saving it. + +--- + +## What is in this project + +The files you are most likely to touch are written in **Markdown** — a simple way +to write text with headings, links, and lists. The two pages are: + +| File | What it is | +| -------------- | --------------------------------------------- | +| `index.md` | The main Teach-Net page (the home page) | +| `resources.md` | The "Teaching resources" page | + +A few other files control how the site looks and works. You usually will not +need to change them: + +| File / folder | What it does | +| ---------------------- | ------------------------------------------------------- | +| `_config.yml` | Site-wide settings (title, description, web address) | +| `_data/navigation.yml` | The list of links in the top menu | +| `_layouts/` | The page frame (header, footer) that wraps each page | +| `_includes/` | Small reusable pieces, like buttons and callout boxes | +| `_sass/` | The colours, fonts, and styling | + +--- + +## How to make a simple edit (right in your browser) + +You can edit the text without installing anything: + +1. On GitHub, open the file you want to change (for example, `index.md`). +2. Click the **pencil icon** in the top-right to edit it. +3. Change the text. Markdown basics: + - `# Heading` is a big heading, `## Heading` is a smaller one. + - `[words you see](https://the-link.com)` makes a link. + - A line that starts with `- ` becomes a bullet point. + - Leave a blank line between paragraphs. +4. Scroll down, write a short note about your change, and click **Commit changes**. + +Within a minute or two, GitHub will rebuild and publish the site automatically. + +### Adding a link to the menu + +Open `_data/navigation.yml`, copy one of the existing two-line blocks, and change +the `title` (the words shown) and the `url` (the page address). Save the file. + +--- + +## Reusable building blocks + +Two ready-made pieces can be dropped into any page. You add them with a short tag. + +**A button:** + +```liquid +{% include button.html text="Subscribe" url="mailto:teach-net+subscribe@lists.berkeley.edu" %} +``` + +**A highlighted callout box** (use `type="note"`, `"tip"`, or `"warning"`): + +```liquid +{% include callout.html type="tip" title="Good to know" content="Your message here." %} +``` + +You can see both of these used in `index.md` and `resources.md`. + +--- + +## Previewing the site on your own computer (optional) + +If you want to see your changes before publishing, you can run the site locally. +The easiest way is with the included **dev container**, which sets everything up +for you: + +- **GitHub Codespaces:** click the green **Code** button on GitHub, choose the + **Codespaces** tab, and create a codespace. It builds the site and opens a + live preview automatically. +- **VS Code on your computer:** install the + [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + and [Docker](https://www.docker.com/), open this folder, and choose + **Reopen in Container** when prompted. + +The preview updates each time you save a file. + +
+Running it manually without the dev container + +You will need [Ruby](https://www.ruby-lang.org/) (version 3.3.9, listed in +`.ruby-version`) and [Bundler](https://bundler.io/). Then: + +```sh +bundle install # install dependencies (first time only) +bundle exec jekyll serve # start the preview at http://localhost:4000 +``` + +
+ +--- + +## How the site gets published + +When a change is committed to the `main` branch, GitHub automatically rebuilds +the site and publishes it. This is handled by `.github/workflows/jekyll.yml`. + +**One-time setup** (only needed once, by a repository administrator): in the +repository's **Settings -> Pages**, set **Build and deployment -> Source** to +**GitHub Actions**. + +--- + +## Accessibility + +Accessibility is a requirement, not an afterthought. Every time the site changes, +an automated check (in `spec/`) runs against each page using the +[axe accessibility checker](https://github.com/dequelabs/axe-core) to confirm it +meets the WCAG 2.1 AA / 2.2 standards. These checks are adapted from the +[berkeley-cdss/berkeley-class-site](https://github.com/berkeley-cdss/berkeley-class-site) +template. If a change introduces an accessibility problem, the check fails and +tells you what to fix. + +When you add content, please keep it accessible: + +- Give every image meaningful **alt text**. +- Use headings in order (don't skip from `#` to `###`). +- Write descriptive link text ("the subscribe page"), not "click here".