diff --git a/.claude/hooks/changelog-nudge.sh b/.claude/hooks/changelog-nudge.sh new file mode 100755 index 00000000..a5909bf9 --- /dev/null +++ b/.claude/hooks/changelog-nudge.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# +# Claude Code PreToolUse hook. +# +# Catches the case where the agent is ABOUT to commit a change that +# bumps a packages//package.json version but has not also +# generated a matching changelog//.md file. Fires on +# Bash tool calls whose command starts with `git commit`. +# +# Hard block: writes a JSON hookSpecificOutput with +# "permissionDecision": "deny" so the tool call is refused and the +# agent reads the included reason. The agent should then run: +# +# node scripts/backfill-changelog.js && git add changelog/ +# +# and retry the commit. +# +# Skipped outside a git work tree and when there are no staged +# version bumps. + +set -e + +# Read the hook payload from stdin so we can inspect the command +# the model is about to run. +INPUT=$(cat) + +# Only act on Bash tool calls whose command is `git commit ...`. +COMMAND=$(printf '%s' "$INPUT" | grep -oE '"command"\s*:\s*"[^"]+"' | head -1 | sed 's/.*: *"\(.*\)".*/\1/') +case "$COMMAND" in + *git*commit*) ;; + *) exit 0 ;; +esac + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + exit 0 +fi + +STAGED_PKG_BUMPS=$(git diff --cached --unified=0 -- 'packages/*/package.json' 2>/dev/null \ + | awk ' + /^diff --git/ { match($0, /packages\/[^\/]+\/package.json/); pkg = substr($0, RSTART+9, RLENGTH-22); next } + pkg && /^\+\s*"version":\s*"[^"]+"/ { + match($0, /"[0-9]+\.[0-9]+\.[0-9]+[^"]*"/); v = substr($0, RSTART+1, RLENGTH-2); + print pkg "@" v + }') + +if [ -z "$STAGED_PKG_BUMPS" ]; then + exit 0 +fi + +MISSING="" +for bump in $STAGED_PKG_BUMPS; do + pkg="${bump%@*}"; ver="${bump#*@}" + if [ ! -f "changelog/$pkg/$ver.md" ]; then + MISSING="$MISSING changelog/$pkg/$ver.md" + fi +done + +if [ -z "$MISSING" ]; then + exit 0 +fi + +# Emit a JSON denial so Claude Code refuses this tool call and +# surfaces the reason to the agent. +cat </package.json version bump but the matching changelog file(s) are missing:$MISSING\n\nRun: node scripts/backfill-changelog.js && git add changelog/\nThen retry the commit. To bypass (rare; emergencies only): git commit --no-verify" + } +} +EOF +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index 163596d4..2450b318 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -9,6 +9,15 @@ "command": ".claude/hooks/block-prose-punctuation.sh" } ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": ".claude/hooks/changelog-nudge.sh" + } + ] } ], "PostToolUse": [ diff --git a/.hooks/pre-commit b/.hooks/pre-commit index d79cd069..8603d6fb 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -32,4 +32,41 @@ if ! npm test --silent; then exit 1 fi +# Gate 3: when a packages//package.json version bumped, require +# a matching changelog//.md to land in the same commit. +# The "diff --cached" check pulls the new version line from the +# staged package.json; the directory existence test catches the +# matching changelog entry. Re-runs `scripts/backfill-changelog.js` +# so an author who forgets only loses time, not the commit. +STAGED_PKG_BUMPS=$(git diff --cached --unified=0 -- 'packages/*/package.json' 2>/dev/null \ + | awk ' + /^diff --git/ { match($0, /packages\/[^\/]+\/package.json/); pkg = substr($0, RSTART+9, RLENGTH-22); next } + pkg && /^\+\s*"version":\s*"[^"]+"/ { + match($0, /"[0-9]+\.[0-9]+\.[0-9]+[^"]*"/); v = substr($0, RSTART+1, RLENGTH-2); + print pkg "@" v + }') + +if [ -n "$STAGED_PKG_BUMPS" ]; then + MISSING="" + for bump in $STAGED_PKG_BUMPS; do + pkg="${bump%@*}"; ver="${bump#*@}" + if [ ! -f "changelog/$pkg/$ver.md" ]; then + MISSING="$MISSING\n - changelog/$pkg/$ver.md (for @webjskit/$pkg $ver)" + fi + done + if [ -n "$MISSING" ]; then + echo "" + echo "ERROR: detected version bumps in staged changes but matching changelog files are missing:" + echo -e "$MISSING" + echo "" + echo "Run:" + echo " node scripts/backfill-changelog.js" + echo " git add changelog/" + echo "" + echo "Then re-commit. To bypass (emergencies only): git commit --no-verify" + echo "" + exit 1 + fi +fi + exit 0 diff --git a/AGENTS.md b/AGENTS.md index 5986a27b..bd36e78b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,6 +97,19 @@ When editing the framework monorepo (this repo, not a scaffolded app): **`packag See `agent-docs/framework-dev.md` for monorepo commands, workspace layout, reference codebases, and per-feature update checklists. +### Changelog: per-package, per-version, auto-generated + +webjs ships per-package per-version changelogs under `changelog//.md`. The model: **a version bump is the trigger**. When any commit on `main` changes the `version` field in `packages//package.json`, the scripts/backfill-changelog.js generator emits a new `changelog//.md` summarising every conventional-commit (`feat:` / `fix:` / `breaking:` / `perf:`) that landed in that package since the prior bump. The website renders the union of all packages' files at `/changelog`. + +**Mandatory rule for AI agents working in this monorepo:** + +1. When you bump a `packages//package.json` `version`, you MUST run `node scripts/backfill-changelog.js` in the same commit (or the immediately following one), so the new `changelog//.md` lands together with the bump. +2. Review the generated file. The script's body excerpts are the first lines of each commit message; if any are unclear, **edit the file in place** to add migration notes (especially for `breaking` entries) before committing. +3. Subsequent re-runs are safe: the script never overwrites an existing entry, so your hand-edits survive. +4. Never edit `changelog//.md` for a version that has already been published. Edit `changelog//.md` instead. + +The `.hooks/pre-commit` git hook (and the Claude Code `PreToolUse` hook `.claude/hooks/changelog-nudge.sh`) catches version bumps that lack a matching changelog file and refuses the commit until both land together. + --- ## What webjs is diff --git a/changelog/README.md b/changelog/README.md new file mode 100644 index 00000000..4bb5e570 --- /dev/null +++ b/changelog/README.md @@ -0,0 +1,128 @@ +# Changelog + +webjs ships **per-package, per-version** changelog files. + +``` +changelog/ + README.md this file + core/ + 0.6.0.md + 0.5.0.md + … + server/ + 0.7.1.md + … + cli/ + 0.7.0.md + … + ts-plugin/ + 0.4.0.md + … + ui/ + 0.2.0.md + … +``` + +The website (`website/app/changelog/page.ts`) reads every +`/.md` at SSR time, sorts by date descending, and +renders the unified release feed. + +## How a release entry gets created + +The model is "**a version bump produces a changelog file**". When a +package's `package.json` `version` field changes in a commit, the +entry script: + +1. Identifies the prior version of the same package on `main`. +2. Walks the commits between the prior version's bump and the new + bump that touched files under `packages//`. +3. Filters to conventional-commit prefixes that matter to users: + `feat:`, `fix:`, `breaking:`, `perf:`. +4. Groups them under `Breaking` / `Features` / `Performance` / + `Fixes` and writes a `.md` file with frontmatter + (`package`, `version`, `date`, `commit_count`) plus a bulleted + list of changes (PR link + commit SHA + body excerpt). + +`chore:`, `refactor:`, `test:`, `docs:`, `style:`, `build:`, `ci:` +commits never appear in the changelog: those changes don't change the +package's user-facing contract. + +## Entry format + +```markdown +--- +package: "@webjskit/core" +version: 0.6.0 +date: 2026-05-21 +commit_count: 4 +--- + +# @webjskit/core 0.6.0 + +## Breaking + +- **Title of the change** ([#NN](https://github.com/vivek7405/webjs/pull/NN)) [`abcd123`](https://github.com/vivek7405/webjs/commit/abcd123) + First four lines of the commit body, indented two spaces. + +## Features +… + +## Fixes +… +``` + +Frontmatter fields: + +| Field | Required | Meaning | +|---|---|---| +| `package` | yes | The fully-qualified npm name (`@webjskit/`). | +| `version` | yes | Semver string of the new release. | +| `date` | yes | Date of the version-bump commit, `YYYY-MM-DD`. | +| `commit_count` | yes | How many qualifying commits this version shipped. | + +## Backfill + ongoing automation + +A single script runs in both modes: + +```sh +node scripts/backfill-changelog.js +``` + +It walks every package's `package.json` history, finds version +bumps, and writes a `/.md` for any version that does +not yet have one. **Files that already exist are left alone**, so +hand-curated entries survive subsequent runs (CI re-runs are safe). + +Going forward, the same script runs: + +- on every CI build of `main`, so a forgotten version bump + still produces an entry without manual intervention; +- locally when an agent edits a `packages//package.json` to + bump the version (the post-commit hook + `.hooks/post-version-bump` runs the script). + +The AGENTS.md rule that AI agents follow on every code-commit: + +> When you bump a `packages//package.json` `version`, run +> `node scripts/backfill-changelog.js` (or just commit; CI will +> regenerate). The generated `changelog//.md` file +> should be reviewed and **edited in-place** for clarity, especially +> for `breaking` entries that need migration notes. Then commit the +> changelog file alongside the version bump. + +## What if the same change ships across packages + +A commit that touches more than one package (e.g. a refactor that +moves code from `core` to `server`) appears in **every** affected +package's release file when each of those packages bumps its +version. That is the right shape: the same change is a user-facing +event for every package that carries it. + +## Migration to the new format + +The pre-0.7.1 history was backfilled in one pass. The files in +`changelog//` are auto-generated but can (and should) be +hand-edited to add migration notes, examples, or links to docs. +Subsequent re-runs of `scripts/backfill-changelog.js` will not +overwrite hand edits because the script skips files that already +exist. diff --git a/changelog/cli/0.1.1.md b/changelog/cli/0.1.1.md new file mode 100644 index 00000000..bdc92d3c --- /dev/null +++ b/changelog/cli/0.1.1.md @@ -0,0 +1,141 @@ +--- +package: "@webjskit/cli" +version: 0.1.1 +date: 2026-04-22 +commit_count: 27 +--- + +# @webjskit/cli 0.1.1 +## Features + +- **webjs test, webjs check, webjs create with AI guardrails** [`6e85e5d`](https://github.com/vivek7405/webjs/commit/6e85e5d) + - webjs test: discovers and runs unit + E2E tests + - webjs check: validates app against 6 convention rules + - webjs create: scaffolds opinionated project with CONVENTIONS.md, + cross-agent configs (.cursorrules, .windsurfrules, copilot-instructions), +- **default theme to system across website, docs, blog, and scaffolder** [`7257b49`](https://github.com/vivek7405/webjs/commit/7257b49) + Theme toggle defaults to 'system' instead of 'light'. Bootstrap scripts + no longer force data-theme — they only set it when the user has an + explicit localStorage preference. CSS prefers-color-scheme handles the + default. Updated all three apps + the webjs create scaffolder template. +- **migrate client tests to WTR + Playwright, real browser testing** [`1ce3888`](https://github.com/vivek7405/webjs/commit/1ce3888) + Added @web/test-runner + @web/test-runner-playwright + playwright. + Client-side tests (renderer, directives, Shadow DOM) now run in real + Chromium — no more linkedom/jsdom fake DOM. Server tests stay on + node:test (they need Node APIs). +- **add Playwright MCP server for browser debugging** [`4c5746e`](https://github.com/vivek7405/webjs/commit/4c5746e) + Added Microsoft's @playwright/mcp as both user-level (global) and + project-level MCP server. End users get it via webjs create (.claude.json). + + Playwright MCP gives AI agents direct browser control: navigate, click, +- **git pre-commit hook blocks commits on main/master** [`ca31525`](https://github.com/vivek7405/webjs/commit/ca31525) + Added .git/hooks/pre-commit locally and .hooks/pre-commit in the + scaffolder template. webjs create now runs git init + configures + core.hooksPath to .hooks/ so the hook is tracked in the repo and + works for everyone who clones it. +- **add .env.example for scaffolder and blog example** [`9994261`](https://github.com/vivek7405/webjs/commit/9994261) + Shows all convention-over-configuration env vars: PORT, SESSION_SECRET, + REDIS_URL (auto-scales everything), S3_BUCKET (cloud storage), + DATABASE_URL. Includes generation command for SESSION_SECRET. +- **NextJs-style cache + NextAuth-style auth + WebSocket broadcast** [`19418b3`](https://github.com/vivek7405/webjs/commit/19418b3) + Removed: storage (s3/disk), background jobs — not core framework. + + Added: + - cache(fn, { revalidate, tags }) + revalidateTag() — NextJs-style +- **scaffold templates (full-stack + API) and code generators** [`b8f569b`](https://github.com/vivek7405/webjs/commit/b8f569b) + webjs create --template api Backend-only: route wrappers over + typed server actions, no pages/SSR + + webjs generate page|module|action|query|component|route +- **SaaS template — webjs create --template saas** [`1ab8ea0`](https://github.com/vivek7405/webjs/commit/1ab8ea0) + Scaffolds a complete SaaS starter with auth, dashboard, Prisma: + - app/login, app/signup — auth pages + - app/dashboard with middleware auth guard + settings page + - app/api/auth/[...path]/route.ts — auth API handlers +- **auto-scope ALL CSS selectors for light DOM components** [`bdb3280`](https://github.com/vivek7405/webjs/commit/bdb3280) + scopeCSS() now prefixes every CSS rule with the component tag name: + input { color: red } → comments-thread input { color: red } + :host { display: block } → comments-thread { display: block } +- **typed component props via defineComponent + .d.ts overlay** [`9f0b09a`](https://github.com/vivek7405/webjs/commit/9f0b09a) + The existing static-properties pattern carried no compile-time type + information, so authors had to duplicate every field as `declare x: T` + for editor intellisense. This adds a TypeScript overlay plus a tiny + runtime helper so the descriptor map is the single source of truth for +- **Class.register() — drop the import.meta.url argument** [`283c2b5`](https://github.com/vivek7405/webjs/commit/283c2b5) + Authors no longer write `Counter.register(import.meta.url)`; the call + is just `Counter.register()`. Module URLs (used by SSR to emit + `` hints) are derived server-side at boot by + scanning the app tree for `class X extends WebComponent { static tag = +- **adopt standard customElements.define — drop static tag + .register()** [`bb61186`](https://github.com/vivek7405/webjs/commit/bb61186) + The web-standard registration convention (same shape Lit uses for its + non-decorator form): + + class Counter extends WebComponent { ... } +- **Counter.register('my-counter') as the idiomatic registration** [`a0f1f68`](https://github.com/vivek7405/webjs/commit/a0f1f68) + Adds a `static register(tag)` method on WebComponent. Delegates to the + internal registry (which in turn calls customElements.define / the + server shim), so every registration still lands in the native + customElements map and webjs's mirror map. Authors write: +- **rename core package webjs → @webjs/core for npm publish** [`49e2d6b`](https://github.com/vivek7405/webjs/commit/49e2d6b) + The unscoped `webjs` name is already taken on npm (webjs@0.6.1, unrelated + project). Moving to the `@webjs` scope lets us publish `@webjs/core`, + `@webjs/server`, and `@webjs/cli` together. +- **rescope @webjs → @webjskit (org name 'webjs' unavailable on npm)** [`316e2be`](https://github.com/vivek7405/webjs/commit/316e2be) + The @webjs organization is not available on npm (likely reserved because + the unscoped `webjs` package is held by an unrelated project). Switching + to the @webjskit scope, which we own. +- **favicon for all three apps + Prisma/SQLite in every scaffold** [`e6ba242`](https://github.com/vivek7405/webjs/commit/e6ba242) + **Favicon** + - scripts/generate-favicon.mjs renders the header-logo artwork + (accent-orange linear gradient, rounded square, 1px inner highlight) + at 512×512 via puppeteer and writes favicon.svg + favicon.png into + +## Fixes + +- **dev mode uses node --watch for reliable file change detection** [`792cde0`](https://github.com/vivek7405/webjs/commit/792cde0) + Root cause: Node's ESM module cache has no public invalidation API. + When loadModule() cache-busts a page with ?t=timestamp, only the + top-level module is re-imported. Transitive imports (server actions, + queries, components, utils) resolve to the SAME cached URL and return +- **enforce commit-often and bypass-mode in all end-user templates** [`016d39f`](https://github.com/vivek7405/webjs/commit/016d39f) + - guard-main-merge.sh template now respects bypass mode + - CLAUDE.md template: commit-often is step 1 of the workflow + - CONVENTIONS.md template: commits listed first in required checklist + - .cursorrules, .windsurfrules, copilot-instructions: COMMIT OFTEN +- **enforce commit-AND-push in all agent instructions** [`286ce7f`](https://github.com/vivek7405/webjs/commit/286ce7f) + Commit without push is half the job. Updated CLAUDE.md, all templates + (CLAUDE.md, CONVENTIONS.md, .cursorrules, .windsurfrules, copilot- + instructions.md) to say 'commit AND push' everywhere. +- **branch-context hook must ALWAYS fire, even in bypass mode** [`c7a35b5`](https://github.com/vivek7405/webjs/commit/c7a35b5) + The bypass-mode early-exit was defeating the entire purpose of the + branch guard. Editing on main is a safety issue — the hook should + always prompt, regardless of mode. Merge/push guard can respect + bypass (those are approval prompts), but branch check is a safety net. +- **merging ALWAYS requires user permission, even in bypass mode** [`a74450e`](https://github.com/vivek7405/webjs/commit/a74450e) + Removed all 'auto-merge' language from AGENTS.md, CLAUDE.md, and every + agent config template (.cursorrules, .windsurfrules, copilot-instructions, + CONVENTIONS.md). Merge guard hook no longer respects bypass mode — it + always prompts. Merging is irreversible and must never be autonomous. +- **all hooks respect bypass mode — no prompts in autonomous mode** [`24dd724`](https://github.com/vivek7405/webjs/commit/24dd724) + Both guard-main-merge.sh and guard-branch-context.sh (local + templates) + now respect skipDangerousModePermissionPrompt. In bypass mode: no prompts + for edits, merges, or pushes. The agent follows best practices via + AGENTS.md instructions, not hook interruptions. +- **clean permission model — feature branches are free, only merge asks** [`9aa740e`](https://github.com/vivek7405/webjs/commit/9aa740e) + Hooks: on feature branches, commit/push are fully free (no prompts). + Only merging into main prompts for approval. Bypass mode allows + everything including merges. +- **update all end-user templates for WTR + Playwright test stack** [`06b77c7`](https://github.com/vivek7405/webjs/commit/06b77c7) + Updated CONVENTIONS.md (browser test section, test matrix table), + .cursorrules, .windsurfrules, copilot-instructions.md to reference + WTR + Playwright instead of Puppeteer/E2E. Removed stale WEBJS_E2E + references. +- **revert blog components with bare selectors to shadow DOM** [`a36400d`](https://github.com/vivek7405/webjs/commit/a36400d) + Components with bare element selectors (input, button, ul, h2, a) + MUST use shadow DOM — bare selectors would leak to the whole page + in light DOM. Reverted: comments-thread, auth-forms, chat-box, + new-post, error-card. +- **remove auto-scope CSS hack, clean shadow/light DOM convention** [`f91ceb0`](https://github.com/vivek7405/webjs/commit/f91ceb0) + Shadow DOM = scoped styles (static styles = css, adoptedStyleSheets). + Light DOM = global styles (Tailwind, global CSS, no static styles). + Pick one. Don't fake shadow DOM scoping with regex. diff --git a/changelog/cli/0.1.2.md b/changelog/cli/0.1.2.md new file mode 100644 index 00000000..0ebfadf0 --- /dev/null +++ b/changelog/cli/0.1.2.md @@ -0,0 +1,15 @@ +--- +package: "@webjskit/cli" +version: 0.1.2 +date: 2026-04-27 +commit_count: 1 +--- + +# @webjskit/cli 0.1.2 +## Features + +- **use icon-only theme toggle in scaffold** [`195614c`](https://github.com/vivek7405/webjs/commit/195614c) + Aligns the scaffolded `` with the website, docs, and blog + versions — a circular icon button with sun/moon/system SVGs. The + previous scaffold rendered the literal text 'Auto'/'Light'/'Dark' in a + pill, which was inconsistent with the rest of the project. diff --git a/changelog/cli/0.1.3.md b/changelog/cli/0.1.3.md new file mode 100644 index 00000000..3cb045d8 --- /dev/null +++ b/changelog/cli/0.1.3.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.1.3 +date: 2026-04-27 +commit_count: 0 +--- + +# @webjskit/cli 0.1.3 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.1.4.md b/changelog/cli/0.1.4.md new file mode 100644 index 00000000..f6f63ca7 --- /dev/null +++ b/changelog/cli/0.1.4.md @@ -0,0 +1,15 @@ +--- +package: "@webjskit/cli" +version: 0.1.4 +date: 2026-04-27 +commit_count: 1 +--- + +# @webjskit/cli 0.1.4 +## Fixes + +- **resolve esbuild from app dir, not server dir** [`8e8207d`](https://github.com/vivek7405/webjs/commit/8e8207d) + When `@webjskit/cli` is installed globally (the typical case for + end-users), `import('esbuild')` from `packages/server/src/dev.js` + walks up from the global install tree — never reaching the user app's + `node_modules`. The dev server then served `.ts` files as diff --git a/changelog/cli/0.2.0.md b/changelog/cli/0.2.0.md new file mode 100644 index 00000000..cd013d3a --- /dev/null +++ b/changelog/cli/0.2.0.md @@ -0,0 +1,15 @@ +--- +package: "@webjskit/cli" +version: 0.2.0 +date: 2026-04-28 +commit_count: 1 +--- + +# @webjskit/cli 0.2.0 +## Features + +- **use esbuild for TS on both SSR + hydration; default to no build** [`4dc8b91`](https://github.com/vivek7405/webjs/commit/4dc8b91) + The dev server now registers an esbuild ESM loader hook + (`module.register()`) at startup. Every server-side `.ts` import flows + through esbuild's transform — same transformer the dev server already + used for browser-bound `.ts` requests. SSR and hydration produce diff --git a/changelog/cli/0.2.1.md b/changelog/cli/0.2.1.md new file mode 100644 index 00000000..c74e7c3a --- /dev/null +++ b/changelog/cli/0.2.1.md @@ -0,0 +1,15 @@ +--- +package: "@webjskit/cli" +version: 0.2.1 +date: 2026-04-28 +commit_count: 1 +--- + +# @webjskit/cli 0.2.1 +## Fixes + +- **share component registry across module instances** [`5bda8c7`](https://github.com/vivek7405/webjs/commit/5bda8c7) + When the cli is installed globally (or hoisted at a different level + than the user's app), Node resolves `@webjskit/core` from each + importer's location and may load TWO instances of the package — one + for `@webjskit/server` (server-side) and one for the user's app diff --git a/changelog/cli/0.3.0.md b/changelog/cli/0.3.0.md new file mode 100644 index 00000000..4d0281ea --- /dev/null +++ b/changelog/cli/0.3.0.md @@ -0,0 +1,15 @@ +--- +package: "@webjskit/cli" +version: 0.3.0 +date: 2026-04-28 +commit_count: 1 +--- + +# @webjskit/cli 0.3.0 +## Features + +- **drop superjson, ship built-in ESM rich-type serializer** [`4c2b704`](https://github.com/vivek7405/webjs/commit/4c2b704) + Replaces `superjson` with a pure-ESM, dependency-free serializer in + `@webjskit/core` (`packages/core/src/serialize.js`). Tagged-inline wire + format, async sync API. Single async `stringify`/`serialize` so AI + agents can't pick the wrong variant; `parse`/`deserialize` stay sync. diff --git a/changelog/cli/0.3.1.md b/changelog/cli/0.3.1.md new file mode 100644 index 00000000..858434cb --- /dev/null +++ b/changelog/cli/0.3.1.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.3.1 +date: 2026-05-04 +commit_count: 0 +--- + +# @webjskit/cli 0.3.1 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.4.0.md b/changelog/cli/0.4.0.md new file mode 100644 index 00000000..17da9e49 --- /dev/null +++ b/changelog/cli/0.4.0.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.4.0 +date: 2026-05-11 +commit_count: 0 +--- + +# @webjskit/cli 0.4.0 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.4.1.md b/changelog/cli/0.4.1.md new file mode 100644 index 00000000..e30e3d3e --- /dev/null +++ b/changelog/cli/0.4.1.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.4.1 +date: 2026-05-12 +commit_count: 0 +--- + +# @webjskit/cli 0.4.1 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.4.2.md b/changelog/cli/0.4.2.md new file mode 100644 index 00000000..d2a142b3 --- /dev/null +++ b/changelog/cli/0.4.2.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.4.2 +date: 2026-05-12 +commit_count: 0 +--- + +# @webjskit/cli 0.4.2 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.4.3.md b/changelog/cli/0.4.3.md new file mode 100644 index 00000000..c94abbc7 --- /dev/null +++ b/changelog/cli/0.4.3.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.4.3 +date: 2026-05-12 +commit_count: 0 +--- + +# @webjskit/cli 0.4.3 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.4.4.md b/changelog/cli/0.4.4.md new file mode 100644 index 00000000..ada293e5 --- /dev/null +++ b/changelog/cli/0.4.4.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.4.4 +date: 2026-05-12 +commit_count: 0 +--- + +# @webjskit/cli 0.4.4 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.5.0.md b/changelog/cli/0.5.0.md new file mode 100644 index 00000000..8443120a --- /dev/null +++ b/changelog/cli/0.5.0.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.5.0 +date: 2026-05-17 +commit_count: 0 +--- + +# @webjskit/cli 0.5.0 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.5.1.md b/changelog/cli/0.5.1.md new file mode 100644 index 00000000..bd688a05 --- /dev/null +++ b/changelog/cli/0.5.1.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.5.1 +date: 2026-05-17 +commit_count: 0 +--- + +# @webjskit/cli 0.5.1 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.5.2.md b/changelog/cli/0.5.2.md new file mode 100644 index 00000000..28f9b3be --- /dev/null +++ b/changelog/cli/0.5.2.md @@ -0,0 +1,9 @@ +--- +package: "@webjskit/cli" +version: 0.5.2 +date: 2026-05-17 +commit_count: 0 +--- + +# @webjskit/cli 0.5.2 +_No user-facing changes shipped with this version (release-only bump)._ diff --git a/changelog/cli/0.6.0.md b/changelog/cli/0.6.0.md new file mode 100644 index 00000000..9df7dca3 --- /dev/null +++ b/changelog/cli/0.6.0.md @@ -0,0 +1,15 @@ +--- +package: "@webjskit/cli" +version: 0.6.0 +date: 2026-05-19 +commit_count: 1 +--- + +# @webjskit/cli 0.6.0 +## Features + +- **erasable-typescript-only rule + ship erasableSyntaxOnly in tsconfigs** [`01d1b18`](https://github.com/vivek7405/webjs/commit/01d1b18) + Adds compilerOptions.erasableSyntaxOnly to: the scaffold tsconfig + generator (packages/cli/lib/create.js), the blog example, the docs + site, the marketing site. TypeScript 5.8+ rejects enum, namespace + with values, constructor parameter properties, legacy decorators diff --git a/changelog/cli/0.7.0.md b/changelog/cli/0.7.0.md new file mode 100644 index 00000000..23513d67 --- /dev/null +++ b/changelog/cli/0.7.0.md @@ -0,0 +1,55 @@ +--- +package: "@webjskit/cli" +version: 0.7.0 +date: 2026-05-21 +commit_count: 9 +--- + +# @webjskit/cli 0.7.0 +## Features + +- **webjs check --rules shows enabled/disabled state per project** [`a5e7067`](https://github.com/vivek7405/webjs/commit/a5e7067) + Reads the project's overrides via loadConventionOverrides(), then + annotates each rule line as [enabled] or [disabled by override]. + Header text states the default-on invariant explicitly so AI + agents reading the output cannot misinterpret it. +- **nudge-uncommitted hook ships in every scaffolded app** [`309de79`](https://github.com/vivek7405/webjs/commit/309de79) + Mirrors the framework-repo hook into the scaffold templates so + end-user webjs apps inherit the same commit-frequency + enforcement out of the box. Three pieces: +- **pre-commit runs webjs test + webjs check** [`f789ef5`](https://github.com/vivek7405/webjs/commit/f789ef5) + Tool-agnostic git-level enforcement. Fires on every commit + regardless of agent (Claude, Cursor, Windsurf, Copilot, human). + Blocks commits that fail tests or convention checks. +- **Gemini CLI nudge-uncommitted hook** [`ed8e692`](https://github.com/vivek7405/webjs/commit/ed8e692) + Adds the same commit-frequency enforcement to scaffolded apps for + Gemini CLI users. The hook output shape (hookSpecificOutput. + additionalContext) is identical to Claude Code's, so the body is + nearly a copy of the Claude version. +- **Cursor nudge-uncommitted hook** [`1a69f56`](https://github.com/vivek7405/webjs/commit/1a69f56) + Cursor 1.7+ ships a hooks system at .cursor/hooks.json. The + afterFileEdit event fires after Cursor's edit/write tools and + accepts a top-level additional_context field (snake_case) to + inject a soft reminder into the agent's context. +- **block-prose-punctuation hook ships in every scaffolded app** [`d6689f4`](https://github.com/vivek7405/webjs/commit/d6689f4) + Mirrors the framework-repo prose hook into scaffolded apps so end + users get the same enforcement of AGENTS.md invariant 11 (no + em-dashes, no hyphen-as-pause, no semicolon-as-pause, no + xyz():-then-prose). The rule is already documented in +- **OpenCode commit-nudge plugin** [`9396fe6`](https://github.com/vivek7405/webjs/commit/9396fe6) + OpenCode does not have shell-script hooks; it uses TS plugin + modules loaded as-is by Bun. This plugin is the counterpart of + the .claude/, .gemini/, .cursor/ hooks already in the scaffold. +- **Tier-2 components -> WebComponent + render() + (rebased)** ([#28](https://github.com/vivek7405/webjs/pull/28)) [`101d1ee`](https://github.com/vivek7405/webjs/commit/101d1ee) + * refactor(ui): toggle.ts -> WebComponent + render() + + + First Tier-2 component converted from extends Base to extends + WebComponent. This is the pattern-locking conversion; remaining + +## Fixes + +- **drop guard-main-merge hook so end users can merge to main locally** [`3ab47b2`](https://github.com/vivek7405/webjs/commit/3ab47b2) + This hook prompted on every 'git merge' and every 'git push ... main' + through Claude's Bash tool. That matches the framework dev's preference + for a strict GitHub-PR-only workflow, but it is the wrong default for + end users. Scaffolded webjs apps may use GitLab, Bitbucket, plain git, diff --git a/changelog/core/0.2.0.md b/changelog/core/0.2.0.md new file mode 100644 index 00000000..a2f95c8a --- /dev/null +++ b/changelog/core/0.2.0.md @@ -0,0 +1,216 @@ +--- +package: "@webjskit/core" +version: 0.2.0 +date: 2026-04-28 +commit_count: 43 +--- + +# @webjskit/core 0.2.0 +## Features + +- **add essential directives (unsafeHTML, live) with renderer integration** [`3d8a253`](https://github.com/vivek7405/webjs/commit/3d8a253) + Only three directives in webjs — each solves a problem with no native + alternative. unsafeHTML for trusted raw HTML, live for input value + dirty-checking, repeat (already existed) for keyed lists. Both client + and server renderers now process directive markers correctly instead +- **full lifecycle, controllers, error boundaries, selective hydration** [`231bc4c`](https://github.com/vivek7405/webjs/commit/231bc4c) + Add shouldUpdate, willUpdate, firstUpdated, updated, changedProperties + tracking. Add ReactiveController pattern (addController/removeController). + Add client-side error boundaries (renderError lifecycle hook). Add + static hydrate='visible' for IntersectionObserver-based deferred +- **add Context Protocol and Task controller** [`d68b207`](https://github.com/vivek7405/webjs/commit/d68b207) + Context: createContext, ContextProvider, ContextConsumer for cross- + component data sharing without prop drilling. Uses W3C Context Protocol + (DOM events, crosses shadow DOM). +- **auto-vendor npm deps, module graph, lazy loading** [`266d041`](https://github.com/vivek7405/webjs/commit/266d041) + Auto-vendor: scan client imports for bare specifiers, bundle via esbuild, + serve at /__webjs/vendor/.js, auto-populate import map. + + Module graph: build dependency graph at startup, emit transitive +- **webjs test, webjs check, webjs create with AI guardrails** [`6e85e5d`](https://github.com/vivek7405/webjs/commit/6e85e5d) + - webjs test: discovers and runs unit + E2E tests + - webjs check: validates app against 6 convention rules + - webjs create: scaffolds opinionated project with CONVENTIONS.md, + cross-agent configs (.cursorrules, .windsurfrules, copilot-instructions), +- **light DOM SSR + hydration — zero flash, mixed shadow/light DOM** [`cf155d7`](https://github.com/vivek7405/webjs/commit/cf155d7) + SSR fix: light DOM components (static shadow = false) now have their + render() called during SSR. Content is injected directly as children + with a marker (no DSD template wrapper). +- **light DOM tests, blog migration, auto-scoped styles** [`75f6d17`](https://github.com/vivek7405/webjs/commit/75f6d17) + Tests: + - test/light-dom-ssr.test.js: 5 unit tests for SSR (light DOM content, + hydration marker, shadow DSD, mixed page, async render) + - test/browser/light-dom-hydration.test.js: 3 browser tests (hydration +- **auto-scope ALL CSS selectors for light DOM components** [`bdb3280`](https://github.com/vivek7405/webjs/commit/bdb3280) + scopeCSS() now prefixes every CSS rule with the component tag name: + input { color: red } → comments-thread input { color: red } + :host { display: block } → comments-thread { display: block } +- **support mixed attribute interpolation in client renderer** [`893c99f`](https://github.com/vivek7405/webjs/commit/893c99f) + Quoted attributes with embedded holes (class="static ${dynamic}") + previously dropped the dynamic portion on client re-render. The + renderer now tracks these as 'attr-mixed' parts that reconstruct + the full attribute value from static pieces + dynamic values on +- **default components to light DOM, shadow DOM is opt-in** [`01b55c2`](https://github.com/vivek7405/webjs/commit/01b55c2) + Change static shadow default from true to false. Components now + render to light DOM by default. Set static shadow = true to opt + into shadow DOM with adoptedStyleSheets. +- **auto-wrap layout output with data-layout for client router** [`306c512`](https://github.com/vivek7405/webjs/commit/306c512) + The SSR pipeline now automatically wraps the outermost layout's + rendered output in
. Users no longer + need to add data-layout manually — the framework handles it. +- **SSR pages default to no-store, caching is opt-in** [`5a4468c`](https://github.com/vivek7405/webjs/commit/5a4468c) + SSR responses now send Cache-Control: no-store by default. Pages + are dynamic — the developer opts in to caching explicitly via + metadata.cacheControl (e.g. 'public, max-age=60'). This ensures + client-side navigation always gets fresh content without needing +- **typed component props via defineComponent + .d.ts overlay** [`9f0b09a`](https://github.com/vivek7405/webjs/commit/9f0b09a) + The existing static-properties pattern carried no compile-time type + information, so authors had to duplicate every field as `declare x: T` + for editor intellisense. This adds a TypeScript overlay plus a tiny + runtime helper so the descriptor map is the single source of truth for +- **Class.register() — drop the import.meta.url argument** [`283c2b5`](https://github.com/vivek7405/webjs/commit/283c2b5) + Authors no longer write `Counter.register(import.meta.url)`; the call + is just `Counter.register()`. Module URLs (used by SSR to emit + `` hints) are derived server-side at boot by + scanning the app tree for `class X extends WebComponent { static tag = +- **adopt standard customElements.define — drop static tag + .register()** [`bb61186`](https://github.com/vivek7405/webjs/commit/bb61186) + The web-standard registration convention (same shape Lit uses for its + non-decorator form): + + class Counter extends WebComponent { ... } +- **Counter.register('my-counter') as the idiomatic registration** [`a0f1f68`](https://github.com/vivek7405/webjs/commit/a0f1f68) + Adds a `static register(tag)` method on WebComponent. Delegates to the + internal registry (which in turn calls customElements.define / the + server shim), so every registration still lands in the native + customElements map and webjs's mirror map. Authors write: +- **rename core package webjs → @webjs/core for npm publish** [`49e2d6b`](https://github.com/vivek7405/webjs/commit/49e2d6b) + The unscoped `webjs` name is already taken on npm (webjs@0.6.1, unrelated + project). Moving to the `@webjs` scope lets us publish `@webjs/core`, + `@webjs/server`, and `@webjs/cli` together. +- **rescope @webjs → @webjskit (org name 'webjs' unavailable on npm)** [`316e2be`](https://github.com/vivek7405/webjs/commit/316e2be) + The @webjs organization is not available on npm (likely reserved because + the unscoped `webjs` package is held by an unrelated project). Switching + to the @webjskit scope, which we own. + +## Fixes + +- **DSD injection regex now handles slashes inside attribute values** [`c792b6e`](https://github.com/vivek7405/webjs/commit/c792b6e) + Bug: rendered as an EMPTY custom element + with no shadow root — the sign-in and sign-up pages appeared blank. + Root cause: the DSD-injection regex in render-server.js used + `[^>/]*` to bound the attribute section, which stopped matching the +- **quoted-attr interpolation no longer crashes client re-renders** [`df2a952`](https://github.com/vivek7405/webjs/commit/df2a952) + Bug: clicking the 'Sign up' tab on /login did nothing. The + component used a hole inside a quoted attribute — + \`autocomplete=\"\${mode === 'login' ? 'current-password' : 'new-password'}\"\` — + and the client compiler was recording a \`{ kind: 'attr', path: [] }\` part +- **boolean attribute SSR coercion — empty value means true** [`016d5b5`](https://github.com/vivek7405/webjs/commit/016d5b5) + Bug: rendered the comment form as + "Sign in to comment" instead of the input+Post form, even when the user + was logged in. +- **filter DSD template from client-navigation swap + update rubric** [`a026010`](https://github.com/vivek7405/webjs/commit/a026010) + Client router: when swapping the layout shell's light-DOM children on + navigation, the new body from DOMParser contains a +