Data

Changelog

Releases with version, date and label, their changes grouped by kind: Added, Changed, Fixed, Security and the rest.

#data#list#document#no-js

Demo

Preview

1.8.0 Autumn Latest

Dark mode everywhere, and exports.

Added

  • Dark mode, following the system
  • CSV export of any table

Changed

  • Buttons are 2px taller

Fixed

  • The date picker skipped February in leap years

1.7.2 Security

Fixed

  • Sessions survive a server restart

Security

  • File names in the upload log are escaped

1.7.0

Deprecated

  • The old `theme` prop: use `tone`

Removed

  • Internet Explorer support

Docs

  • A page on forms

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/changelog.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/changelog.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/changelog.qwe
view ChangelogDemo() =    <Changelog id="changelog-demo" level={4} releases={[        { version: "1.8.0", title: "Autumn", date: "2026-10-28", tag: "Latest", summary: "Dark mode everywhere, and exports.", changes: {            Fixed: ["The date picker skipped February in leap years"],            Added: ["Dark mode, following the system", "CSV export of any table"],            Changed: ["Buttons are 2px taller"],        } },        { version: "1.7.2", date: "2026-10-02", href: "#v1.7.2", tag: "Security", tag_tone: "danger", changes: [            { kind: "Security", text: "File names in the upload log are escaped" },            { kind: "Fixed", text: "Sessions survive a server restart" },        ] },        { version: "1.7.0", date: "2026-09-14", changes: {            Deprecated: ["The old `theme` prop: use `tone`"], Removed: ["Internet Explorer support"], Docs: ["A page on forms"],        } },    ]}/>

Source

