Update the "What's New" home-page feed¶
The home page of the SyRF web app shows a permanent "What's New" section with the newest update featured at the top and earlier updates collapsed underneath. The section is driven by a static JSON feed shipped with the web app. You edit its reviewed YAML source, docs/content/whats-new.yaml, and regenerate the JSON: updating it for a new release is a content-only change, no Angular code required.
The same YAML file also holds the runtime What's New entries for features released at runtime (runtime:, see below). Those are never written to the public JSON feed: they go only into the server help bundle.
Files¶
| File | Purpose |
|---|---|
| docs/content/whats-new.yaml | The reviewed source. Edit this for each release. |
| src/services/web/src/assets/data/whats-new.json | The feed the web app fetches (assets/data/whats-new.json). Generated from the YAML; never edit it by hand. |
| src/services/web/src/assets/data/whats-new.schema.json | JSON Schema for the generated feed. The validator checks the feed against it in pre-commit and CI. |
| src/services/web/src/app/info/home/whats-new/ | The Angular component that renders the feed. You only touch this if the shape of the data needs to change. |
| src/charts/syrf-common/scripts/compile-release-content.ts | The content compiler. It puts the runtime: entries into the server help bundle (schema 2), never into public output. |
Feed shape¶
The source is an object with an updates array, newest first, and an optional runtime array (next section). There is no latest or past key: the first item of updates is the featured one. Each update has id, date, title and userGuidePath, plus either highlights[] or body, and an optional featureId:
- First entry (featured): requires
highlights(1-6 items, each withtitleandbody) anduserGuidePath. It is rendered as the featured update, with numbered highlights and a release-notes link. - Every later entry (past): requires
body(plain text);userGuidePathis optional. These render as collapsed timeline rows that expand to show the body. featureId(optional): a feature ID fromsrc/charts/syrf-common/feature-definitions.yaml. It links the announcement to the feature for the registry validator and is stripped from the shipped JSON.
updates:
- id: 2026-08-bulk-pdf-upload
date: 18th August 2026
title: Bulk Upload PDFs
userGuidePath: whats-new/whats-new-bulk-pdf-upload.html
highlights:
- title: Attach a folder of full-text PDFs to a search
body: One or two sentences in user-facing language.
- id: 2026-04
date: 28th April 2026
title: April 2026
userGuidePath: whats-new/whats-new-april-2026.html
body: |-
Summary of the April 2026 release.
Multi-line bodies use a block scalar (`|-`), which keeps the line breaks.
The generator writes the JSON with $schema first and the same field order, so the shipped bytes change only when the content does. It adds one field to each update, straight after date: isoDate, the machine form of date, so the browser can merge this feed with runtime entries in date order.
date is human-readable, in the form <ordinal> <FullMonth> <YYYY> (for example 28th April 2026), or <FullMonth> <YYYY> for a month-only release. It is parsed strictly: the ordinal suffix must match the day (1st, 2nd, 3rd, 11th, 22nd) and the day must exist in that month. 18th August 2026 becomes isoDate: 2026-08-18; August 2026 becomes 2026-08. Anything else fails generation with the exact form expected. Never write isoDate yourself; the generator refuses it.
Validated in pre-commit and CI.
pnpm run validate:generated(theValidate Generated Codelane, which runs for any change to the YAML, the feed, its schema or auser-guide/page) checks that the JSON was regenerated from the YAML, that it matcheswhats-new.schema.json, that everyuserGuidePathnames a published user-guide page and that everyfeatureIdexists. The component treats a first entry withouthighlightsas "no featured update" and hides the section; the schema check catches that before merge.
Runtime What's New entries (feature releases)¶
A feature released at runtime (6D release Apply, communication.whatsNew: announce, the Release default) announces itself through a runtime: entry, not through updates. The entry is compiled into the server help bundle only; the API discloses it only to people the feature has been released to (6G PR B), and the browser renders it only from then on (6G PR D). It is never in whats-new.json, the public bundle or any web asset.
runtime:
- id: project-membership-disable
featureId: project-membership-disable
title: Disable a project membership
body: |-
Project administrators can now disable a member's access to a project without removing them, under *Project Settings > Membership > Current Members*. The member's groups and past review work are kept, and the membership can be re-enabled at any time.
topicId: project-membership-status
| Field | Rule |
|---|---|
id |
A stable lower-case ID, unique among runtime entries (usually the feature ID). |
featureId |
A feature in src/charts/syrf-common/feature-definitions.yaml whose resolved communication.whatsNew is announce. A quiet feature has no entry. |
stages |
Optional: LimitedRelease, GeneralRelease, or both (the default). At most one entry per feature and stage. |
title |
A short heading, 1-200 characters. |
body |
Markdown, 1-4000 characters. No images, no raw HTML (including <...> autolinks), and links only to https:// addresses or to user-guide pages by path or permalink. No bare http:// or www. address. |
topicId |
One of the feature's topicIds. "Read the guide" opens that topic's public guide page and section, taken from its docs/content/release-content.yaml entry (source and section). |
There is no date: the date shown is the day the release was confirmed in that environment, so staging and production each show their own.
Write plainly, in UK English, for the people who will use the feature: what they can now do and where.
When it is needed. The registry validator warns while the feature is Developing and fails once it is past Developing if an announcing feature has no entry. Release Apply refuses an announced release whose feature and stage have no entry in the verified server bundle (WhatsNewEntryMissing); applying quietly with a reason is the escape. See Applying a release.
Check it: pnpm run validate:generated runs the validator (feature, topic, stages, one entry per feature and stage, body rules) and the compiler tests, including a byte scan proving no runtime title reaches public output.
Release workflow¶
When cutting a new release:
- Open
docs/content/whats-new.yaml. - Convert the current first entry to the past shape: remove
highlightsand write abodysummary (userGuidePathmay stay). - Insert the new release at the top of
updateswith a freshid(e.g.2026-05). - Set
userGuidePathto the What's New page in the user guide (e.g.whats-new/whats-new-may-2026.html): its path with.htmlor.md, or its front-matterpermalink:if it has one. No leading slash, no scheme: theUserGuideUrlPipeprepends the environment-appropriate base URL (production vs. staging vs. preview). - Add 2-4
highlights. Keep eachtitleshort (one bold-voiced sentence, no trailing full stop, the template adds one) and eachbodyto one or two sentences in user-facing language. - Regenerate and check, from the repository root:
- Commit the YAML, the regenerated
whats-new.jsonand.generated-checksums.json. The feed ships on the next web deployment.
Hiding the section¶
Set updates to an empty list in docs/content/whats-new.yaml and regenerate:
The section renders nothing. Useful between releases or during a quiet period.
Rolling back¶
Because the feed is just a static asset, rolling back is as simple as reverting the commit. No database migration, no cache invalidation; users pick up the previous feed on their next page load.
Design decisions¶
- Why a JSON feed, not a hard-coded component? Non-engineers (e.g. product owner, designer) can update the home-page release section by editing one file in the repo. The bar to ship a release note is a PR, not an Angular build.
- Why YAML as the source? Multi-line release prose reads and reviews better as YAML block text than as escaped JSON strings, and the source can carry build-time links (
featureId) that the browser never needs. The JSON stays the runtime format, so the web app and its fetch path are unchanged. - Why runtime entries in the same file but never in the JSON? One place to write What's New text, linked to the feature by
featureIdand to the guide bytopicId. A runtime release may reach only some people, so its text must not be in a public asset before the server decides who may see it. - Why static asset, not an API endpoint? A new service is overkill for one file that changes ~monthly. Serving from
/assets/data/whats-new.jsonuses the same edge-cached path as the web app itself, so there's zero extra infrastructure. - Why one
updatesarray? The home page and latest-release announcement now share one feed, so users do not see a current release at the top of the page and an outdated hardcoded archive farther down. - Why no loading spinner? If the fetch is slow or fails the section simply stays hidden. The home page is not meant to be blocked by release notes that are nice to have; an invisible failure mode is the correct failure mode here.
Testing your changes¶
The generator, compiler and validator tests cover isoDate parsing, the runtime list staying out of the public feed and every runtime-entry rule:
pnpm exec tsx --test src/charts/syrf-common/scripts/generate-feature-definitions.test.ts \
src/charts/syrf-common/scripts/compile-release-content.test.ts scripts/feature-registry.test.ts
Unit tests cover featured-update rendering, past-update rendering, empty archive handling, and error-handling logic:
cd src/services/web
pnpm exec ng test --no-watch --include="src/app/info/home/whats-new/**/*.spec.ts"
For a local visual check, run the dev server and inspect the home page:
Changing the feed shape¶
If a release genuinely needs something the current schema can't express (an image, a link list, a carousel of multiple releases) update in this order:
- Update
whats-new.schema.jsonwith the new shape. - Update the TypeScript interfaces in
whats-new.component.tsto match. - Update the template and tests.
- Only then change
docs/content/whats-new.yamland regenerate. If the new shape needs a field the generator should not ship (likefeatureId), strip it inrenderWhatsNewinsrc/charts/syrf-common/scripts/generate-feature-definitions.ts.
Keep the feed backwards-compatible where practical — old feeds served from CDN caches should still render without crashing the home page.