Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ It is a Jekyll 4.4 static site with a plugin of its own. GitHub Actions deploys
| `index.md` | The home page's URL and layout, nothing more. `_layouts/home.html` has no `{{ content }}`, so the body of `index.md` is never rendered: edit the home page's sections in that layout |
| `pages/` | Every other page, in Markdown or HTML |
| `_layouts/` | A layout for each kind of page. `default.html` wraps them all, with the header navigation, the footer and the links to CSS and JavaScript |
| `_includes/activity/` | The parts of an Activity row: `meta.html`, `title.html` and `summary.html`, for every Activity view, and `log-row.html`, a row of the `/activity/` log |
| `_posts/` | News posts |
| `_data/activity/` | Activity entries, one YAML file per UTC day and stream |
| `_plugins/` | `activity/rows.rb` turns the Activity entries into rows, and `activity_generator.rb` exposes them to Liquid as `site.data.activity_view`. Jekyll runs both on every build |
Expand Down Expand Up @@ -92,13 +93,16 @@ To add something, copy the front matter of an existing file of the same kind. Th

The navigation is in `_layouts/default.html`. Styles go in `assets/main.scss`, which defines the Sass variables in README's "Brand Colours", or in a partial in `assets/sass/` that it loads with `@use`. A partial can't see those variables (the build fails with "Undefined variable"), so use the `--qe-*` custom properties that `main.scss` sets on `:root`, such as `var(--qe-blue)`, as `_about.scss` does.

`_layouts/default.html` adds a `js` class to `<html>` before first paint. Style states that need JavaScript, such as a collapsed list, under `.js`, and hide controls that need it under `html:not(.js)`: nothing then moves when scripts run, and without JavaScript everything shows. Have the script remove the class if it fails, or what it collapsed stays hidden. The `/activity/` filter and its "N changes" lists work this way.

### News and Activity

- **People write News; the reporter bot writes Activity, and never News.** README's "News and Activity" decides which stream an item belongs to and what belongs in neither. Promoting an Activity item to News is a person's decision.
- **Activity files are append-only.** CI fails a pull request that changes, removes or reorders an entry already on `main`, so a correction to a published entry needs an admin to merge it.
- **Don't edit or add to `.github/scripts/fixtures/activity/`.** It is a frozen copy of the data at 4c47f03, and `test-activity.rb` checks fixed numbers against it. To see the Activity pages with that data, build a copy of the site with `_data/activity/` replaced by the fixture.
- **Build Activity views from `site.data.activity_view`,** not from `site.data.activity`. The header comment of `_plugins/activity_generator.rb` documents every key and field. In the templates:
- Escape every field, attributes included: nothing in the view is escaped, and summaries and PR titles come from outside the site. Turn backtick spans into `<code>` after escaping.
- Put the view inside an element with the `mathjax_ignore` class. MathJax runs on every page and would typeset the text between two `$` signs in a title or summary.
- Dates are `YYYY-MM-DD` strings for UTC days, with precomputed labels. Compare them as strings, and never pass them through Liquid's `date` filter, which works in Sydney time.
- A row's `id` is its merge key and feed guid, not an HTML id. Render the month and day anchors (`months[].id` and `months[].days[].id`): group and editions rows link to a day's anchor.
- **A build that stops with `Activity data: <file>, entry <n> (<project>): …`** names the entry that the rows plugin can't use, with `<file>` missing its `.yml`. Correct the entry, then run the data check.
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ These definitions decide which stream an item belongs to, not where it is shown.

### Activity data