ui/changelog.qwe
// Changelog: releases, newest first, each with its version, date, an optional// label and its changes grouped by kind (Added, Changed, Deprecated, Removed,// Fixed, Security, as in "Keep a Changelog").////   <Changelog releases={[//       { version: "1.8.0", date: "2026-10-28", tag: "Latest", changes: {//           Added: ["Dark mode", "CSV export"],//           Fixed: ["The date picker skipped February"],//       } },//       { version: "1.7.2", date: "2026-10-02", href: "/releases/1.7.2",//         changes: [{ kind: "Security", text: "Escape file names in the log" }] },//   ]}/>//// releases: maps of { version, date?, title?, tag?, tag_tone?, href?, summary?,// changes }: date an ISO date ("2026-10-28", shown as Oct 28, 2026); title a name// beside the version; tag a label ("Latest", "Breaking"), in tag_tone// ("accent" by default, or "success", "warning", "danger", "info", "neutral");// href links the version; summary a paragraph before the changes. changes: a map// from kind to a list of texts, or a list of { kind, text }; the kinds are shown// in the order above, then any others in the order they come. level: the heading// level of each release (2 by default). id: the element's id. Each release is an// <article> with an id of "v" and its version, so a link can point at it.import ui/theme.qwe use ui_clslet changelog_css = css`.ui-changelog { display: grid; gap: 0; font: 14px/1.55 var(--ui-font); color: var(--ui-fg); }.ui-changelog-rel { display: grid; grid-template-columns: 140px minmax(0, 1fr); gap: 4px 28px; padding: 22px 0; scroll-margin-top: 16px; }.ui-changelog-rel + .ui-changelog-rel { border-top: 1px solid var(--ui-border); }.ui-changelog-rel:first-child { padding-top: 0; }.ui-changelog-side { color: var(--ui-muted); font-size: 13px; padding-top: 4px; }.ui-changelog-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin: 0; font-size: 20px; font-weight: 650; line-height: 1.3; }.ui-changelog-head a { color: inherit; text-decoration: none; }.ui-changelog-head a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-changelog-head a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-changelog-title { color: var(--ui-muted); font-weight: 500; }.ui-changelog-tag { padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; --t: var(--ui-accent); color: var(--t);  background: color-mix(in srgb, var(--t) 13%, transparent); }.ui-changelog-tag-success { --t: var(--ui-success); }.ui-changelog-tag-warning { --t: var(--ui-warning); }.ui-changelog-tag-danger { --t: var(--ui-danger); }.ui-changelog-tag-info { --t: var(--ui-info); }.ui-changelog-tag-neutral { --t: var(--ui-muted); }.ui-changelog-summary { margin: 6px 0 0; color: var(--ui-muted); }.ui-changelog-main { min-width: 0; }.ui-changelog-group { margin-top: 14px; }.ui-changelog-kind { display: inline-block; margin: 0 0 6px; padding: 1px 8px; border-radius: var(--ui-radius-sm); font-size: 12px; font-weight: 600;  letter-spacing: .03em; text-transform: uppercase; --t: var(--ui-muted); color: var(--t); background: color-mix(in srgb, var(--t) 12%, transparent); }.ui-changelog-added { --t: var(--ui-success); }.ui-changelog-changed { --t: var(--ui-info); }.ui-changelog-deprecated { --t: var(--ui-warning); }.ui-changelog-removed { --t: var(--ui-danger); }.ui-changelog-fixed { --t: var(--ui-accent); }.ui-changelog-security { --t: var(--ui-danger); }.ui-changelog ul { margin: 0; padding-left: 20px; }.ui-changelog li + li { margin-top: 3px; }@media (max-width: 560px) { .ui-changelog-rel { grid-template-columns: 1fr; } .ui-changelog-side { order: 2; padding: 0; } }`let changelog_kinds = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"]// The changes as [{ kind, items }], kinds in the usual order, then the others.fn changelog_groups(changes) -> list {    let pairs = type_of(changes) == "map"        ? flatten(for [k, v] in changes => (type_of(v) == "list" ? v : [v]) | map((t) => { kind: k, text: t }))        : (changes ?? [])    let seen = unique(pairs | map((p) => p.kind ?? "Changed"))    let order = (changelog_kinds | filter((k) => index_of(seen, k) != null)) + (seen | filter((k) => index_of(changelog_kinds, k) == null))    order | map((k) => { kind: k, items: pairs | filter((p) => (p.kind ?? "Changed") == k) | map((p) => p.text) })}view Changelog(releases: list, level: int = 2, id: str? = null, class: str? = null) {    style global changelog_css    <div class={ui_cls(["ui-changelog", class])} id={id}>        {for r in releases => {            let vid = `v${re.replace(str(r.version), "[^A-Za-z0-9_.-]+", "-")}`            let heading = [                r.href != null ? <a href={r.href}>{r.version}</a> : <span>{r.version}</span>,                r.title != null && [" ", <span.ui-changelog-title>{r.title}</span>],                r.tag != null && " ",                r.tag != null && <span class={ui_cls(["ui-changelog-tag", r.tag_tone != null && `ui-changelog-tag-${r.tag_tone}`])}>{r.tag}</span>,            ]            <article.ui-changelog-rel id={vid} aria-labelledby={`${vid}-h`}>                <div.ui-changelog-side>                    {r.date != null && <time datetime={r.date}>{date.valid(r.date) ? date.format(r.date, "MMM D, YYYY") : r.date}</time>}                </div>                <div.ui-changelog-main>                    {match level {                        3 => <h3.ui-changelog-head id={`${vid}-h`}>{heading}</h3>                        4 => <h4.ui-changelog-head id={`${vid}-h`}>{heading}</h4>                        _ => <h2.ui-changelog-head id={`${vid}-h`}>{heading}</h2>                    }}                    {r.summary != null && <p.ui-changelog-summary>{r.summary}</p>}                    {for g in changelog_groups(r.changes) => <section.ui-changelog-group aria-label={`${g.kind} in ${r.version}`}>                        <p class={ui_cls(["ui-changelog-kind", `ui-changelog-${lower(re.replace(g.kind, "[^A-Za-z]+", ""))}`])}>{g.kind}</p>                        <ul>{for t in g.items => <li>{t}</li>}</ul>                    </section>}                </div>            </article>        }}    </div>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On Changelog: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.