diff --git a/AGENTS.md b/AGENTS.md index 2e60b54..c05f79e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 | @@ -92,6 +93,8 @@ 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 `` 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. @@ -99,6 +102,7 @@ The navigation is in `_layouts/default.html`. Styles go in `assets/main.scss`, w - **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 `` 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: , entry (): …`** names the entry that the rows plugin can't use, with `` missing its `.yml`. Correct the entry, then run the data check. diff --git a/README.md b/README.md index 88df28a..e6a9bbf 100644 --- a/README.md +++ b/README.md @@ -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/-.yml` and holds a list of entries. `` is the UTC day the entries cover, not the day the reporter ran, and `` 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/-.yml` and holds a list of entries. `` is the UTC day the entries cover, not the day the reporter ran, and `` 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 | |---|---|---| @@ -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. diff --git a/_includes/activity/log-row.html b/_includes/activity/log-row.html new file mode 100644 index 0000000..0a1e0bd --- /dev/null +++ b/_includes/activity/log-row.html @@ -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 -%} +
  • +
    {% include activity/meta.html row=activity_row %}
    + {%- case activity_row.kind -%} + {%- when "group" %} +

    + {%- for activity_release in activity_row.releases -%} + {{ activity_release.project | escape }}{% if activity_release.version != "" %} {{ activity_release.version | escape }}{% endif %} + {%- if forloop.rindex == 2 %} and {% elsif forloop.rindex > 2 %}, {% endif -%} + {%- endfor -%} +

    + {%- when "editions" %} +

    {{ activity_row.title | escape }}

    + {%- else %} + {% include activity/title.html row=activity_row %} + {%- endcase -%} + {%- if activity_row.summary != "" %} +

    {% include activity/summary.html text=activity_row.summary %}

    + {%- endif -%} + {%- if activity_row.per_edition %} +
      + {%- for activity_edition in activity_row.per_edition %} +
    • {{ activity_edition.lang | escape }} + {%- if activity_edition.summaries.size > 0 -%} + {%- assign activity_text = activity_edition.summaries | join: " " -%} + : {% include activity/summary.html text=activity_text %} + {%- endif -%} +
    • + {%- endfor %} +
    + {%- elsif activity_row.kind == "editions" %} + + {%- endif -%} + {%- assign activity_changes = activity_row.changes -%} + {%- if activity_changes.size == 1 -%} + {%- assign activity_change = activity_changes.first %} +

    {{ activity_change.title | remove: "`" | escape }}{% if activity_change.num != "" %} {{ activity_change.num | escape }}{% endif %}

    + {%- elsif activity_changes.size > 1 -%} + {%- assign activity_list_id = activity_row.id | slugify | prepend: "changes-" %} + + + {%- endif %} +
  • diff --git a/_includes/activity/meta.html b/_includes/activity/meta.html new file mode 100644 index 0000000..0ae1679 --- /dev/null +++ b/_includes/activity/meta.html @@ -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 "· ". 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 -%} + +{{ include.row.type_word | escape }} + +{%- if include.tag != false and include.row.tag != "" %} +· {{ include.row.tag | escape }} +{%- endif -%} diff --git a/_includes/activity/summary.html b/_includes/activity/summary.html new file mode 100644 index 0000000..7895e6f --- /dev/null +++ b/_includes/activity/summary.html @@ -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 . 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 -%}{{ activity_part }}{%- else -%}{{ activity_part }}{%- endif -%} +{%- endfor -%} diff --git a/_includes/activity/title.html b/_includes/activity/title.html new file mode 100644 index 0000000..1d35e35 --- /dev/null +++ b/_includes/activity/title.html @@ -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 != "" %} {{ include.row.version | escape }}{% endif -%} diff --git a/_layouts/activity.html b/_layouts/activity.html index d1a84fb..e08eae0 100644 --- a/_layouts/activity.html +++ b/_layouts/activity.html @@ -1,62 +1,131 @@ --- layout: default --- +{% comment %} + The /activity/ log, read from site.data.activity_view (the header comment of + _plugins/activity_generator.rb documents it): months, newest first, each holding its days, + each holding that day's rows in the view's order. Month and day ids are the anchors that + other pages link to. Nothing in the view is escaped: the includes in _includes/activity/ + escape every field. mathjax_ignore stops MathJax, which loads on every page, from + typesetting a "$" in a summary. + Without JavaScript the filter and the "N changes" toggles are hidden, and every row and PR + link shows. With it, CSS keyed on the js class that _layouts/default.html sets before first + paint shows the filter and collapses the PR lists, and the script below adds the behaviour. + If the script fails, it removes that class, so everything shows again. +{% endcomment %} +{%- assign activity = site.data.activity_view -%} +
    {{ content }} +{%- if activity.months.size > 0 %} +
    + + + + + +
    +

    + +{%- for month in activity.months %} +
    +

    {{ month.label | escape }}

    +
      + {%- for day in month.days %} +
    1. +
        + {%- for row in day.rows %} + {% include activity/log-row.html row=row %} + {%- endfor %} +
      +
    2. + {%- endfor %} +
    +
    +{%- endfor %} +

    The log begins on .

    +{%- else %} +

    No activity has been recorded yet.

    +{%- endif %} +
    -{% comment %} - Activity entries live in _data/activity/-.yml, one file per UTC day covered - and stream. Files are append-only: a re-run adds new entries after the existing ones and - never changes or removes one (see "News and Activity" in README.md). Flatten every file into one - list in filename order, group it by day (group_by_exp keeps that order within a day), - sort the days newest first, then group the days by month. Sorting whole days rather - than entries keeps the order stable, since Liquid's sort is not a stable sort. -{% endcomment %} -{% assign files = "" | split: "" %} -{% for file in site.data.activity %} - {% assign files = files | push: file[0] %} -{% endfor %} -{% assign files = files | sort %} -{% assign entries = "" | split: "" %} -{% for file in files %} - {% assign entries = entries | concat: site.data.activity[file] %} -{% endfor %} -{% assign days = entries | group_by_exp: "entry", "entry.date | date: '%Y-%m-%d'" | sort: "name" | reverse %} -{% assign months = days | group_by_exp: "day", "day.name | slice: 0, 7" %} + diff --git a/_layouts/default.html b/_layouts/default.html index 25cd390..e757f36 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -8,6 +8,9 @@ + + + - +