Each file is `_data/activity/<day>-<stream>.yml` and holds a list of entries. `<day>` is the UTC day the entries cover, not the day the reporter ran, and `<stream>` is `software` for releases and `lectures` for lecture, translation and book updates. A run writes no file for a stream with nothing to report. Files are append-only: a re-run adds its new entries after the existing ones and never changes or removes one. It appends them as text after the file's last byte, with no `---` line: re-dumping the whole file would drop its header comment, and Jekyll reads only a file's first YAML document. Older files, migrated from the previous reporter or added by hand (`2026-09-28-software.yml`), are named for the day they were written, so their entries can be earlier. The Activity page merges every file, orders the entries by date (newest first, and in file order within a day), and groups them by month. CI checks every file against this schema (`.github/scripts/check-activity-data.rb`), and fails if `_data/activity/` holds anything but `.yml` and `.yaml` files or a file holds more than one YAML document. It also fails when a release or pull-request URL is listed more than once, in one file or across files, and when a pull request changes or removes an entry that is already on `main`, so a correction to a published entry needs an admin to merge it. A pull request opened by the reporter bot also fails CI unless all it does is add day files or append to them, keeping every existing byte as it is (the first step after checkout in `.github/workflows/build.yml`).
Each file is `_data/activity/<day>-<stream>.yml` and holds a list of entries. `<day>` is the UTC day the entries cover, not the day the reporter ran, and `<stream>` is `software` for releases and `lectures` for lecture, translation and book updates. A run writes no file for a stream with nothing to report. Files are append-only: a re-run adds its new entries after the existing ones and never changes or removes one. It appends them as text after the file's last byte, with no `---` line: re-dumping the whole file would drop its header comment, and Jekyll reads only a file's first YAML document. Older files, migrated from the previous reporter or added by hand (`2026-09-28-software.yml`), are named for the day they were written, so their entries can be earlier. The Activity page shows the rows that the plugin builds from every file (see "Activity rows"), by month and day, newest first. CI checks every file against this schema (`.github/scripts/check-activity-data.rb`), and fails if `_data/activity/` holds anything but `.yml` and `.yaml` files or a file holds more than one YAML document. It also fails when a release or pull-request URL is listed more than once, in one file or across files, and when a pull request changes or removes an entry that is already on `main`, so a correction to a published entry needs an admin to merge it. A pull request opened by the reporter bot also fails CI unless all it does is add day files or append to them, keeping every existing byte as it is (the first step after checkout in `.github/workflows/build.yml`).

| Field | Required | Description |
|---|---|---|
Expand Down Expand Up @@ -75,8 +75,6 @@ At build time, a plugin turns the entries into the rows that the Activity views

Run the tests with `ruby .github/scripts/test-activity.rb`; CI runs them after the data check. They check fixed numbers against a frozen copy of the data in `.github/scripts/fixtures/activity/` (the 40 entries at commit 4c47f03, which must not change). On the live data they check only rules that stay true as entries are added. To see the pages with the frozen data, build a copy of the site with `_data/activity/` replaced by the fixture.

The interim `/activity/` page doesn't use the view yet. It keeps file order within a day until #277 switches it to the view.

## News Post Tags

