Skip to content

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 with title and body) and userGuidePath. It is rendered as the featured update, with numbered highlights and a release-notes link.
  • Every later entry (past): requires body (plain text); userGuidePath is optional. These render as collapsed timeline rows that expand to show the body.
  • featureId (optional): a feature ID from src/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 (the Validate Generated Code lane, which runs for any change to the YAML, the feed, its schema or a user-guide/ page) checks that the JSON was regenerated from the YAML, that it matches whats-new.schema.json, that every userGuidePath names a published user-guide page and that every featureId exists. The component treats a first entry without highlights as "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:

  1. Open docs/content/whats-new.yaml.
  2. Convert the current first entry to the past shape: remove highlights and write a body summary (userGuidePath may stay).
  3. Insert the new release at the top of updates with a fresh id (e.g. 2026-05).
  4. Set userGuidePath to the What's New page in the user guide (e.g. whats-new/whats-new-may-2026.html): its path with .html or .md, or its front-matter permalink: if it has one. No leading slash, no scheme: the UserGuideUrlPipe prepends the environment-appropriate base URL (production vs. staging vs. preview).
  5. Add 2-4 highlights. Keep each title short (one bold-voiced sentence, no trailing full stop, the template adds one) and each body to one or two sentences in user-facing language.
  6. Regenerate and check, from the repository root:
pnpm --filter syrf-common-chart generate:feature-definitions
pnpm run validate:generated
  1. Commit the YAML, the regenerated whats-new.json and .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:

updates: []

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 featureId and to the guide by topicId. 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.json uses the same edge-cached path as the web app itself, so there's zero extra infrastructure.
  • Why one updates array? 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:

cd src/services/web
pnpm start
# open http://localhost:4200/

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:

  1. Update whats-new.schema.json with the new shape.
  2. Update the TypeScript interfaces in whats-new.component.ts to match.
  3. Update the template and tests.
  4. Only then change docs/content/whats-new.yaml and regenerate. If the new shape needs a field the generator should not ship (like featureId), strip it in renderWhatsNew in src/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.