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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ When available, prefer skills over ad-hoc approaches:
</Update>
```
Always prompt the user for the `tags` value (e.g., `"Terraform Provider"`, `"CLI"`) before generating an entry.
- **Product updates** (`changelog/product-updates.mdx`) — a separate page whose RSS feed the Kosli app shows in its What's new menu. Follow `docs/writing-product-updates.md`: keep each `id` unchanged once published, and use only the tags it lists to limit an entry to some organizations.

## Deployment

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mint update # Update the mint CLI tool
- **`config/`** — Split config: `navigation.json` (page routing — every new page must be added here), `redirects.json`, `footer.json`.
- **Content directories** — `understand_kosli/`, `getting_started/`, `administration/`, `user/`, `integrations/`, `tutorials/`, `implementation_guide/`, `labs/`, `troubleshooting/`, `faq/`, and the reference directories `client_reference/`, `helm/`, `policy-reference/`, `terraform-reference/`, `template-reference/`, `github-action-reference/`. Directory names don't decide where a page appears; `config/navigation.json` does.
- **`snippets/`** — Reusable MDX fragments (only for content used in 2+ pages)
- **`changelog/`** — Product changelog
- **`changelog/`** — Product changelog. `changelog/product-updates.mdx` also feeds the What's new menu in the Kosli app: see [Writing a product update](/docs/writing-product-updates.md) before adding an entry.

See [`CLAUDE.md`](CLAUDE.md) for full authoring conventions.

Expand Down
4 changes: 3 additions & 1 deletion changelog/product-updates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ hidden: true
<span id="product-updates-page" hidden />

{/*
Add new entries at the top, newest first. Tags and the description are for the RSS feed and are hidden on this page with CSS.
Add new entries at the top, newest first. Read docs/writing-product-updates.md first: the Kosli app shows
these entries, and the tags decide which organizations see each one.
Tags and the description are for the RSS feed and are hidden on this page with CSS.
Keep the rss prop so the feed gets one item per entry, and keep id unique and unchanged once published,
because it is the item's anchor in the feed. Example:

Expand Down
107 changes: 107 additions & 0 deletions docs/writing-product-updates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Writing a product update

[`changelog/product-updates.mdx`](/changelog/product-updates.mdx) is the list of new
features and improvements in the Kosli app. It is not the CLI and Terraform changelog in
`changelog/index.mdx`, which a workflow writes from release tags.

Each entry on the page is also an item in the page's RSS feed,
`https://docs.kosli.com/changelog/product-updates/rss.xml`. The Kosli app reads that feed
and shows the entries in its **What's new** menu, with an unread badge. So an entry is
written for two readers: someone on the docs page, and someone in the app who sees only
the title and a one-sentence summary.

## Add an entry

Add the entry at the top of the page, above the newest one, and open a PR as for any other
docs change. Copy this shape:

```mdx
<Update
label="November 10, 2026"
id="new-navigation"
tags={["new-navigation"]}
description="Kosli's sidebar is now grouped into Home, Evidence, Control and Governance."
rss={{
title: "Finding your way around Kosli's new navigation",
description: "Kosli's sidebar is now grouped into Home, Evidence, Control and Governance.",
date: "2026-11-10"
}}
Comment thread
jumboduck marked this conversation as resolved.
Resolved
>

## Finding your way around Kosli's new navigation

What changed and why it matters to the reader, with links to the docs pages that explain
it.

</Update>
```

| Field | What it does | Rules |
|---|---|---|
| `label` | The date shown beside the entry on the docs page. | Month, day and year, for example `November 10, 2026`. |
| `id` | The anchor of the entry, and how the app recognizes it. | Lowercase words joined by hyphens. Unique on the page. **Never change it once published**: the app treats a new `id` as a new update, unread for every user. |
| `tags` | Which organizations see the entry in the app. | See [Show an entry only to some organizations](#show-an-entry-only-to-some-organizations). |
| `description` | Summary kept for the page. | The same sentence as `rss.description`. It is hidden on the page. |
| `rss.title` | The title in the app. | Short, as for the `##` heading. Plain text: markup is removed. |
| `rss.description` | The summary under the title in the app. | One sentence. Plain text: markup is removed. |
| `rss.date` | When the entry was published. The app lists unread entries first, then the newest. | `YYYY-MM-DD`. Add a time (`2026-11-12T08:00:00Z`) only to order two entries on the same day. |

The body is for the docs page only. The app links to it but does not show it.

## Show an entry only to some organizations

Some features are turned on one organization at a time. An entry about such a feature
must only show in the app for organizations that have the feature. A tag does that: the
server maps some tags to a feature flag, and it hides the entry from every organization
where that flag is off. When the flag is turned on for an organization later, the entry
shows there as unread.

These are the tags the server knows:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the current list and would need updating as we add more things?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbevc1 Correct. Maybe we should not have this list here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if this is going to change we should either reword this or remove it from here, because it's a high risk of rot


| Tag | Shows the entry to organizations with |
|---|---|
| `new-navigation` | The new navigation |
| `new-trail-page` | The new trail page |

- **Any other tag is ignored**, and the entry shows to every organization. So check the
spelling: a mistyped tag shows the entry to everyone.
- **Keep the tag after the feature reaches every organization.** It does no harm, and
removing it changes nothing in the app.
- **For a new tag**, ask the server team (open an issue in `kosli-dev/server`) before you
publish. They add it to `PRODUCT_UPDATE_TAG_FLAGS` in `src/lib/feature_flags.py`. Until
that change is deployed, the entry shows to every organization.

Tags use neutral names, not the names of the feature flags, because the feed is public.

### Turn the feature on for more organizations

An entry with a tag follows its feature flag, so you change who sees it in LaunchDarkly,
not in the docs:

1. Find the flag for the tag in `PRODUCT_UPDATE_TAG_FLAGS` in `src/lib/feature_flags.py`
in `kosli-dev/server`. Each tag points to the function that checks the flag, and that
function names the flag.
2. In LaunchDarkly, turn that flag on for the organizations, or ask the team that owns the
feature to do it. The flag is the feature itself, not only the entry: the organization
gets the feature and the entry together.
3. The entry shows as unread for the users of those organizations on their next page
load. Nothing changes in the docs.

The What's new menu itself is behind a flag too, so an organization sees no entry until
the menu is turned on for it. Ask the server team to turn it on.

## After the PR merges

- The app reads the feed every five minutes, so a merged entry shows in the app within a
few minutes of the docs deploy.
- Each user sees the entry as unread until they open the What's new menu. The first
time the app shows the menu to a user, every entry published by then counts as read:
the unread badge is for updates that come after that.
- Entries older than twelve months are never unread.

## Change or remove an entry

- **Fix the text** at any time. Keep the `id`, so users who have read the entry do not
see it as unread again.
- **Remove an entry** by deleting its `<Update>` block. It leaves the app at the next read
of the feed.
Loading