Posts in `_posts/` use a `tag` frontmatter field with coloured pill badges on the News page.
Expand Down
57 changes: 57 additions & 0 deletions _includes/activity/log-row.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{%- comment -%}
One row of the /activity/ log. Parameter: row, a row of site.data.activity_view. Nothing in
the view is escaped, so every field is escaped here. Two or more PRs are rendered as an open
list after an "N changes" toggle: with JavaScript, CSS collapses the list from the first
paint and the layout's script flips aria-expanded; without it, the toggle is hidden.
{%- endcomment -%}
{%- assign activity_row = include.row -%}
<li class="activity-row" data-type="{{ activity_row.type | escape }}">
<div class="activity-meta">{% include activity/meta.html row=activity_row %}</div>
{%- case activity_row.kind -%}
{%- when "group" %}
<p class="activity-row__releases">
{%- for activity_release in activity_row.releases -%}
<a href="{{ activity_release.url | escape }}">{{ activity_release.project | escape }}{% if activity_release.version != "" %} <span class="activity-row__version">{{ activity_release.version | escape }}</span>{% endif %}</a>
{%- if forloop.rindex == 2 %} and {% elsif forloop.rindex > 2 %}, {% endif -%}
{%- endfor -%}
</p>
{%- when "editions" %}
<p class="activity-row__series">{{ activity_row.title | escape }}</p>
{%- else %}
<a class="activity-row__title" href="{{ activity_row.url | escape }}">{% include activity/title.html row=activity_row %}</a>
{%- endcase -%}
{%- if activity_row.summary != "" %}
<p class="activity-row__summary">{% include activity/summary.html text=activity_row.summary %}</p>
{%- endif -%}
{%- if activity_row.per_edition %}
<ul class="activity-row__per-edition" role="list">
{%- for activity_edition in activity_row.per_edition %}
<li><a href="{{ activity_edition.url | escape }}">{{ activity_edition.lang | escape }}</a>
{%- if activity_edition.summaries.size > 0 -%}
{%- assign activity_text = activity_edition.summaries | join: " " -%}
: {% include activity/summary.html text=activity_text %}
{%- endif -%}
</li>
{%- endfor %}
</ul>
{%- elsif activity_row.kind == "editions" %}
<ul class="activity-row__editions" role="list">
{%- for activity_edition in activity_row.editions %}
<li><a class="activity-chip" href="{{ activity_edition.url | escape }}">{{ activity_edition.lang | escape }}</a></li>
{%- endfor %}
</ul>
{%- endif -%}
{%- assign activity_changes = activity_row.changes -%}
{%- if activity_changes.size == 1 -%}
{%- assign activity_change = activity_changes.first %}
<p class="activity-row__pr"><i class="bi bi-github" aria-hidden="true"></i><span><a href="{{ activity_change.url | escape }}">{{ activity_change.title | remove: "`" | escape }}</a>{% if activity_change.num != "" %} <span class="activity-row__num">{{ activity_change.num | escape }}</span>{% endif %}</span></p>
{%- elsif activity_changes.size > 1 -%}
{%- assign activity_list_id = activity_row.id | slugify | prepend: "changes-" %}
<button type="button" class="activity-changes__toggle" aria-expanded="false" aria-controls="{{ activity_list_id | escape }}"><i class="bi bi-github" aria-hidden="true"></i>{{ activity_changes.size }} changes<i class="bi bi-chevron-down activity-changes__chevron" aria-hidden="true"></i></button>
<ul class="activity-changes__list" id="{{ activity_list_id | escape }}" role="list">
{%- for activity_change in activity_changes %}
<li><a href="{{ activity_change.url | escape }}">{{ activity_change.title | remove: "`" | escape }}</a>{% if activity_change.num != "" %} <span class="activity-row__num">{{ activity_change.num | escape }}</span>{% endif %}</li>
{%- endfor %}
</ul>
{%- endif %}
</li>
17 changes: 17 additions & 0 deletions _includes/activity/meta.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{%- comment -%}
A row's meta line, shared by the Activity views: the type icon, the visually hidden type
word, the date and "· <tag>". Parameters: row, a row of site.data.activity_view; tag=false
leaves the tag out. The caller supplies the wrapping element.
{%- endcomment -%}
{%- case include.row.type -%}
{%- when "release" -%}{%- assign activity_icon = "bi-box-seam" -%}
{%- when "lectures" -%}{%- assign activity_icon = "bi-journal-text" -%}
{%- when "translation" -%}{%- assign activity_icon = "bi-translate" -%}
{%- when "book" -%}{%- assign activity_icon = "bi-book" -%}
{%- endcase -%}
<i class="bi {{ activity_icon }} activity-meta__icon" aria-hidden="true"></i>
<span class="visually-hidden">{{ include.row.type_word | escape }}</span>
<time datetime="{{ include.row.date | escape }}">{{ include.row.date_label | escape }}</time>
{%- if include.tag != false and include.row.tag != "" %}
<span>· {{ include.row.tag | escape }}</span>
{%- endif -%}
9 changes: 9 additions & 0 deletions _includes/activity/summary.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{%- comment -%}
A summary, shared by the Activity views: plain text in which single backticks mark code. It is
escaped first, then every second backtick-separated part becomes <code>. Parameter: text.
{%- endcomment -%}
{%- assign activity_parts = include.text | escape | split: "`" -%}
{%- for activity_part in activity_parts -%}
{%- assign activity_odd = forloop.index0 | modulo: 2 -%}
{%- if activity_odd == 1 -%}<code>{{ activity_part }}</code>{%- else -%}{{ activity_part }}{%- endif -%}
{%- endfor -%}
6 changes: 6 additions & 0 deletions _includes/activity/title.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{%- comment -%}
A row's title, shared by the Activity views: the project or series, then a single release's
version at weight 400. Parameter: row. The caller supplies the link.
{%- endcomment -%}
{{- include.row.title | escape -}}
{%- if include.row.version != "" %} <span class="activity-row__version">{{ include.row.version | escape }}</span>{% endif -%}
Loading
Loading