Skip to content

feat: per-package per-version changelog + /changelog page + auto-enforce on version bumps#49

Merged
vivek7405 merged 8 commits into
mainfrom
feat/changelog-system
May 21, 2026
Merged

feat: per-package per-version changelog + /changelog page + auto-enforce on version bumps#49
vivek7405 merged 8 commits into
mainfrom
feat/changelog-system

Conversation

@vivek7405
Copy link
Copy Markdown
Collaborator

Summary

Three layers, one feature.

1. Architecture: per-package per-version under changelog/

changelog/
  README.md                   conventions + entry format
  core/0.6.0.md               one file per release
  server/0.7.1.md
  cli/0.7.0.md
  ts-plugin/0.4.0.md
  ui/0.2.0.md
  …

Each file has frontmatter (package, version, date, commit_count) and a body that groups conventional-commits under ## Breaking / ## Features / ## Performance / ## Fixes with PR + commit links.

The model: a version bump in any packages/<pkg>/package.json triggers a changelog file, populated from the commits since the last bump of the same package. chore / refactor / test / docs / style / build / ci commits never appear (they don't change the user contract).

scripts/backfill-changelog.js walks every packages/<pkg>/package.json history, finds version-changing commits, scans the inter-bump commits that touched that package, and writes any missing <pkg>/<version>.md. Re-runs are idempotent (existing files are never overwritten, so hand-curation survives).

2. Backfill from project inception

43 files generated across all 5 packages: core (7 versions), server (14), cli (18), ts-plugin (3), ui (1).

3. Website: /changelog page with responsive nav

website/app/changelog/page.ts reads every backfilled file at SSR time, parses the minimal markdown shape the generator emits (h1, h2, bulleted lists, links, inline code, bold), and renders one card per release with a package-coloured badge, version, date, and commit count.

Layout nav (website/app/layout.ts) gets the new "Changelog" link. The header now uses flex-wrap + gap-y on both row and nav, so the six chrome elements (Docs, UI, Changelog, Blog Demo, GitHub, theme-toggle) wrap into a second row on narrow viewports instead of overflowing. Mobile / laptop / desktop all unblocked.

Verified locally: GET /changelog returns 200 with 156 KB containing every version across all packages.

4. Automation: hooks that won't let an agent forget

Two gates fire when staged diffs bump a packages/<pkg>/package.json version without a matching changelog/<pkg>/<version>.md:

  • .hooks/pre-commit refuses the commit and prints the exact fix (node scripts/backfill-changelog.js && git add changelog/).
  • .claude/hooks/changelog-nudge.sh is a Claude Code PreToolUse hook on Bash that catches git commit invocations from agents and emits permissionDecision: deny so the tool call is refused with the same reason.

Root AGENTS.md gets a "Changelog: per-package, per-version, auto-generated" section spelling out the rule for AI agents working in the monorepo.

Test plan

  • node scripts/backfill-changelog.js → 43 entries created across 5 packages.
  • GET /changelog returns 200, renders all entries, package badges colour-coded.
  • Pre-commit hook refused a simulated core version bump that lacked a matching file. Fixed an off-by-one in the awk slice that previously emitted core/ instead of core.
  • Existing npm test (1150) + browser (265) + e2e (48) suites continue to pass since nothing in the runtime changed.

Follow-ups (separate PRs)

  • Hand-edit a few breaking: entries (core 0.5.0 signals migration, core 0.4.0 light-DOM defaults) to add migration code samples.
  • Add a "filter by package" affordance on /changelog.
  • Optional: a release script that bundles unreleased entries into a releases/<version>.md snapshot for the framework as a whole.

vivek7405 added 8 commits May 21, 2026 18:32
Introduce a changelog/ directory at the repo root with one md file
per (package, version) tuple:

  changelog/
    README.md               architecture + conventions
    core/0.6.0.md           one file per release
    server/0.7.1.md
    cli/0.7.0.md
    ts-plugin/0.4.0.md
    ui/0.2.0.md
    …

The shape (per-package per-version, not per-commit) matches the way
we publish: every change of a packages/<pkg>/package.json version
field becomes a release entry. Each entry frontmatters package,
version, date, commit_count; the body groups qualifying commits
(feat / fix / breaking / perf) under Breaking / Features /
Performance / Fixes headings with PR links and commit SHAs.

scripts/backfill-changelog.js walks every packages/<pkg>/package.json
in git history, finds the commits that changed the version field,
and writes <pkg>/<version>.md for any (package, version) tuple that
does not yet have an entry. Existing files are left alone so
hand-curation survives re-runs.

Backfilled 43 files across the 5 packages from project inception
through 0.6.0 (core), 0.7.1 (server), 0.7.0 (cli), 0.4.0 (ts-plugin),
and 0.2.0 (ui).
Add website/app/changelog/page.ts that reads changelog/<pkg>/*.md
at SSR time, parses YAML-ish frontmatter and the minimal markdown
shape the backfill generator emits (h1, h2, bulleted lists, links,
inline code, bold), sorts entries by date desc, and renders one
card per release with a package-coloured badge, version, date, and
commit count.

Add the Changelog link to the layout nav. The header now uses
flex-wrap with gap-y-3 and the nav itself uses flex-wrap with
gap-x-3 sm:gap-x-4 + gap-y-2 so the now-six chrome elements
(Docs, UI, Changelog, Blog Demo, GitHub, theme-toggle) wrap into a
second row on narrow viewports instead of overflowing.

Verified locally: GET /changelog returns 200 with 156 KB of
rendered HTML containing every backfilled version across all five
packages.
Add the "version bump triggers changelog" rule to the framework
AGENTS.md and back it with two gates:

1. .hooks/pre-commit refuses any commit whose staged diff bumps a
   packages/<pkg>/package.json version field without a matching
   changelog/<pkg>/<version>.md file. Prints the exact command the
   author needs (node scripts/backfill-changelog.js && git add
   changelog/).

2. .claude/hooks/changelog-nudge.sh is a PreToolUse hook that fires
   on Bash tool calls whose command matches git commit. It runs the
   same check and emits a permissionDecision: deny JSON payload, so
   Claude Code refuses the commit before it leaves the agent's
   shell. Wired into .claude/settings.json.

Both hooks are bypassable with --no-verify for emergencies. The AI
agent rule in AGENTS.md instructs agents to run the generator in
the same commit as the version bump, review the generated file,
and edit it in place for clarity before pushing.
The previous flex-wrap nav let the items wrap below the logo on
narrow viewports, but on iPhone XR (414px) the 5 links + theme
toggle still felt congested. Replace it with a real mobile menu.

Below md (768px): the logo, theme-toggle, and a hamburger button
sit on one row; the hamburger is a native <details>/<summary> that
pops the same 5 links into a dropdown panel. At md and up: hide the
mobile cluster, show the inline nav as before. CSS strips the
default disclosure triangle and swaps the hamburger / close icons
via .mobile-menu[open].

Progressive enhancement: native <details> works without JS, so the
menu opens / closes on every viewport even when scripts have not
yet hydrated.
Native <details> stays open on inner-anchor activation, so tapping
Changelog (or any other link) on mobile kept the panel visible.
Add a delegated click listener that strips the open attribute from
the parent <details> when any link inside .mobile-menu is activated.
Covers both regular target=_blank links and the same-origin client-
router-intercepted ones.
webjs.dev, example-blog, and ui.webjs.dev now share the same mobile
header layout: hamburger LEFT, theme-toggle RIGHT, with a native
<details>/<summary> dropdown panel anchored to the hamburger.

- webjs website: swap the order so the hamburger sits LEFT of the
  theme-toggle (was the reverse).
- example blog: drop the old fixed-drawer + body[data-menu-open]
  pattern in favour of the dropdown. Removes ~30 lines of CSS and
  matches the rest of the chrome surfaces. The mobile cluster is
  sm:hidden because the blog's existing inline nav is sm:flex.
- ui.webjs.dev: introduce the same dropdown pattern from scratch.
  The "Webjs" cross-link, previously hidden on small screens via
  hidden sm:inline, now lives in the dropdown panel where it's
  reachable on phones.

Docs site keeps its existing left-rail-drawer pattern (intentionally
untouched, since the docs UI needs a full sidebar, not a dropdown).

Same delegated click handler in every layout: any anchor activation
inside .mobile-menu strips the open attribute on the parent
<details>, so the panel auto-closes on navigation.
Tapping outside the open dropdown should dismiss it on a phone;
previously only the close icon (the toggled summary) closed it,
which felt off because the user has nothing visually grabbing
their attention to that one tap target.

Extend the existing delegated click handler in all three layouts
to walk `.mobile-menu[open]` and strip the open attribute on any
that don't contain the click target. The link-click branch is
unchanged: links inside the panel still close the panel directly.

Docs site keeps its own drawer pattern; this change does not touch
it.
@vivek7405 vivek7405 merged commit 1ac9e1c into main May 21, 2026
@vivek7405 vivek7405 deleted the feat/changelog-system branch May 21, 2026 13:30
vivek7405 added a commit that referenced this pull request May 21, 2026
Three packages have user-facing changes accumulated since their
last bump:

- @webjskit/core 0.6.0 -> 0.7.0
  Breaking: signals replace this.state / setState across the
  stack (#43). Slot projection cycle fix in light DOM (#44).
- @webjskit/cli 0.7.0 -> 0.8.0
  Scaffold templates updated for the signals migration (#43); a
  newly-scaffolded app now ships signal-based state out of the
  box and the scaffold tests assert the new shape.
- @webjskit/ui 0.2.0 -> 0.3.0
  Env-driven sibling URLs with localhost dev fallbacks (#42),
  workspace dep alignment (#35), signal migration in the
  registry components and ui-website (#43), and the responsive
  mobile menu / changelog page that landed alongside the
  changelog system (#49).

Cross-package dependency ranges in website, docs, examples/blog,
ui-website, server, and cli updated to track the new versions
(^0.6.0 -> ^0.7.0 for core, ^0.7.0 -> ^0.8.0 for cli, ^0.2.0 ->
^0.3.0 for ui). package-lock.json regenerated.

After this lands on main, the Auto-release workflow will publish
all three to npm and create matching GitHub Releases.
vivek7405 added a commit that referenced this pull request May 21, 2026
…rce on version bumps (#49)

* feat(changelog): per-package per-version changelog + backfill from git

Introduce a changelog/ directory at the repo root with one md file
per (package, version) tuple:

  changelog/
    README.md               architecture + conventions
    core/0.6.0.md           one file per release
    server/0.7.1.md
    cli/0.7.0.md
    ts-plugin/0.4.0.md
    ui/0.2.0.md
    …

The shape (per-package per-version, not per-commit) matches the way
we publish: every change of a packages/<pkg>/package.json version
field becomes a release entry. Each entry frontmatters package,
version, date, commit_count; the body groups qualifying commits
(feat / fix / breaking / perf) under Breaking / Features /
Performance / Fixes headings with PR links and commit SHAs.

scripts/backfill-changelog.js walks every packages/<pkg>/package.json
in git history, finds the commits that changed the version field,
and writes <pkg>/<version>.md for any (package, version) tuple that
does not yet have an entry. Existing files are left alone so
hand-curation survives re-runs.

Backfilled 43 files across the 5 packages from project inception
through 0.6.0 (core), 0.7.1 (server), 0.7.0 (cli), 0.4.0 (ts-plugin),
and 0.2.0 (ui).

* feat(website): /changelog page + Changelog nav link, responsive nav

Add website/app/changelog/page.ts that reads changelog/<pkg>/*.md
at SSR time, parses YAML-ish frontmatter and the minimal markdown
shape the backfill generator emits (h1, h2, bulleted lists, links,
inline code, bold), sorts entries by date desc, and renders one
card per release with a package-coloured badge, version, date, and
commit count.

Add the Changelog link to the layout nav. The header now uses
flex-wrap with gap-y-3 and the nav itself uses flex-wrap with
gap-x-3 sm:gap-x-4 + gap-y-2 so the now-six chrome elements
(Docs, UI, Changelog, Blog Demo, GitHub, theme-toggle) wrap into a
second row on narrow viewports instead of overflowing.

Verified locally: GET /changelog returns 200 with 156 KB of
rendered HTML containing every backfilled version across all five
packages.

* feat(changelog): auto-enforce changelog when a package version bumps

Add the "version bump triggers changelog" rule to the framework
AGENTS.md and back it with two gates:

1. .hooks/pre-commit refuses any commit whose staged diff bumps a
   packages/<pkg>/package.json version field without a matching
   changelog/<pkg>/<version>.md file. Prints the exact command the
   author needs (node scripts/backfill-changelog.js && git add
   changelog/).

2. .claude/hooks/changelog-nudge.sh is a PreToolUse hook that fires
   on Bash tool calls whose command matches git commit. It runs the
   same check and emits a permissionDecision: deny JSON payload, so
   Claude Code refuses the commit before it leaves the agent's
   shell. Wired into .claude/settings.json.

Both hooks are bypassable with --no-verify for emergencies. The AI
agent rule in AGENTS.md instructs agents to run the generator in
the same commit as the version bump, review the generated file,
and edit it in place for clarity before pushing.

* fix(hooks): correct off-by-one in awk slice that picked up trailing slash in pkg name

* feat(website): mobile hamburger menu using native <details>/<summary>

The previous flex-wrap nav let the items wrap below the logo on
narrow viewports, but on iPhone XR (414px) the 5 links + theme
toggle still felt congested. Replace it with a real mobile menu.

Below md (768px): the logo, theme-toggle, and a hamburger button
sit on one row; the hamburger is a native <details>/<summary> that
pops the same 5 links into a dropdown panel. At md and up: hide the
mobile cluster, show the inline nav as before. CSS strips the
default disclosure triangle and swaps the hamburger / close icons
via .mobile-menu[open].

Progressive enhancement: native <details> works without JS, so the
menu opens / closes on every viewport even when scripts have not
yet hydrated.

* fix(website): close mobile menu when a link inside it is clicked

Native <details> stays open on inner-anchor activation, so tapping
Changelog (or any other link) on mobile kept the panel visible.
Add a delegated click listener that strips the open attribute from
the parent <details> when any link inside .mobile-menu is activated.
Covers both regular target=_blank links and the same-origin client-
router-intercepted ones.

* feat(website,blog,ui): unify mobile-menu pattern across the three apps

webjs.dev, example-blog, and ui.webjs.dev now share the same mobile
header layout: hamburger LEFT, theme-toggle RIGHT, with a native
<details>/<summary> dropdown panel anchored to the hamburger.

- webjs website: swap the order so the hamburger sits LEFT of the
  theme-toggle (was the reverse).
- example blog: drop the old fixed-drawer + body[data-menu-open]
  pattern in favour of the dropdown. Removes ~30 lines of CSS and
  matches the rest of the chrome surfaces. The mobile cluster is
  sm:hidden because the blog's existing inline nav is sm:flex.
- ui.webjs.dev: introduce the same dropdown pattern from scratch.
  The "Webjs" cross-link, previously hidden on small screens via
  hidden sm:inline, now lives in the dropdown panel where it's
  reachable on phones.

Docs site keeps its existing left-rail-drawer pattern (intentionally
untouched, since the docs UI needs a full sidebar, not a dropdown).

Same delegated click handler in every layout: any anchor activation
inside .mobile-menu strips the open attribute on the parent
<details>, so the panel auto-closes on navigation.

* fix(website,blog,ui): close mobile menu on outside click too

Tapping outside the open dropdown should dismiss it on a phone;
previously only the close icon (the toggled summary) closed it,
which felt off because the user has nothing visually grabbing
their attention to that one tap target.

Extend the existing delegated click handler in all three layouts
to walk `.mobile-menu[open]` and strip the open attribute on any
that don't contain the click target. The link-click branch is
unchanged: links inside the panel still close the panel directly.

Docs site keeps its own drawer pattern; this change does not touch
it.
vivek7405 added a commit that referenced this pull request May 21, 2026
Three packages have user-facing changes accumulated since their
last bump:

- @webjskit/core 0.6.0 -> 0.7.0
  Breaking: signals replace this.state / setState across the
  stack (#43). Slot projection cycle fix in light DOM (#44).
- @webjskit/cli 0.7.0 -> 0.8.0
  Scaffold templates updated for the signals migration (#43); a
  newly-scaffolded app now ships signal-based state out of the
  box and the scaffold tests assert the new shape.
- @webjskit/ui 0.2.0 -> 0.3.0
  Env-driven sibling URLs with localhost dev fallbacks (#42),
  workspace dep alignment (#35), signal migration in the
  registry components and ui-website (#43), and the responsive
  mobile menu / changelog page that landed alongside the
  changelog system (#49).

Cross-package dependency ranges in website, docs, examples/blog,
ui-website, server, and cli updated to track the new versions
(^0.6.0 -> ^0.7.0 for core, ^0.7.0 -> ^0.8.0 for cli, ^0.2.0 ->
^0.3.0 for ui). package-lock.json regenerated.

After this lands on main, the Auto-release workflow will publish
all three to npm and create matching GitHub Releases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant