Changelog

Swift Web framework version release history — all changes across CLI tooling, framework module, and documentation site.

This file summarizes major Swift Web changes month by month across the 5 repos, split per month by framework / starter / standards / cli / docs. Only records important new features / major refactors / important bug fixes / Breaking Changes / cross-repo events. See git log for complete history.


2026-09

framework v0.140.0 → v0.155.0 · starter v0.35.13 → v0.37.0 · standards v0.13.27 → v0.13.89 · cli v0.21.1 → v0.23.1

This month focused on consistent caching and deployment, reliable API generation, and shared workflows across coding tools, alongside duration conventions, media SEO, authentication controls, and playback quality tracking.

framework

  • ⚠️ Duration and upload APIs — Numeric durations in components, composables, and module options now use milliseconds; recurring periods use Nm / Nh / Nd or always (ISR TTL remains in seconds). Upload props change from isSmall to thumbnail, retryNum to retries, and threadNum to concurrency; the back-to-top layer token becomes --sw-back-top-z
  • ⚠️ Explicit ISR TTL bounds — isr: true is rejected; declared TTLs must be integer seconds from 60 to 3600. Outgoing s-maxage is capped at 60 seconds to limit stale content across cache layers (#394 / #414)
  • 🚀 Media structured data — useSeo adds video / media / itemList for VideoObject, Movie / TVSeries / TVEpisode, and ItemList, with video sitemap support; consumers supply real content data (#381)
  • ⚠️ Unified request configuration — request.transport supports base64 / binary. nitroApiFetch reads runtimeConfig and global request.headers directly, retiring the flat swTimeout / apiTransport keys and application bridge plugin; plaintext business failures retain their actual errorCode (#383 / #391 / #392 / #409)
  • ⚠️ API generation boundaries — Source configuration moves to swiftConfig.apiDocs in nuxt.config.ts, with credentials in API_DOCS_TOKEN. OpenAPI prefers tags for grouping; generated files carry @generated, while handwritten APIs live in automatically registered app/api/manual/ modules. YAPI supports parameter template paths and preserves existing output on fetch, parse, or naming failures (#402 / #403 / #404 / #406)
  • 🚀 Auth and system info controls — auth.deviceLogin independently disables device login, while auth.enabled gates automatic login paths. Relogin separates identity cookies from user profiles, and client system info is reused for clientRefreshTtl (300 seconds by default) (#410 / #412 / #413)
  • 🐛 Playback recovery — Fixes iOS fullscreen and switching failures. The hls.js paths in SwVideoPlayer and SwVideoFeed perform bounded reloads of the same URL and resume playback after expired segment signatures or network errors (#398 / #405 / #408)
  • 🐛 Image and encrypted asset fallback — SwImage falls back to a placeholder for missing URLs instead of causing an SSR 500. Encrypted assets ignore stale request results and continue loading when cache reads or writes fail (#380 / #397 / #421)
  • 🚀 Session and playback tracking — The self-hosted channel gains referrer, UTM, landing page, and device context, with a new session after 30 minutes of inactivity. Adds first-frame, startup time, cumulative buffering, and play_id, removes duplicate initial PVs, and records fatal hls errors (#415 / #417 / #423 / #425)
  • 🚀 Redirect and preview controls — Adds useExternalRedirect for safe external redirect logic, SwVideoPreview settled for terminal outcomes, and useHoverPreview.closeOnScroll for inline previews (#400)

starter

  • 🔄 Single-process deployment — Test and production use one PM2 fork process, with process configuration application and nginx cache probes to avoid alternating ISR snapshots; page types declare explicit TTLs (standards#86 / framework#414)
  • 🔄 API integration template — Handwritten API examples move to app/api/manual/, and the runtime environment bridge is removed. API and tracking proxies carry X-Site-Id; proxy routes follow the project's API_PREFIX

standards

  • 🔄 Shared Claude / Codex rules — AGENTS.md becomes the shared entry point, with a Codex MCP configuration shell; skill mirrors use real directories to avoid Windows symlink checkout differences (#72)
  • 🔧 MR submission and closeout — Checks the default branch baseline before submission and blocks reuse of merged branches. Review monitoring follows the actual reviewed SHA, with explicit Closes / Refs and issue closeout semantics (#70 / #73 / #78)

cli

  • ⚠️ Handwritten API command — sw add api generates top-level function modules in app/api/manual/; sw_project_apis recognizes generated files by @generated
  • 🐛 Project creation and configuration — New projects use real skill mirrors and retain test fixtures when dev mock is disabled. Adds transport, device login, and system info reuse configuration, and warns that nuxt.config.ts may override JSON settings

docs

  • 🚀 Expanded reference guides — Adds the trilingual useExternalRedirect reference and completes component CSS token tables; API generation, rendering TTL, and deployment guides reflect the new contracts
  • 🐛 Site build configuration — Catch-all documentation pages use explicit prerender rules, the nonexistent ~/api auto-import is removed, and PWA name and description derive from site configuration

Cross-repo events

  • 🔧 MR gates for all five core repos — Regular development follows short-lived branches → MR → dev, with automated review and checks. npm releases remain explicit maintainer operations on a local machine; docs retains deployment from dev (framework#416)
  • 🔧 Nuxt security upgrade — framework, starter, and docs upgrade to Nuxt 4.5.2 to address development tooling and SSR security issues, with consumer compatibility checks (framework#386)

2026-08

framework v0.95.0 → v0.140.0 · starter v0.32.0 → v0.35.13 · standards v0.4.4 → v0.13.27 · cli v0.16.4 → v0.21.1

A giant month (1000+ commits across the 5 repos). Early on, the ISR cache-correctness campaign wrapped up (canonical strategy inverted to "follow the visited domain by default"); the main line then shifted to three engineering-governance campaigns — the three-layer rule architecture, e2e joining the CI gate, and the feature-branch flow migration (dev/vN retired) — while framework completed the PWA overhaul and the mobile playback compatibility batch.

framework

  • ⚠️ SwVideoPreview now stops after 60 seconds of playback, and loop defaults to false (#286, 0.101.0) — two behavioral BCs. Preview URLs are commonly implemented as "fall back to the full feature's playback URL when ops hasn't configured a dedicated preview asset", and the component previously had no duration cap at all plus looping on by default ⇒ it streamed for as long as the pointer hovered, meaning paid content could be watched through the preview overlay. That hole was open for every applier, closable only if each one remembered to.
    Adds maxDuration (default 60, 0 = uncapped), measured as cumulative playback time (pauses and buffering don't count); on reaching it the component calls hls.stopLoad() + pause() (keeping the last frame) and emits capped. Why 60: measured industry preview reels already run 55s~2min (Tencent 112s / Youku 55s), so a 60-second cap almost never fires for them (they end naturally via @ended); meanwhile 60 seconds out of a 90-minute feature is 1.1%. loop flipped to false in the same release — neither reference site loops; looping is fundamentally a patch for footage that is too short, and it pairs badly with the cap (6-second footage under a 60-second cap replays 10 times).
    Also closes the silent-on-empty-source hole: three paths (fetchPlayUrl: null without a url / empty id / fetch returning no usable address) previously emitted loading and then went quiet forever ⇒ consumers were stuck in their loading state indefinitely (cinebay's loading scanline swept endlessly because of it). All three now emit the new empty event (not error — "there simply is no preview source" is a valid business state).
    Migration: pass loop explicitly if you need looping and maxDuration if you need longer; applier-side workarounds like "unmount the player with v-if after N seconds" and "force-clear loading on a timeout" can be deleted wholesale
  • 🐛 useHoverPreview now gates touch input internally (#287, 0.101.0) — the source comment used to claim "touch devices have no mouseenter, so it never fires (no extra guard needed)", which is wrong: to keep mouse-only legacy sites working, browsers fire a burst of compatibility mouse events after a tap (mouseover / mouseenter / mousemove / click). Testing with a Pixel 7 profile and real touch input also surfaced a worse consequence than "one stray popup": there is no mouseleave after a tap — lifting the finger produces no "pointer out", and the previous element only receives it when the user next taps elsewhere ⇒ tapping a card on mobile pops the overlay open and leaves it hanging, rather than "flashing briefly before navigation wipes it".
    The gate prefers PointerEvent.pointerType (which input started this interaction) and only falls back to matchMedia, with allowTouch as an opt-out. Don't judge by the media query alone: (hover:hover) and (pointer:fine) reports the device's primary input capability and is wrong in both directions on 2-in-1s — an iPad with a trackpad reports true (finger taps still get through) while a touch-first 2-in-1 with a mouse reports false (mouse hover gets killed, worse than the original bug). Callers should move from @mouseenter="show" to @pointerenter="show" to get the precise check
  • ⚠️ --container-padding gains a wide-screen ladder (≥1280 → 48px, ≥1600 → 96px) (#284, 0.100.0) — a visual change: content areas gain margins on wide screens. This is the follow-up to #283: after the max-width default was raised to 1920, review on a 1920 CSS viewport found it "worse than before" — because a 1920 container on a 1920 screen is full-bleed, making max-width a no-op there; the content was inset by only the 24px padding and read as edge-to-edge.
    Settled by measurement (playwright against live DOM at a 1920 viewport): bilibili, which shares our shape (no sidebar, centred container), gets its margins from padding, not from max-width — max-width: 2260px; padding: 0 140px, with the padding scaling by breakpoint (down to 60px at a 1440 viewport). YouTube (content 240→1920) and iQIYI (192→1897) appear to have a left margin, but that is a sidebar occupying the space — their content area is itself full-bleed, so they are not comparable.
    ⇒ Division of responsibility (the key takeaway): --content-max-width decides "where the container stops growing" (relevant only on 2K/4K displays); --container-padding decides "how much margin there is at any width". Tuning max-width, as we had been doing, was the wrong lever.
    The top tier is 96, not bilibili's 140: their 140 pairs with a 2260 ceiling aimed at 2560 displays, whereas ours tops out already at 1920, where a further 140 would read as empty.
    Migration: no applier needs a code change (none of the seven overrides this variable). Sites that do want a narrower content area must change --content-max-width and --container-padding together — changing only the former leaves the 96px padding and squeezes it a second time (set 1280 and you actually get 1088). See example 3 in starter's overrides.css.
  • ⚠️ --content-max-width default 1200px → 1920px (#283, 0.99.0) — a visual change: content areas get wider everywhere. 1200 is the traditional article-site / marketing-site tier (same family as Bootstrap .container 1320 and Tailwind max-w-screen-2xl 1536), but this ecosystem's consumers are mostly content sites, where it reads visibly narrow on wide screens.
    The deciding evidence is the ecosystem's own vote, not an industry benchmark: anibay, cinebay and yc001 each independently overrode it to 1920 (yc001 writes 120rem, the same value). Leaving the default at 1200 means every site has to remember that one line — and forgetting it is a bug (cinebay#7 caught it only at review). Industry numbers are corroboration only: no content site uses a container ≤1536 (iQIYI 1808 / bilibili 2260 / YouTube 2864; Netflix and Disney+ set no limit at all).
    What 1920 actually means: on a 1920 screen it is full-bleed (only the 24px --container-padding remains); margins appear from 2560 up — bilibili and YouTube are likewise full-bleed at 1920, and none of the three sites felt the need to adjust padding. Long-form pages are unaffected: line length is constrained by each page's own max-width (all 8 static pages in starter use 44rem), independent of the site container.
    Migration: sites that want the old width set --content-max-width: 1200px back in their own overrides.css (one line). Sites already overriding to 1920 need not remove it immediately (idempotent); clean it up next time you touch overrides.
  • 🚀 Build-time guard: "declared uncacheable but swallowed by a cacheable wildcard" (#277, 0.98.0) — when a cacheable wildcard exists, every path it covers gets cached, and a sub-path writing isr: false / cache: false cannot stop it (the cache wrapper is decided once at app init from the route pattern of each registered handler; the routeRules merged per request take no part in that decision). Such pages are green across lint / typecheck / build and render fine — only the response headers reveal them.
    Tiered by ssr, not a blanket error: ssr: false is skipped (it returns a build-time-generated SPA shell that is by definition request-independent — measured: two different queries on the same page produce byte-identical HTML ⇒ caching it is harmless); ssr: true (or undeclared) errors — it really does render on the server, and it fools eslint no-query-in-cacheable-ssr at the same time (that rule only fires when the file declares isr/swr/cache/prerender, while this one declares isr: false) ⇒ it may legitimately read query during SSR while actually being cached ⇒ content mismatch. Tiering makes false positives zero, which is what lets it be an error without needing an opt-out mechanism
    0.98.1 fixed a false positive in the guard itself: redirect / proxy rules do not render a page (the former emits a 3xx, the latter forwards upstream), so a caching wildcard over them cannot cause content mismatch — yet 0.98.0 flagged them. It surfaced the same day, in yc118's real build (a single /home redirect). ⚠️ The lesson attached to it: before releasing 0.98.0 I concluded "one hit across the whole ecosystem, zero false positives" based on scanning each repo's static routeRules in nuxt.config — that does not see the globs that page-level defineRouteRules expands into. Deciding "does any rule of shape X exist in the ecosystem" requires actually running a build
  • 🔧 Error toast decoupled from error reporting (#282, 0.97.0) — errorHandler gains a toast switch: whether an error shows a user-visible toast. Defaults to false = report silently (matching the Sentry / Bugsnag industry default; since 0.87.0 enabling it unconditionally popped "系统出现了一个错误", pure interruption for immersive content sites). Set { "errorHandler": { "enabled": true, "toast": true } } for the old behavior. The POST reporting path is unchanged
  • ⚠️ origin / canonical policy reversed (#278 follow-up, 0.96.0 – 0.96.1) — 0.95.0 had made NUXT_PUBLIC_SITE_URL the primary path; practice overturned that for three reasons: developers often do not know the production domain, production domains change, and a product frequently has several domains. The new policy:
    L1  explicit useSeo({ siteUrl })   per-page override
    L2  NUXT_PUBLIC_SITE_URL           set it to converge on that domain (opt-in, an operational decision)
    L3  useRequestURL().origin         default: follow the domain the user actually visited
    

    The real fix is what makes L3 work at all: the isr → cache translation now always injects varies: ['host', 'x-forwarded-proto'] so cached pages get Host back. The cost is those two headers entering the cache key, so entry count ≈ number of legitimate domains (exactly what a multi-domain product wants); "arbitrary Host creates entries" belongs at the reverse proxy (list every server_name, have default_server return 444). Relatedly, "NUXT_PUBLIC_SITE_URL not set" went from a warning to a normal state — it is only required when the project has prerender pages
  • ⚠️ SwInput upgraded to a wrapper structure (#305, 0.108.x) — the skin (border / background / focus state) moved from the inner input to a new wrapper element. Structural BC: appliers that put --sw-input-* variables or selectors on the inner input fall back to the default skin. Upgrade to ≥0.108.3 before adapting — 0.108.2 and earlier had a class/style placement bug that made the variables land on the wrong layer
  • 🐛 PWA overhaul: the manifest was never linked into head (#355 et al., 0.122.0–0.128.0) — every PWA site in the ecosystem had been generating a manifest that no page ever referenced (@vite-pwa/nuxt only registers the component, never mounts it; "registered ≠ consumed", only visible by inspecting the live DOM). Same batch: manifest gained background_color (dark sites flashed white on cold start) / lang / description (#357); iOS home-screen name prefill and standalone detection fixed (#352 / #353); BC: framework.pwa narrowed to a pure boolean, manifest content moved into swift.config.json (#359, 0.125.0); SwAddToHomescreenGuide adapted to the iOS 26 toolbar plus a redesigned default skin (0.128.0)
  • 🔄 Raw-source publishing experiment and rollback (#289 → #362, 0.124.0–0.126.0) — dist/runtime briefly shipped raw TS/.vue sources (so consumer IDEs could read the source directly); rolled back after real-world testing degraded type resolution on some consumer chains and the changed artifact shape clashed with existing rules. 0.126.0 restored full compilation and added the four assert-publish-shape publish-time guards against silent artifact-shape drift
  • 🚀 Keyboard accessibility batch (#331, 0.130.0) — sortable table headers wrap their content in a <button> (W3C APG pattern; structural BC: #header-* slot content becomes a button descendant and may only contain phrasing content, and th:hover visual selectors must target button.th-inner); SwTable warns in dev when @row-click is bound but rows contain no focusable element (WCAG 2.1.1); overlay buttons div→button, scanner focus ring. Zero adaptation needed across all 9 appliers in practice
  • 🔄 Upgraded to pinia 4, removed the ESM alias hack (0.131.0) — the Pinia 3.x-era ESM compatibility alias was removed wholesale; this also surfaced a legacy violation where an applier self-declared pinia (caught by the duplicate-framework-module build guard)
  • 🚀 Mobile playback compatibility batch (#365 et al., 0.132.0–0.133.0) — iPhone now prefers native HLS: hls.js defaults to ManagedMediaSource, and third-party engines like UC "advance the timeline without rendering frames" (verified on real devices); inline playback attributes completed (webkit-playsinline / x5, preventing iOS forced fullscreen); new hijacking-browser interception blockHijackingBrowsers (UC / Quark / QQ / Baidu blocked with a prompt by default, WeChat deliberately not blocked, customizable via the #hijacking-browser slot)
  • 🚀 Two-state touch progress-bar gestures (#372, 0.138.0) — during playback you can start a horizontal drag directly on the collapsed progress bar to seek, without summoning the control bar first; collapsed / expanded states each have their own gesture semantics
  • 🐛 Set-Cookie stripping on cacheable pages + declaration-conflict guard (#374 / #375, 0.139.x) — a Set-Cookie written during render of an ISR (or otherwise cacheable) page enters the cached copy and is replayed to every subsequent visitor; a nitro plugin now strips it and warns in dev. Page-level defineRouteRules conflicting with config routeRules on the same key is now a build-time error. When upgrading, skip 0.139.0 and go straight to 0.139.1
  • 🚀 SwSelect remote search (#376, 0.140.0) — new remote / loading props and @search event; the search term is handed to the business side for async fetching

starter

  • ⚠️ Template default switched to "follow the visited domain" — the 8 static pages under (footer) moved from prerender: true to isr: true, and NUXT_PUBLIC_SITE_URL became optional. The reason is that prerender runs at build time with no real request and therefore no Host, so origin can only come from that variable ⇒ a single prerender page forces you to configure it, and thereby to converge. isr: true (cached until the next deploy) differs negligibly in runtime cost and needs no such variable
  • 🔧 CI template grew an e2e gate (companion to standards#48, 0.35.0–0.35.1) — verify and e2e run in parallel in the same stage; e2e triggers via path filters on "does the change fall in the e2e applicability surface" (pages / layouts / middleware / server routes / framework config / dependencies), so style-only MRs run verify alone; release MRs always run the full suite as the backstop
  • 🐛 The 0.35.9 broken release and the CI contract test (0.35.10) — a script regex (greedy re.S) swallowed .gitlab-ci.yml down to 3 jobs and it shipped: ci/lint valid, unit tests green — "deletion-type bugs are immune to existence-checking tests". 0.35.10 fixed it and added ci-jobs-contract.spec.ts (each job present + total count + line-count floor), shipped to all appliers with the template

standards

  • 🔧 CI must invoke pnpm via npx --yes pnpm@11; npm i -g pnpm is banned — the runner is a shared shell runner (multiple projects run concurrently on one machine sharing one global node environment), so a global install mutates the global bin and one job can rip out the pnpm binary another job is actively using. Real incident: two pipelines created in the same second landed on the same runner, and one blew up at step four with pnpm: No such file or directory — while its own first three steps had used that same pnpm fine, and a re-run went green. Triage rule: verify failing at "some step reports pnpm/node binary not found" while earlier steps used the same binary successfully = infrastructure flake, re-run before investigating code
  • 🚀 sw-standards sync self-service CLI (#32, 0.10.0) — appliers sync rules themselves with one command (marker sections / skills / thin shells) instead of waiting for maintainer pushes; prints a rule-change summary, --check has exit-code semantics
  • 🔄 Three-layer rule architecture (#43) — downstream convention files split from a single monolith into three layers: an always-loaded AGENTS.md index (P0 red lines + selection table + detail channels) + on-demand .claude/skills/sw-* details + the standards rules/ source of truth; sync target families extended accordingly (agents / skills / thin shells), with Codex / Copilot sharing the same content via the .agents/skills mirror
  • 🚀 Lint enforcement batch — prettier base flipped to semi: true + singleAttributePerLine (0.11.0, ecosystem-wide reformat); new stylelint rules hover-media-gate (:hover must be wrapped in @media (hover: hover), paired with the touch hover-stickiness fix) and component-token-prefix; eslint root-class-sw-prefix upgraded to v3 (the root class name must match the component name)

cli

  • 🐛 sw config set rejected legitimate object configs (#46, 0.16.5) — framework.pwa and framework.errorHandler are typed boolean | XxxOptions on the framework side, yet both were wrongly listed in cli's "boolean-only keys" allowlist ⇒ sw config set framework.pwa '{"enabled":true}' was refused as an invalid shape. Replaced with per-union shape validation covering all three entry points (leaf key / sub-field / whole-object set on the parent)
  • 🐛 An object pwa config was read as "off" and deleted @vite-pwa/nuxt (#47, 0.16.6) — a regression from the fix above. Before #46 the object value was rejected before it could be written, so this path was unreachable; once the boolean guard was removed the object became legal, but the applier dispatch still read getNestedValue(...) === true ⇒ any object evaluates to false ⇒ disablePwa() runs. The config said PWA was enabled while the dependency was removed from package.json, with the notice buried in the success output. Fixed by mirroring framework's own resolvePwa normalization (boolean → itself; object → read .enabled, absent means off)
    ⚠️ The lesson: removing a key from a validation allowlist admits a shape its downstream consumers have never seen. Check what consumes that key and what shape they assume before removing the guard. The same class of bug, cli#39, was originally avoided by rejecting at write time; #46 dismantled that rejection without patching the dispatch, so it came back unchanged
  • 🔧 Dropped the ./ prefix from the bin path (0.16.7) — silences the "bin[sw]" script name ... was invalid and removed warning npm prints on every publish. The wording is misleading (it sounds like bin was dropped; npm only strips the prefix). Zero behavior change — what it removes is the noise printed on every release
  • ⚠️ AI-invocation safety batch (#48 / #49, 0.20.0) — sw upgrade gained --check / --yes; non-interactive (MCP / piped) write operations no longer run with zero confirmation, and exit codes are tiered 0/1/2 (three BCs); all 15 MCP tools gained annotations (read-only / write / destructive appearance)
  • ⚠️ PWA install/uninstall responsibility handed to framework (0.17.0–0.19.0) — the cli pwa feature no longer installs or removes @vite-pwa/nuxt (framework carries it uniformly); the framework.pwa config shape narrowed to a pure boolean alongside framework#359, validatePwaShape removed

docs

  • 🐛 /composables/use-hover-preview had been unreachable in production since it shipped — docs is an SSG site whose prerendered routes come from a crawler following existing links, and that page was neither in the sidebar nor linked from any page body ⇒ zero inbound links ⇒ never generated, so production served the homepage fallback. What makes it insidious: build, tests and lint were all green, and hitting the URL under local pnpm dev worked perfectly (dev renders on demand and never runs the crawler) — only a human could catch it. The sidebar entry is restored, plus an orphan-page guard: every content page must have at least one inbound link (sidebar / catalog.ts / another page's body — any one suffices; demanding "all must be in the sidebar" would wrongly flag the latter two legitimate forms)
  • 🚀 Guides caught up with process evolution (trilingual) — new "Test sync & gates" chapter (e2e applicability / verify gate / coverage ratchet / login-state injection); the Git collaboration chapter rewritten for feature-branch flow; the production deployment page reshaped as an ops runbook (a 9-step first-launch checklist + a single command for every launch after)

Cross-repo events

  • 🔧 Feature-branch flow migration (standards#51 / #52) — all 9 appliers unified on dev/main as the default branch and the long-lived dev/vN branches were fully retired; only dev/main (integration / testing) and main (production) remain long-lived, everything else is a short-lived branch deleted on merge. A production release is a dev/main → main release MR: mr-review posts a "🚀 release summary" for release MRs (D5) and stops auto-merging MRs that touch risk paths like server/ / CI / dependencies (D6, human merges); the starter CI gained a release-guard advisory job. Branch naming and upstream-first were settled as recommended-and-default, not mandatory — the mechanical layer only protects the two long-lived branches themselves
  • 🔧 e2e joined the CI gate + coverage ratchet (standards#48 / #49) — e2e formally became part of the applier merge gate (parallel with verify, path-triggered, full run on release MRs as backstop); coverage ratchets per-repo and only goes up; login-state e2e logs in through the UI once in a setup project and reuses storageState (TS-003); yc118 / yc124 were the first two appliers onboarded

2026-07

framework v0.58.1 → v0.95.0 · cli v0.10.0 → v0.16.4 · standards v0.3.2 → v0.4.4 · starter v0.21.0 → v0.32.0

The last week of the month was the "ISR cache correctness" campaign (starting at #271): it began with the discovery that isr has no effect at all on a self-hosted node-server (nitro core simply does not consume it), and cascaded into five more failures that are green everywhere yet silently broken — wildcard pollution, an fs-driver parent/child path collision, rules dropped for multi-dynamic-segment paths, missing zero-segment parent coverage, and cached pages losing Host so canonical became localhost. Each got a build-time guardrail.

Theme/language switchers consolidated and the 0.64 architecture orthogonalization laid the base; mid-month the two-tier CSS variable naming model and token-surface expansion hardened the design language; late month shipped the admin table (SwTable), SwSelect search/create, the self-hosted tracker channel and self-serve registration back to back. starter landed the movie demo trio plus a universal footer template; the design system (Claude Design / Open Design dual-track + 20-card pipeline) went fully live; the month closed with the OpenAPI dual-source & ISR privacy guardrails, unified form-control sizing + SwInput, and the SwVideoFeed preload traffic switch.

framework

  • 🚀 Theme capability rework (epic #191) — theme config collapsed to a single theme.mode knob (system / toggle / dark / light) + color-scheme decoupling + resolvedTheme; built-in SwThemeSwitch (renders only in toggle mode; token / icon-slot / scoped-slot customization tiers); anti-FOUC inline bootstrap injected in toggle mode
  • 🚀 SwLangSwitch built-in language switcher (#192) — self-gated (renders only with i18n enabled and locales ≥ 2), wired to cli locale config and SEO prefix routing; fixed active-state on SSR-rendered prefixed pages
  • 🚀 Encrypted useCookie override + structured props in framework-api.json (#203) — framework useCookie (auto cachePrefix + AES) registered as a same-name auto-import override of the Nuxt built-in; framework-api.json gained a structured props array + a three-level tsLayer fallback
  • ⚠️ 0.64.0 architecture orthogonalization BC (#206) — ModuleOptions dropped the theme boolean sugar; sitemap merged into the seo suite; YAGNI-shrunk public API; safeExternalUrl / safeInternalPath guards moved up into framework
  • 🚀 Dev mock on both ends (#208, 0.65.0) — registerApiMock widened to dev-time SSR + CSR interception; the universal-plugin pattern became the recommendation; prod stays fully stripped on both ends
  • 🚀 Baseline-styles doctrine (#210, 0.66.0) — "baseline styles, not zero styles" (ugly-but-usable passes, looks-broken fails) entered the style spec; SwRail gap default fixed, SwFormField gained state-feedback baselines
  • 🐛 Fix batch (0.66.x) — nuxt-schema-org pinned to 6.1.3 (client-hydration crash); preroll-period autoplay audio/video mixup (#211: autoplay suppressed while the preroll shows; skip() is the single playback entry)
  • 🔄 Two-tier CSS variable naming + token-surface expansion (#219 / #213, 0.68.0) — semantic tier keeps bare Shadcn names; component tier is always full --sw-<component>-<prop> (--modal-*→--sw-modal-* migrations, direct rename in 0.x, no shims); new --foreground-soft / --gradient-brand tokens; dark-site --shadow-* override and currentColor icon-tinting doctrines fixed
  • 🔧 WebSDK v1.1.6 vendored upgrade (#225, 0.70.0) + seo-tdk-length lint (TDK governance: static display-width pre-check of literal title/description, warn level)
  • 🚀 SwTable data table (#227 → #234 admin hardening → #235 type exports, 0.71–0.73) — the admin-console component: columns config + #cell-<key> slots, controlled tri-state sorting (v-model:sort), batch row selection (v-model:selected with indeterminate), structure-preserving loading mask, stripes / compact density / sticky header; SwTableColumn etc. barrel-exported; self-defends against global table styles
  • 🚀 SwSelect searchable / creatable (#226, 0.71.0) — Combobox branch: input filtering + chips multi-select + "create …" on the fly (multiple + searchable + creatable = tag-input); alongside SwVideoFeed playback-rate / line-switch APIs (#229)
  • 🚀 Self-hosted tracker channel + first-screen pageView (#228 / #236, 0.71–0.73.3) — sites without WebSDK report via their own endpoint (batching + sendBeacon fallback); first-screen pageView backfill for SSR entry (0.73.3 hotfix for a TDZ crash in the backfill watch; ⚠️ do not stay on 0.73.2)
  • 🚀 sw-hit-area touch-target opt-out (#230, 0.72.0) — compact controls escape the 44px stretch while a pseudo-element extends the tap area to 44×44 (the "small visual + wrapped hit area" convention, built in); 🐛 SwVideoFeed gesture-interrupt snap-back (#231), SwModal close ✕ → inline SVG (#232)
  • 🚀 userStore.register self-serve sign-up (#233, 0.74.0) — symmetric with loginByUsername, reuses the setInfo chain, logs the user in on success (driven by the bayverse user system)
  • 🐛 Fix batch (0.74.x) — sitemap XSL title "undefined" (#237: site.name falls back to app.head.title); SwCarousel manual navigation now resets the autoplay phase + pauses on keyboard focus (#238: a just-picked slide is no longer swapped away by an imminent tick)
  • 🚀 SwVideoFeed pull-to-refresh (#241, 0.75.0) — opt-in pullToRefresh: pulling past the threshold on the first clip at the top emits @refresh, closed via the controlled refreshing prop + a #refresh-indicator slot for brand indicators (floating, never shifts content, touch only)
  • 🐛 useSeo lint now covers error.vue (#240, 0.74.2) — require-useSeo-in-pages previously only matched app/pages/**, contradicting the documented scope; with isErrorPageFile the global error page now warns when useSeo is missing (noindex on error pages is a hard requirement)
  • 🚀 generate-api dual source: OpenAPI 3 (#242, 0.76.0) — source-adapter architecture; the "API docs → request code" auto chain for self-built servers runs alongside the YAPI source
  • 🚀 ISR private-data guardrails L2–L4 (#243 / #245, 0.77.0–0.78.0) — generate-api splits files by visibility + manifest + private metadata injection; eslint isr-no-private-api machine-checks private API calls on ISR/prerender pages (predicate narrowed to SSR execution paths to kill event-handler false positives); dev-time SSR-calls-private warning in request; generate-api --check drift detection
  • 🚀 Unified form-control sizing + SwInput (#244, 0.78.0) — semantic tokens --control-height (32px) / --control-font-size (14px); SwInput / SwSelect / SwSegmented align by default in one row (⚠️ intentional visual BC: SwSelect 36→32px & font 16→14, SwSegmented 42→32px); new SwInput component (form baseline box + iOS focus-zoom counter-measure on touch devices), starter LoginForm migrated as the reference implementation
  • 🚀 SwVideoFeed configurable preloading (#246 / #248, 0.79–0.80.0) — preload?: boolean | number: by default the next 2 video items preload their first 5s each (⚠️ previously ±1 items fully preloaded 30s each — big CDN traffic cut); ad entries skipped without consuming quota, tail clamps, mount window extends forward; false disables entirely
  • 🐛 Wheel inertia-tail overshoot fix (#247, 0.80.0) — one Windows precision-touchpad swipe skipping multiple items: unlock switched to a 240ms quiet window + absolute floor on the force-rise check; deltaMode normalization added (one Firefox mouse-wheel notch now flips one item)
  • 🚀 Tracking capacity trio (#249 / #250 / #251, 0.81.0) — track event names widened to "preset enum + any custom string", routed per channel (custom names only persist through the self-hosted endpoint; the WebSDK channel warns in dev and drops non-enum names); video playback heartbeat plus cumulative watch_time; ad-close tracking via useAdTrack.reportClose (with close_reason)
  • 🚀 useHoverPreview hoisted into framework (#253, 0.83.0) — the hover-intent state machine for card zoom preview (300ms open / 140ms close / hold while traversing / close on scroll) plus the computePreviewBox positioning pure function; the overlay's visuals stay with the app, and starter deleted its local copy in favor of the framework one
  • 🔄 Token and geometry trio — media scrim semantic tokens --scrim / --scrim-top (#254, 0.84.0, consumed in one line so no no-hardcode disable is needed); SwRail arrow vertical anchor --sw-rail-arrow-top (#252, 0.82.0); SwModal viewport-safe width / max-height plus class pass-through onto content (0.85.0, the official channel for per-dialog customization)
  • ⚠️ SwSegmented default visuals aligned to the industry grey-track form (0.86.0, visual BC) — along with a fix for "the selected item's hover was flattened to low-contrast black text by the generic hover rule"
  • 🚀 Built-in copy localized (#261, 0.88.0) — new sw-i18n built-in dictionary plus useSwText; all built-in copy and aria-labels across 19 components now go through it (previously hardcoded Chinese, which showed through on non-Chinese sites)
  • 🚀 @swift-web/framework/store subpath export (0.90.0) — makes the "same-name override" extension template actually work: when an app store shares a name with a framework store, only an explicit import reaches the base — otherwise it points at itself and recurses forever
  • 🐛 ISR never cached anything on self-hosted node-server (#267, 0.92.0 — the month's most important fix) — nitro core only normalizes swr and does not consume isr (only the vercel / netlify / zeabur presets recognize it), so writing isr on a self-hosted deployment meant zero caching and a full SSR per request. It is now translated at build time into an equivalent cache key that coexists with it (platforms use the CDN, self-hosting uses the server cache — both correct). The cache also moved onto a bounded LRU (1000 entries / 50MB / 2MB per entry), downgrading "random query strings exhaust process memory" from an OOM to a hit-rate drop. Application code is unchanged — keep writing isr
  • 🚀 Four build-time guardrails for rendering strategies (0.89–0.95) — what these failures share is that lint / typecheck / build are all green and the page looks fine; only curling the artifact reveals them, so each became a build-time assertion that prints a paste-ready fix: #262 (a wildcard rule filling rendering-semantic keys into the parent path, 0.89.0; 0.89.1 fixed three criteria defects, 0.89.2 excluded Nuxt-internal routes) / #274 (fs cache driver × parent and child path both cacheable ⇒ ENOTDIR, 0.93.0) / #276 (a path with ≥2 dynamic segments makes Nuxt drop the page's defineRouteRules entirely, 0.94.0) / #279 (the zero-segment parent path of an optional-segment page never gets the cache wrapper, 0.95.0)
  • 🚀 Cacheable pages must not read query (#271 companion) — new eslint rules no-query-in-cacheable-ssr (error) and no-ineffective-route-rules; guardrail L5 catches "a cacheable page carrying user state through SSR"; a dev-time large-response warning names the offending endpoint and its size at the root cause. Combined with a path-only cache key at the reverse proxy, cache explosion moves from mitigated to eliminated
  • 🐛 Cached pages emitted localhost as their canonical (#278, 0.95.0) — before entering the handler, cachedEventHandler keeps only the request headers listed in cache.varies; Host is dropped, so useRequestURL().origin falls back to http://localhost and canonical / og:url are wrong — but only on pages that have actually been cached
  • ⚠️ Global touch minimum height removed in favor of opt-in sw-hit-area (#265, 0.93.0, visual BC) — the old rule applied min-height: 44px to every a / button and accumulated 38 opt-outs over a year. A survey of the shipped CSS of 13 major libraries found zero instances of such a blanket rule; the spec constrains the hit region rather than the visual box, the compliance line is AA's 24px rather than AAA's 44px, and inline text links are explicitly exempt — all three were backwards. The replacement expands the hit region with a pseudo-element and leaves visuals untouched
  • 🔧 Also — vendored WebSDK upgraded to v1.1.7 (#280, adds automatic collection of four hardware-info event types; the 12 facade methods and 28 events are unchanged); player autoplay now defaults to off, respects the prop, and degrades to muted, plus a self-healing fallback for "playing but the poster is still covering it" (#281); useLocal / useSession generics no longer collapse to unknown

starter

  • 🐛 Redirect-page DOM XSS hardening — ?url= jumps go through an http/https scheme whitelist (rule also distilled into standards)
  • 🚀 theme.mode adoption + SwLangSwitch + Cursor out-of-the-box — single-knob swift.config + topbar switched to framework built-ins; .cursor/mcp.json checked in
  • 🚀 Movie demo trio fully landed (#48–#51, 0.26–0.28) — /movie floors / list / detail reference implementation: three layout knobs + ads in every layout + card info layer v2 + episode panel + full danmaku chain + preroll-ad showcase (play_ads → SwAdPreroll)
  • 🚀 Universal site footer template (#52, 0.29.x) — data-driven AppFooter (brand block / link groups / round icon links / optional friend-links module; edit 5 constants, never the layout) + six (footer) route-group static pages (about / contact / ads / terms / privacy / faq, prerendered) + top-nav typography & active-state polish + new lightning logo unified
  • 🔄 Audit fix batch (0.26.1) — login-mode toggle migrated to SwSegmented + in-site redirect guard converged on framework safeInternalPath
  • 🚀 Compliance placeholder pages (0.30.0) — two structural placeholder pages under (footer): the 18 U.S.C. § 2257 compliance statement and an abuse report (DMCA) page (noindex + prerendered); uncomment the footer "Policies" links to enable — standard compliance fixtures for adult-content sites
  • 🚀 Card hover zoom preview (Refs fw#253, 0.30.3) — the trending floor switched to 16/9 landscape, plus a MovieHoverPreview overlay and a preview prop on MovieCard / MovieFloor (on by default); the mechanism later moved up into framework and starter deleted its local copy in favor of useHoverPreview / computePreviewBox
  • 🔄 Audit fix batch (0.31.0) — deploy no longer clobbers the hand-written .env on the server; placeholder domains corrected; noindex pages excluded from the sitemap; TabBar dead-link defaults; the error-report endpoint moved out of /api/ (clear of the app's own proxy); the comment composer's avatar moved to onMounted backfill to clear an ISR-page hydration mismatch
  • 🐛 Extension store template fix (0.32.0, paired with fw 0.90.0) — now imports the base explicitly from @swift-web/framework/store, fixing "the base points at itself when names collide"
  • 🔄 Sort parameter moved from query to a path segment (#271 pattern) — movie/list/[id]/[[order]].vue plus boundary validation, along with a nuxt.config routeRules fallback (Nuxt silently dropped that page's defineRouteRules — the fw#276 shape)

cli

  • 🐛 Windows hard-failure fix (#31) — spawnSync with shell, fixing sw create / run always failing on Windows
  • 🐛 Upgrade & timeout fixes — sw upgrade with --latest fixes false-success across 0.x minors; npm pack timeout misreport fixed + cap raised to 300s (#36)
  • 🚀 sw_project_apis MCP tool (#40) — parses the applier's app/api output so AI can query the backend API list directly
  • 🔄 Config-sync BC batch (0.15.2 – 0.16.1) — framework.sitemap moved to framework.seo.sitemap; sw add page template TDK guidance upgraded (Chinese-width notes + TODO placeholders, 0.16.0); schema dropped tracker.autoPageClick (with fw#228, 0.16.1)
  • 🐛 Fix batch (0.15.x) — sw info crash on missing fields / config set validation gap / corrupt swift.config.json now yields CONFIG_PARSE_FAILED
  • 🐛 Input allowlist hardening (#45, 0.16.2) — sw add / sw config now reject prototype-chain keys, invalid identifiers, path traversal and prototype pollution. ⚠️ The first regex also caught Nuxt dynamic route segments [id] / [...slug] (a self-inflicted regression, allowed through in 0.16.3)
  • 🔄 --text mode delivers on its plain-text promise (0.16.2) — ANSI escapes are now stripped uniformly; also fixed cross-repo drift in KNOWN_DESCRIPTIONS and aligned sw info's framework-version read with version-check

standards

  • 🚀 v0.4.x rule additions — "user-controlled jump targets must pass a scheme whitelist" / SOP "claim existing issues instead of duplicating" / no-hardcode transition-period playbook / --brand-* business-token namespace / dual-end mocking
  • 🚀 v0.4.3 TDK governance — new eslint rule seo-tdk-length (warn-level literal pre-check) + the "write TDK to full length up front" rule section (display-width based)
  • 🔄 Component rule sync batch — SwTable / SwSelect searchable·creatable (SEL-003) / sw-hit-area compact-control opt-out (incl. chips/tab as legitimate cases) / register added to the userStore write-method list / dark-site shadow overrides & currentColor icon tinting / SwInput & --control-* form-control family added to the component-first table / SVF-006 pull-to-refresh controlled wrap-up
  • 🔄 Major rendering-strategy revisions (v0.4.4 onward) — added the hard rule "cacheable pages must not read query in SSR" plus the four structural preconditions for path-ification, and rewrote the "touch targets constrain the hit region, not the visual size" section (including the 13-library survey). Also corrected the root-cause wording for zero-segment parent paths: inside nitro, the routeRules matcher and the h3 handler router disagree on whether /P matches /P/** — the original text only described one of them

docs

  • 🚀 Demo props coverage closed (#84) — coverage rule landed + four backfill batches, final 278/278 = 100%
  • 🚀 "Design System" section launched (#85 – #88) — 7 tri-lingual pages (design language / Figma workflow / Claude Design quick start / collaboration / governance) with Figma and Claude Design as equal tracks; "Meet Open Design" for local-agent design-system bundles; the card-dictionary page previews all 20 cards in-frame with both download bundles
  • 🚀 Deployment guide: video transcoding contract — HEVC must ship with H.264 dual-codec variants (mermaid selection diagram + single-URL frontend contract + dual-URL anti-pattern + AV1 outlook)
  • 🐛 Fixed 39 broken same-page anchors (#90, 34% of the site's 113) — the root cause is that heading ids come from @nuxtjs/mdc under counter-intuitive rules (dots are swallowed, consecutive - collapse into one, a leading digit gets a _ prefix); a new docs-anchors.spec.ts now validates every same-page anchor and pins 8 measured values for the reimplemented algorithm itself
  • 🐛 Fixed 6 groups of factual errors plus a new table-structure guard (07-28 audit)
  • 🚀 New guide: how isr actually takes effect when self-hosting (all three languages) — companion to fw#267, correcting the original description of where the cache lives

Cross-repo events

  • 🔧 Continuous audit cycles (07-02/03/04 triple + 07-07/08 double + 07-13 deep audit) — multiple swift-web-audit repair rounds; the 07-13 batch coupled the four-way TDK governance (framework lint + cli templates + standards rules + docs), with release chains spanning framework 0.62–0.69 / cli 0.13–0.16 / starter 0.24–0.26 / standards 0.4.1–0.4.3
  • 🔧 Design-system dual-track pipeline (07-09 – 07-17) — the framework scripts/design pipeline (ds-build derives 20 cards from source + brand-inject + od-build Open Design bundle) → Claude Design base & yc001 brand projects + docs-site bundles and live card previews, all with framework source as the single source of truth
  • 🔧 ISR cache-correctness campaign (07-29 – 07-31; framework 0.89 → 0.95 plus starter, standards, docs and 8 downstream sites) — starting from "should the reverse proxy cache HTML at all", the trail led to five rendering-strategy failures that are green everywhere yet silently broken. Each got a build-time guardrail, the rule source and trilingual docs were updated, and all 8 consumers were upgraded. The lesson condenses into one criterion: "the site isn't slow" is not evidence that a cache rule took effect — a fixed TTL at the reverse proxy masks misconfiguration in the application layer, so the only valid evidence is the response headers you curl out of the artifact
  • 🔧 Design-system derivation now reads from source (fw#257) — ds-build reads component-card CSS straight from each .vue file's <style> block, eliminating hand-copied snapshot drift

2026-06

framework v0.16.1 → v0.58.1 (42 minors + 20 patches) · cli v0.5.6 → v0.10.0 · standards v0.2.0 → v0.3.2 · starter v0.14.4 → v0.21.0

A component explosion month: the ad component family (12 SwAd*) + WebSDK tracking foundation, the rail / carousel / video-feed / grid component family (fully dropping swiper), player ads & paywall, and the SwEditor rich-text editor — plus the module setup layering refactor, the config system overhaul, the ecosystem-wide pnpm 11+ gate, and the obfuscation CSR bare-style root fix as cross-repo events.

framework

  • 🚀 Ad component family + unified tracking foundation (epic #94)
    • 12 self-rendering ad components landed over the month: image SwAdBanner / SwAdMrec / SwAdIcon, text cards SwAdText / SwAdMarquee / SwAdNative, popup / floating SwAdSplash / SwAdPopup / SwAdFloating, player slots SwAdPreroll (pre-roll, play_ads protocol adapter) / SwAdPause (pause ad), compliance badge SwAdLabel — with built-in impression + click tracking; optional copy + CTA overlay for image ads
    • Group WebSDK reporting foundation: useTracker (28 WebSdkEvent types) / useAdTrack (auto-assembles ad fields) / useAdFrequency (frequency control via encrypted cache) + the tracker.client plugin (lazy-load / auto pageView / silent degrade when unconfigured)
    • :track built-in component tracking (SwLink / SwButton / SwCarousel / players, #123) + the generic useExposure composable
  • 🚀 Rail / carousel / video-feed / grid component family (#106) — SwRail (native horizontal scroll + scroll-snap) / SwCarousel (transform paging + seamless loop + autoplay + dots / thumbnail indicators) / SwVideoFeed (TikTok-style vertical short-video feed, bare-hls video pool + virtualization) / SwGallery (responsive grid + load more) / SwPagination (page navigation, merged up from starter)
  • 🚀 Player ads + paywall + danmaku — SwVideoFeed in-feed ad items + entry pre-roll (forced watch prerollAdForceWatch, #179); SwVideoPlayer / SwVideoFeed trial paywall (#174) + replay-preview (#186); SwVideoPlayer built-in danmaku (conditional lazy-load + primary-color token)
  • 🚀 SwEditor rich text (#187) — rewritten wrapper keeping the Tiptap core: v-model / @mention #topic / built-in emoji panel / image & video upload (emit-to-business) / à-la-carte toolbar / tokenized default skin
  • 🚀 New components & skin move-ups — SwSegmented / SwSelect (inline single-select / dropdown), SwBreadcrumb (headless breadcrumb + BreadcrumbList JSON-LD); modal / toast default skins tokenized and moved up into framework (#173) so appliers can drop their local copies
  • 🚀 registerApiMock dev-time request mock layer (#188) — declarative plaintext-layer interception (dev-only + client-only + DCE-stripped in prod) + built-in login / system-info presets; build now emits framework-api.json (four-layer API JSDoc data source, consumed by the cli MCP)
  • 🔄 module.ts setup layering refactor (#99) — extracted seven setup/ layers (types / defaults / resolve-options / diagnostics / register-config / register-runtime / configure-build); module.ts collapsed into thin orchestration + an assembly-snapshot guard test
  • ⚠️ BC set — userStore login() → autoLogin() + new public loginByDevice(); useLoginDialog show → open; SwAdSide → SwAdFloating rename; ad popups split by scenario into SwAdSplash / SwAdPopup; player API naming aligned with HTML / industry conventions
  • 🐛 Obfuscation breaking CSR CSS preload — root-fixed (#169) — production obfuscation swallowed Vite CSS preload URLs, leaving SPA navigations fully unstyled; 0.39.4 disables stringArray + disables inlineStyles by default (framework-level fix, upgrade to get it)
  • 🐛 SSR / security fix batch — deviceId switched to the secure-context-safe randomUuid() (fixes 500 on non-HTTPS / LAN HTTP); useSeo prerender-page origin falls back to NUXT_PUBLIC_SITE_URL (#117, fixes OG / canonical baked as localhost); build-time fail-fast for cacheAesKey length / auth cookie names

starter

  • 🔄 Ad system replacement — fully dropped swiper; deleted all self-built ad components in favor of the framework SwAd* family; splash ad migrated to SwAdSplash (driven by system/info.layers)
  • 🚀 Login & capability consolidation — yc001 login component UI / structure backported (LoginForm + login.vue); modal / toast skin copies deleted in favor of framework built-ins; PaginationBar retired and merged into SwPagination
  • 🐛 Sitemap consolidated into framework — removed the self-built sitemap pipeline (eliminating the path-traversal risk of self-written file-serving routes); unified on framework's @nuxtjs/sitemap
  • 🔧 CI / deploy / mock engineering — verify gate job (type-check + lint) + CI moved to pnpm; deploy/ ops kit + pnpm deploy:prod (build in a separate directory, rsync without --delete — root-fixing the bare-first-paint window); built-in business dev-mock presets (full movie / search set)

standards

  • 🔄 Rules single-source refactor — split the monolithic file into rules/ sources + dual assembly manifests (_claude / _mr); downstream CLAUDE.md slimmed with separated responsibilities
  • 🚀 stylelint no-hardcode enforcement (#17) — hex literals banned for color properties (error level) + Tailwind at-rule / function whitelist
  • 🚀 Unified dev SOP + applier CI/CD + deployment rules — issue-dev-sop landed as the single source (issue-as-plan / atomic per-task commits / self-test evidence), governance wikis retired; applier MR gate workflow + long-lived branch sync discipline + test / production deployment rules
  • 🚀 usage-review-rules expansion + interaction & UX rules — absorbed 20+ numbered rules across utils / store / composable layers (CACHE / REQ / AES / DB / STORE etc.); new rules for encrypted cache utils / ad slots & tracking / dev mock guardrails / medium doc density; interaction & UX rules UX-001~010 (#25)

cli

  • 🚀 theme applier + MCP rich docs — theme feature applier (#20, theme.enabled auto-toggles dark-mode marker blocks); sw_framework_docs reads framework-api.json (four API layers + gotcha links + framework config layer)
  • 🚀 create / config capability completion — sw create asks about enabling dev mock (deletes presets if declined); sw config + MCP now cover the full swiftConfig + double-confirmation for dangerous auth items; applier creation adds the pnpm 11 guard + generates .npmrc

docs

  • 🚀 Component family docs batch-added — carousel family (SwRail / SwCarousel / SwVideoFeed) trilingual + DemoBox; "Ad foundation & tracking" guide trilingual
  • 🚀 Build-time llms.txt / llms-full.txt generation (AI-friendly docs entry)

Cross-repo events

  • 🔧 Ecosystem-wide pnpm 11+ gate — engines.pnpm ">=11" floor across repos, dropped corepack / packageManager pinning, pnpm field moved to pnpm-workspace.yaml, CI installs pnpm@11
  • 🔧 Release chain hardening — all 4 publishing repos unified on npm version (pnpm version ignores the tag prefix) + typecheck hard gate in release + release scripts load .env safely (no more secrets on stdout) + per-repo .env files deleted in favor of the single ~/.zshrc credential source
  • 🔧 Config system overhaul (framework#164) — project constants moved into nuxt.config, .env keeps only environment-specific values, zero required env at runtime (removing the empty-config trap for non-primary cluster instances); landed across framework / starter / standards
  • 🔧 Test hardening — framework e2e fixture scenario-count thresholds codified + fixture expansion across all components (#143–156 group, 240 e2e all green); starter page smoke tests removing 0/0 coverage blind spots; cli parameter-combination + boundary tests

2026-05

framework v0.7.0 → v0.16.0 (9 minors + dozens of patches) · cli v0.2.7 → v0.5.1 · standards initial release v0.0.1 → v0.1.3 · starter initial release v0.14.0

The pivotal month where the 5-repo system (framework + starter + standards + cli + docs) took shape and entered rapid iteration. Covers ISR security hardening, cookie encryption, SEO pipeline consolidation, SwButton returning to vue idiom, verdaccio private registry launch, and starter / standards first npm releases.

framework

  • ⚠️ useSeo single entry + fix duplicated <title> (v0.16.0 BC, refs framework#89)
    • Removed the system.client chain: the old version overwrote the page's useSeo title/description with backend header values via useSeoMeta after hydration, causing <title> to duplicate into SiteName | SiteName and description to be overwritten
    • SEO is now fully explicit, single entry: useSeo no longer falls back to backend systemStore.header for title/description/keywords/og_image (missing values become dev console.warn, no silent fallback)
    • titleTemplate handed back to Nuxt native: removed ModuleOptions.head.titleTemplate + useSeo's disableTitleTemplate input; appliers configure the site-name suffix via app.head.titleTemplate in nuxt.config.ts (sourced from swift.config.json's project.title)
    • JSON-LD WebSite.name reads runtimeConfig.public.siteName, no longer backend site_title; og:image falls back to local /og-image.png only
    • Upgrading appliers must: pass title/description explicitly per page + configure app.head.titleTemplate + runtimeConfig.public.siteName in nuxt.config.ts
  • 🚀 sitemap.xml refinement + i18n strategy detection (refs framework#85, alongside #87)
    • GAP1: sitemap siteUrl locked to NUXT_PUBLIC_SITE_URL, decoupled from useSeo's runtime origin (sitemap is a search-engine catalog requiring a stable primary domain; canonical follows the user's visited domain)
    • NUXT_PUBLIC_SITE_URL host-format check: valid URL + http/https protocol + pure origin (no path/query/hash). dev console.warn / build console.error, build is not blocked. Catches missing-protocol (example.com) / placeholders (<your-domain>) / wrong protocol (ftp://) / path-included (https://x.com/api) dirty inputs
    • GAP2 simplified: detects @nuxtjs/i18n strategy === 'no_prefix' → dev console.warn guiding toward prefix* strategy (hreflang auto-config tracked in framework#88)
    • GAP3 default exclude for private paths: sitemap by default excludes /login, /register, /mine/**, /profile/**, /admin/**, /dashboard/** and other common private paths, so it no longer contradicts useSeo({ robots: 'private' })
    • Added ModuleOptions.sitemap.exclude?: string[] for appliers to append business-specific private paths (merged with framework defaults, not replacing)
    • Added docs: sitemap config / NUXT_PUBLIC_SITE_URL config / ModuleOptions.sitemap
  • ♻️ useSeo origin resolution simplified to a single source of truth + CSR consistency check (refs framework#87)
    • 4-tier fallback (L1 explicit → L2 systemStore.info.site_url → L3 runtimeConfig.public.siteUrl → L4 useRequestURL().origin) → 1 source of truth + 1 explicit override
    • Main path: useRequestURL().origin (SSR Node parses Host/X-Forwarded-* headers; CSR uses window.location.origin)
    • L1 explicit input.siteUrl is preserved as an "explicit override" (e.g. multi-domain setups forcing canonical to converge on the primary domain)
    • During CSR hydration, framework runs a one-time comparison between <link rel="canonical"> and window.location.origin. Mismatch = nginx forwarding misconfigured → dev console.warn / prod console.error + fix guide + docs URL (nginx origin header forwarding)
    • SystemInfo.site_url is preserved as a backend field for business use (e.g. "Latest Address" jump link), no longer involved in SEO origin resolution
    • NUXT_PUBLIC_SITE_URL is now build-time only (prerender / sitemap)
    • Behavior change but the API signature is unchanged — projects that previously relied on L2/L3 fallback should verify their nginx forwards Host / X-Forwarded-Host / X-Forwarded-Proto; otherwise canonical will resolve to internal IPs (the diagnostic console.error will fire immediately)
  • 🔧 publishConfig switched to verdaccio (v0.14.0 / v0.14.1, refs framework#77 / #78 / #79)
    • framework switched to self-hosted verdaccio private registry (npm.djatm.cloud); downstream appliers no longer install via GitLab npm registry
    • prepublishOnly hook strips scripts.preinstall from the publish manifest, preventing consumers from accidentally running pnpm check scripts during install
    • workspace config @swift-web/standards: false prevents consumers from running standards' preinstall
  • ⚠️ defineRouteRules hard validation + removed default error.vue (v0.13.0 dual BC, refs framework#71)
    • Appliers must explicitly enable experimental.inlineRouteRules; not enabling it makes the framework module fail at setup time
    • Previously this was a silent no-op: appliers wrote defineRouteRules but it had no effect, surfacing latent ISR / SSR behavior bugs after this switch
    • Removed framework default error.vue; errorComponent is now fully delegated to the applier
    • Framework-internal createError now throws directly instead of falling back to a default error page
  • 🐛 SSR / error handling hardening (v0.12.7 ~ v0.13.0 series)
    • SystemInfo onError defaults to fail-fast throw; removed SSR deviceId guard so onError='throw' actually takes effect
    • createError switched to error.data: { source, title, cause } for structured diagnostic payload, no more string-concatenation in message
    • SSR with empty apiBase fails fast with a clear error instead of silently skipping
    • SSR-side deviceId uses fixed value 'node' (per backend convention), decoupled from client logic
    • SSR error.vue rendering no longer triggers background systemInfo fetch on client + title contains full API path
    • Overall: SSR failures surface immediately rather than accumulating silent damage
  • ⚠️ SwButton API redesign (v0.12.0 BC, refs framework#56 / #57)
    • Since v0.6.0, SwButton used onTap callback prop + 100ms built-in throttling + actively filtered onClick — against vue idiom + against industry norms (el-button / antd / mui / reka-ui all don't do built-in throttling, all emit @click)
    • Removed onTap callback prop; use standard @click event: <SwButton :on-tap="fn"> → <SwButton @click="fn">
    • Removed wait prop + 100ms built-in throttling; preventing double-submit during async is now the parent's responsibility via :loading state
    • Removed inheritAttrs:false; vue-standard pass-through, all class / style / aria-* flow to native <button> automatically
    • Added nativeType: 'button'|'submit'|'reset' (required for form submit) / autofocus / icon (left-side icon Vue component, replaced by spinner during loading)
    • Added default loading spinner: :loading="true" without #loading slot auto-renders (~10 lines CSS, 1em + currentColor auto-adapts to button font-size and color)
    • Backed by standards mr-review SB-001 (@click bound to async handler must pair with :loading) + SB-002 (form submit must pass native-type="submit")
  • 🚀 Test infrastructure (v0.12.7 ~ v0.12.12 series)
    • Integrated vitest coverage (v8 provider + json-summary reporter)
    • Rewrote SwButton e2e covering disabled / loading / type / icon main props
    • utils/debug-log.ts unit test coverage 0 → 89.47%
    • framework e2e gotoPage helper bypasses @nuxt/test-utils createPage hydration timing issues
  • 🔧 Release flow + engineering infrastructure (v0.12.0+)
    • release script split three ways: release:patch / release:minor / release:major; bare release errors out forcing explicit version selection (lesson from v0.12.0 BC accidentally released as patch)
    • release script explicitly passes --tag-version-prefix=fw/v to work around pnpm not reading .npmrc
    • Locked packageManager pnpm@11.0.8
    • playground devServer port 3500 → 3000 (monorepo port unification) + host env-aware
  • 🐛 Business component small fixes (v0.12.x patches)
    • SwQrcodeScanner: added decode timeout fallback, fixed real-user bug where non-QR images stayed in loading forever
    • upload-chunk: empty files (size <= 0) were silently skipped with no callback, locking consumer pending state (refs framework#60)
    • SwImage URL.createObjectURL memory leak (refs framework#61, included in SwImage refactor)
    • SwTiptapEditor: template inline as type cast failed rollup parsing in downstream appliers → moved to script setup computed
  • ⚠️ framework takes over head elements (v0.10.0 / v0.11.0 BC, refs framework#51)
    • <html> htmlAttrs owned by framework (v0.11.0): lang single-sources from framework.i18n.defaultLocale; data-build-id / data-build-time read in priority order CI_COMMIT_SHORT_SHA → VERCEL_GIT_COMMIT_SHA → GITHUB_SHA → 'local'
    • i18n plugin auto-injects <html lang> (refs framework#51), replacing starter's hard-coded nuxt.config.app.head.htmlAttrs.lang
    • <title> titleTemplate owned by framework (v0.10.0): default '%s | %site_title', %site_title replaced at runtime with backend system.info.header.site_title
    • useSeo no longer falls back to site_title for title, avoiding the homepage siteTitle | siteTitle duplication bug from titleTemplate stacking
    • useSeo({ disableTitleTemplate: true }) opts out per-page (for landing pages / error pages / full-screen features)
    • New eslint rule swift-web/require-useSeo-in-pages: app/pages/**/*.vue must explicitly call useSeo, blocks AI / developer omissions
    • Appliers can remove the entire app.head.htmlAttrs block and top-level buildId / buildTime constants
  • ⚠️ Cookie / auth state / device-auth security hardening (v0.9.0 BC, refs framework#48 / #45)
    • AccountInfo moved from cookie to localStorage (encrypted); password no longer travels with every HTTP request, no longer visible in DevTools Cookies panel
    • 🔴 Fixed "Remember me" bypass privacy bug: from 0.8.x, framework loginByUsername / loginByQrcode unconditionally called setAccount, writing password to cookie for 30 days regardless of "Remember me" intent; 0.9.0 honors user intent via options.remember
    • AccountInfo interface adds required remember: boolean field; loginByUsername / loginByQrcode signature extra?: Record → options?: { remember?, extra? }, remember defaults to true for backward compatibility
    • Core cookies (token / deviceId / channel_code / share_code) are AES-CBC encrypted + secure: true by default (localhost / 127.0.0.1 browser-exempted)
    • New userStore.clearAccount() action (clears AccountInfo alone, leaves tokenInfo) + useTheme.setTheme(theme) action
    • useCookie wrapper extended to support maxAge / sameSite / secure / path / domain / default / watch; on decrypt failure auto-fallbacks to default (compatible with legacy plaintext cookies)
    • New eslint rule no-direct-cookie-in-app blocks direct read/write of sensitive cookies under app/ at compile time
  • 🚀 ISR security hardening + path-aware lint plugin (v0.8.0 BC, refs framework#44)
    • Systematic refactor against user-state leaks on ISR routes (device tokens baked into Pinia payload, etc.)
    • Device + auth identity moved entirely from SSR to post-hydrate client; new auth.client.ts plugin is the single entry point
    • system.server.ts simplified: removed SSR-side userStore.login() + built-in IGNORE_PATHS, kept systemInfo fetch + initCode
    • useAuthReady composable lets business request() transparently wait for auth.client
    • framework module.ts auto-injects experimental.inlineRouteRules: true so page-level defineRouteRules actually works
    • New @swift-web/framework/eslint subpath export with 3 rules: require-define-route-rules / no-isr-on-paid-paths / no-isr-on-user-state-paths
    • 0.8.1 path-pattern switched to segment-level matching covering nested routes; 0.8.2 rule options support custom segments; 0.8.3 ModuleOptions.auth widened to boolean | AuthOptions, auth: false disables the entire client auth init
    • BC: systemInfoIgnorePaths: string[] removed, replaced with systemInfo: boolean | { enabled?, endpoint?, ignorePaths? }; migration ['/'] → false, ['/p'] → { ignorePaths: ['/p'] }
  • 🔄 SwImage / SwAsyncText refactor (v0.7.x ~ v0.8.x BC, refs framework#34 / #35 / #61)
    • Removed @nuxt/image dependency; introduced external-image provider abstraction
    • SwDecodeImage deleted; its encryption capability merged into SwImage (cleaned up business-specific .cursor-scale / .cursor-scale-av styles)
    • SwDecodeText renamed to SwAsyncText and switched to useDecryptedAsset composable (depends on #34)
    • Fixed URL.createObjectURL not paired with revokeObjectURL, causing memory leaks in encrypted image list (#61)
    • SwImage capability expansion: wrapper-level click event (covers overlay), title prop (hover tooltip / SEO), placeholder extended to string | { dark, light } | false, #placeholder slot, @load / @error forwarding, hover-zoom class
  • 🚀 PWA SW config switched to industry mainstream (v0.7.7, refs framework#43)
    • skipWaiting: true (new SW activates immediately, no waiting for all tabs to close)
    • clientsClaim: true (new SW takes over all clients)
    • Image runtime caching switched to StaleWhileRevalidate (return cache + update in background)
    • Aligned with Workbox recommendations + el-ui / antd-pro PWA defaults
  • 🚀 useSeo length thresholds use display-width algorithm (v0.7.5, refs framework#39)
    • Original validateSeoLengths used JS .length (UTF-16 code units) treating ASCII / CJK equally
    • But the 50-160 character thresholds follow Google SERP English character-width standards; CJK visual width is roughly 2× English
    • Introduced displayWidth function (CJK Unified Ideographs / Extension A-H / full-width symbols / CJK punctuation count as 2, ASCII as 1)
    • title / description use displayWidth; keywords checks count and total length, still uses .length
    • Threshold numbers (10/60/70 / 50/160/200) unchanged, unit semantics upgraded to "display width"
  • 🔄 codegen extracted into framework (v0.7.0)
    • Moved starter's optimize-images / generate-icons / check-og-image build scripts into @swift-web/framework/codegen submodule
    • Downstream scripts/*.ts collapsed to one-line thin wrappers (import + call)
    • Core logic upgrades via pnpm update @swift-web/framework; appliers no longer "copy starter scripts to their own project"
    • framework provides pure functions + callback interfaces (e.g. generateIcons's onFaviconConflict callback); terminal interactions stay in the caller
    • starter migrated five scripts

starter

  • ⚠️ Adapt to framework#89 SEO single entry (v0.14.2)
    • nuxt.config.ts switched to Nuxt-native app.head.titleTemplate: \%s | ${swiftConfig.project.title}`+runtimeConfig.public.siteName(site name sourced fromswift.config.json's project.title`, single source of truth)
    • Also carries the starter#24 fix: .npmignore excludes the publish-only template-integrity.spec.ts to prevent leaking into sw create-derived appliers
  • 🚀 Initial npm release (v0.14.0)
    • starter was previously a git-only template (cli used SSH git clone to fetch); not published to npm
    • From v0.14.0 it ships as an npm package for cli to fetch via npm pack (pairs with cli 0.5.0)
    • prepack / postpack hooks process the publish manifest: reset version to 0.0.0, set private:true, remove publishConfig / release scripts
    • Ensures the published package is a "new project" template, not a business instance
  • 🔧 publishConfig switched to verdaccio + downstream deps bumped
    • Bumped framework ^0.14.0 + standards ^0.1.1
    • .npmrc switched to verdaccio
    • Locked Vue stack to single version, fixed production build SSR 500
    • Patched vue-router 5.0.7 to fix @nuxt/test-utils TypeError
  • 🔧 Added pnpm 11+ preinstall guard
    • check-pnpm.mjs validates process.env.npm_config_user_agent during preinstall
    • pnpm < 11 / npm / yarn exits 1 immediately with Chinese guidance
  • 🔧 systemInfo onError changed to throw + nuxt-config adapted for fw 0.13
    • Aligned with framework fail-fast strategy
    • Enabled experimental.inlineRouteRules for fw v0.13.0 hard validation
    • Bumped framework 0.13.0
  • 🚀 error.vue dev details structured (pairs with fw v0.12.9-0.12.11)
    • Dev-mode error.vue shows error.message details
    • Switched to error.data structured display instead of message string splitting
    • Detail title turned red (no leading dot)
  • 🚀 LoginForm reuses framework account storage (pairs with fw v0.9.0)
    • Removed self-maintained useLocal('login-username' / 'login-password' / 'login-remember') triple
    • Uses framework userStore.setAccount / getAccountInfo / clearAccount
    • Passes "Remember me" intent via loginByUsername(u, p, { remember: isRemember.value }) for framework to honor
  • 🚀 Home page rewrite + visual components (v0.7-0.8 era)
    • Rewrote homepage + icons demo page
    • Added LogoMark / LangSwitch / ThemeSwitch business components
    • Lightning LOGO regenerated icons

standards

  • 🚀 Initial release v0.0.1 → v0.1.3 (latest wave)
    • v0.1.1: switched publishConfig to verdaccio
    • v0.1.2: preinstall added cwd-in-node_modules guard (prevents accidental trigger during consumer install)
    • v0.1.3: prepublishOnly strips preinstall
  • 🔄 Directory restructure: common-standards/ → rules/ (marker protocol v1 → v2 BC)
    • Filenames lost number prefixes: 10-rendering-strategies.md → rendering-strategies.md
    • _order.json sections structure controls concat order (replacing filename-number based)
    • marker v2 adds version number to prevent rollback (v1 BEGIN/END wrapping → v2 with <!-- swift-web/standards@version -->)
    • Synced downstream literal references common-standards/ → rules/ in 4 repos
  • 🚀 component-review-rules system (progressive expansion)
    • New chapter organizing mr-review rules vertically by component name
    • First batch SwButton: SB-001 (@click bound to async handler must pair with :loading, prevents double-submit) + SB-002 (form submit must pass native-type="submit")
    • Progressively expanded to 18 rules covering common review misses for SwImage / SwModal / SwUploadImage / SwTiptapEditor etc.
    • Progressively mechanized into eslint rules under @swift-web/standards/eslint
  • 🚀 eslint rule set evolution (v1 → v4 across periods)
    • v4 (fw v0.12.x): SwButton SB-001/002 mechanization
    • v3 (fw v0.10.0): require-useSeo-in-pages
    • v2 (fw v0.9.0): no-direct-cookie-in-app
    • v1 (fw v0.8.0): require-define-route-rules / no-isr-on-paid-paths / no-isr-on-user-state-paths
    • Three-tier classification: must-fix (block PR) / important (discussion) / minor (review summary)
  • 🚀 Client-security master chapter (pairs with fw v0.9.0)
    • 5 review rules: cookie secure, no sensitive fields in cookie, remember propagation, no bypassing wrapper, encryption is not a security barrier
    • Client-side encryption security model documented: makes it explicit that "encryption ≠ security barrier" (cacheAesKey / apiKey exposed in client bundle); real security relies on HTTPS + backend auth
    • Tri-lingual /guide/client-encryption-boundary launched in sync
  • 🚀 ISR security mr-review checklist + discuss-rendering v3 (pairs with fw v0.8.0)
    • Rendering strategy §3 user-state leak (most serious risk) dedicated chapter
    • SWR downgraded to ISR alias (simplifies decision complexity)
    • Decision tree adds prerender (SSG) row
    • rendering-quick-guide practitioner-style guide
  • 🚀 standards repo launched + npm release (refs standards#1 / #3 / #4)
    • Extracted swift-web common conventions as single source of truth — rendering strategies / SSR safety / public-private API boundary / AI commit marking / client security rules no longer scattered across framework / starter / docs CLAUDE.md
    • Centralized in standards/rules/*.md, with _order.json explicitly controlling concat order
    • pnpm sync script injects rule sections into 4 downstream CLAUDE.md files via marker protocol
    • Published as npm package @swift-web/standards exposing /eslint subpath
    • Includes mechanized rules: navigate-to-must-return (Nuxt navigateTo can't be called bare) / root-class-sw-prefix (no class auto-exempt + multi-root independent judgment)
    • /prettier + /stylelint subpaths let each repo's config use import base + spread override pattern

cli

  • 🔧 Adapt to framework#89 (v0.5.5)
    • applyProjectVars removed the titleTemplate placeholder replacement (starter now reads a template literal from swift.config.json, so sw create-derived projects get their own site name natively, no string replacement needed)
    • add.test.ts store demo input changed to shopping-cart to avoid the old user store naming triggering a cross-repo string false-positive in pre-release-doc-check
  • 🚀 sw create switched to npm pack mode (v0.5.0, refs framework#79)
    • Previously sw create cloned @swift-web/starter via SSH git, requiring new machines to have GitLab SSH key + corresponding repo permissions
    • From v0.5.0 uses npm pack @swift-web/starter to fetch tgz from verdaccio and extract (pairs with starter first npm release, see starter section)
    • New machines have zero GitLab dependency; template always fetches latest, no version-lag concerns
    • applyProjectVars placeholder replacement for package.json / nuxt.config.ts / README.md unchanged
  • 🔧 publishConfig switched to verdaccio + install scripts removed AUTH section (v0.4.0 / v0.5.0)
    • cli's own releases switched to verdaccio
    • install.sh / install.ps1 removed NPM_TOKEN AUTH section (verdaccio access:$all allows anonymous install)
  • 🐛 sw info adds errorHandler row + sw run exit(1) on failure (v0.3.x)
    • sw info output now shows errorHandler config row
    • sw run subprocess failure correctly propagates exit code
  • 🐛 sw add page default rendering strategy changed to ISR (v0.3.x)
    • Default ssr: true → isr: 3600, aligning with swift-web "ISR is the default" design intent
    • Performance first, full SSR only when necessary
  • 🔧 release script split three ways + bare release errors (v0.3.1+)
    • Aligned with framework: release:patch / release:minor / release:major; bare pnpm release errors with three-option prompt
    • Prevents BC changes from being released as patch by hitting enter (lesson from framework v0.12.0; cli / standards aligned)
    • release script explicitly passes --tag-version-prefix=cli/v
  • 🔄 Command-layer output unified through core/output (v0.3.1)
    • Previously command files scattered direct console.log / console.error calls
    • Unified to output.success / output.error / output.warn / output.info / output.hint
    • Enables interactive / text / json tri-mode output via --text / --json
    • setNestedValue made generic
  • 🚀 Integrated ESLint flat config (v0.3.x)
    • Unified lint setup with framework / starter / docs
    • Integrated @swift-web/standards/eslint
  • ⚠️ configKey renamed (v0.3.0 BC)
    • swiftWebFramework → swiftConfig, shorter config key
    • Comments + test fixtures synced
    • Project template nuxt.config.ts / swift.config.json migration synced
  • ⚠️ Removed sw init-mcp command (v0.3.0 BC)
    • MCP config now follows doc-guided manual .mcp.json writing, no longer generated by cli command
    • Paired with sw_framework_docs MCP tool provided one-stop inside cli

docs

  • 🔧 publishConfig + .npmrc switched to verdaccio
    • Bumped downstream deps framework ^0.14.0 / standards ^0.1.1
  • 🔧 Added pnpm 11+ preinstall guard
    • Same check-pnpm.mjs as starter
  • 🔧 Docs synced with framework 0.10 / 0.11 / 0.12 / 0.13 BC
    • SwButton API redesign → seo / component docs full update
    • titleTemplate / htmlAttrs takeover → framework/seo new chapter
    • defineRouteRules hard validation / removed default error.vue → routing / error-handling rewritten
    • SystemInfo fail-fast / createError structured diagnostics → docs sync
    • 7 component docs synced with v0.12 API redesign
  • 🚀 Tri-lingual client-encryption-boundary added (pairs with fw v0.9.0 cookie security)
    • Makes "encryption ≠ security barrier" explicit
    • cacheAesKey / apiKey exposed in client bundle; real security relies on HTTPS + backend auth
    • Synced with standards client-security master chapter
  • 🚀 docs framework/module-options systemInfo migration (pairs with fw v0.8.0)
    • Replaced old systemInfoIgnorePaths section with new systemInfo schema docs + migration notes
    • systemInfo Store auto-init section updated to new API
    • Introduction example config updated from systemInfoIgnorePaths to systemInfo
  • 🚀 Tri-lingual rendering-quick-guide added (pairs with fw v0.8.0 ISR security)
    • 5-minute decision matrix + detail-page decision tree + anti-pattern cheat sheet + sample code + review checklist
    • Sits alongside the existing /guide/rendering-strategies (theory chapter)
    • Helps practitioner-style developers quickly decide ISR / SSR / CSR / SSG

2026-04

framework v0.4.x → v0.6.x · cli v0.2.1 → v0.2.7

SEO milestone release + large-scale component renaming BC + business component tri-state prop pattern unification.

framework

  • 🚀 auth security hardening (v0.6.5)
    • initCode channel-code URL injection guard: middleware rejects channel codes injected via URL on ?switch=1 paths, preventing malicious links from hijacking attribution
    • logout cleanup synchronized: clears channel-code cookie / IDB decryption cache / login-credential localStorage atomically
  • 🚀 useSeo dev experience polish (v0.6.1 ~ v0.6.4 series)
    • v0.6.4: SEO guide page migrated from framework internal wiki to public docs site
    • v0.6.3: console warning output compacted + link to SEO knowledge base wiki
    • v0.6.2: dev length warnings include page URL + new silenceLengthWarnings: true mute switch (for private / utility pages)
    • v0.6.1: JSON-LD switched to @unhead/schema-org/vue import path (resolves signature divergence with nuxt-schema-org)
  • 🚀 SEO milestone release (v0.6.0)
    • useSeo() composable: one call per page emits full og:* / twitter:* / canonical / JSON-LD stack
    • 4-layer origin fallback: explicit → systemStore.info.site_url → runtimeConfig.public.siteUrl → useRequestURL (fixes canonical resolving to internal IPs in multi-mirror + reverse-proxy setups)
    • 4-layer image fallback: explicit → header.og_image → header.img → /og-image.png
    • robots prop: 'private' (noindex,nofollow) / 'noindex' (noindex,follow) / arbitrary raw string
    • JSON-LD emits WebSite + WebPage schema by default; type:'article' adds Article schema
    • Mandatory @nuxtjs/sitemap + nuxt-schema-org + @nuxtjs/robots sub-modules (cannot be disabled; only aiBotPolicy option controls GPTBot/ClaudeBot/CCBot/PerplexityBot training crawlers)
    • SwImage adds preload prop to auto-inject <link rel="preload" as="image">, paired with loading="eager" + fetchpriority="high" for hero-image LCP optimization
    • Backend system.info.header.og_image field (CMS-uploaded 1200×630 PNG/JPG); legacy img field kept through 0.6.x, removed in 0.7.0
  • 🐛 Business component fixes (v0.5 / v0.6)
    • useLoginBy iOS Safari mis-detection as WebView: original /Version\/[\d.]+(?: Chrome\/)?/ regex made Chrome/ optional, so iOS Safari Version/X.X Mobile/... matched and was wrongly routed to redirect login; fixed to require literal <space>Chrome/ after Version/X.X
    • SwModal: description failed to render when default slot was also provided (aria-describedby warning); now always renders when description is set
    • useToast / useModal: crypto.randomUUID unavailable in non-HTTPS contexts (e.g. http://0.0.0.0); added Date.now + Math.random fallback
    • SwTiptapEditor: removed template-binding as Editor type assertion that caused vite-plugin-vue-tracer parse error
  • 🚀 SwImage capability expansion (c-image migration gaps, v0.5)
    • Wrapper-level click event (covers overlay region)
    • title prop (hover tooltip / SEO)
    • placeholder type extended to string | { dark, light } | false, object form uses CSS-only theme switching
    • placeholderClass prop + built-in minimal placeholder fallback styles
    • #placeholder slot (slot takes precedence over prop); @load / @error event forwarding
    • hover-zoom class (opt-in) + CSS variable --sw-image-hover-zoom-scale
  • 🚀 New components (v0.5)
    • SwAddToHomescreenGuide: H5 PWA add-to-home-screen prompt, slides up as non-blocking bottom-sheet; iOS Safari / Android Chromium get tailored steps; desktop / WebView / regional browsers stay silent; rememberFor prop supports 'session' / positive N (suppress for N days); CSS variables --sw-add-to-homescreen-guide-bg / --sw-add-to-homescreen-guide-radius / --sw-add-to-homescreen-guide-z for theming
    • SwQrcodeScanner: image-based QR code scanner with trigger / preview slots and full event stream
    • SwQrcodeGenerator: QR code generator with configurable size, error correction, foreground/background colour, and PNG download
    • SwFormField simple mode: name is now optional; new required / hint / error props let the component work as a form field layout wrapper without vee-validate
  • 🚀 Business component "tri-state prop" pattern unified (v0.5)
    • onRecord / fetchPlayUrl and similar props support three semantics
    • undefined → use default API (SwVideoPlayer default POST /movie/doHistory, SwAudioPlayer default POST /audio/doHistory, SwVideoPreview default POST /movie/play)
    • function → override default behavior
    • null → explicitly disable
    • Business code works zero-config; pass function to override, or null to explicitly disable
  • ⚠️ Large-scale component renaming + API redesign (v0.5 BC)
    • SwBtn → SwButton: CSS class sw-btn → sw-button; added type semantic prop + loading prop + #loading / #icon / #icon-right slots
    • SwAlink → SwLink: sw-alink → sw-link; security fix target="_blank" auto-adds rel="noopener noreferrer", external links additionally get nofollow
    • SwPlayer → SwVideoPlayer: rename only, API unchanged
    • SwPlayerAudio → SwAudioPlayer: rename + fully rewritten to align with SwVideoPlayer. Added url / type / linkCode v-model / initialSeekTime (replaces seekTime) / 14 emit / 6 expose (incl. switchLink preserving progress); removed playMode; fixed "switch line N times → N timeupdate listeners stacked"
    • SwPlayerPreview → SwVideoPreview: rename + capability expansion. fetchPlayUrl tri-state defaults to /movie/play; new url direct passing skips fetch + mp4/webm support + reload() for URL expiry
    • AudioRecordParams type change: added event / view_time / code fields (aligned with VideoRecordParams); custom onRecord needs to update type signature
  • 🔧 reset.css / theme.css enhancement (v0.5)
    • Added global scrollbar-width: thin; all consumer projects get thin native scrollbars out of the box
    • Both dark-mode selectors gain color-scheme: dark; browser scrollbar tracks and system UI track the theme automatically
    • dexie / qrcode auto-injected into Vite optimizeDeps.include; consumer projects no longer need manual config under pnpm strict mode

starter

  • 🚀 Full migration from useSeoMeta to useSeo() (pairs with fw v0.6.0)
    • 6 starter pages migrated to framework-owned useSeo()
    • local-seo/require-seo-meta ESLint rule enforces useSeo on every page
    • Private pages (profile / login / register / mine etc., 4 pages) set robots: 'private' / 'noindex'
  • 🚀 public/og-image.png placeholder + prebuild check-og-image (pairs with fw v0.6.0)
    • 1200×630 PNG placeholder placed in public/
    • scripts/check-og-image.ts verifies during prebuild; build fails when missing
    • Prevents social share cards from breaking after appliers accidentally delete og-image.png
  • 🚀 @phosphor-icons/vue (v0.5 era)
    • 1,300+ icons in 6 weights as a standard icon library, added as direct dependency
    • Replaces previous dependency on framework's built-in icon scheme
  • 🚀 modal.css default visual styles (v0.5 era)
    • SwModal default visuals (overlay, centred card, buttons, enter animation), light / dark auto-adaptation
    • Customize via --sw-modal-* CSS variables in custom.css
  • 🔧 CSS rem unit enforcement (pairs with fw v0.4)
    • stylelint declaration-property-unit-allowed-list enforces rem for layout dimensions (width / height / padding / margin)
    • Scales with browser font size for accessibility
    • border-* and positioning properties retain px
    • Whole project migrated from hard-coded px to rem unit system
  • 🔧 auth middleware enhancement (pairs with fw v0.4)
    • guest.global middleware supports ?switch=1, allows signed-in users to reach login page for account switching
    • logout clears credentials automatically
    • Guest middleware matching switched to startsWith, fixes sub-paths not being correctly intercepted

cli

  • 🐛 sw upgrade private-registry auth fix (v0.2.7)
    • Original sw upgrade used npm view @swift-web/framework version without passing ~/.npmrc _authToken, hitting 401
    • Fixed to correctly pass token, can now fetch latest framework version from private registry
  • 🚀 MCP tool sw_framework_docs (v0.2.6)
    • Dynamically queries framework component / composable list
    • Lets AI tools like Claude Code stop referencing stale component names from memory (e.g. avoiding "SwBtn renamed to SwButton but AI still generates SwBtn")
    • Pairs with sw init-mcp to generate .mcp.json (later v0.3.0 removed init-mcp, replaced with doc guidance)
  • 🚀 i18n-applier symmetric + scanBusinessI18nUsage (v0.2.4 / v0.2.5)
    • enable / disable flows are now symmetric, aligned with PWA / Tailwind idempotence
    • New scanBusinessI18nUsage module scans business code for residual i18n API usage
    • Trimmed enable reports; lists disable residues
  • 🚀 sw config sync command (v0.2.3)
    • Re-runs all feature-flag appliers (i18n / pwa / tailwind etc.) to force-sync swift.config.json with nuxt.config.ts
    • Used for: syncing project files after manual swift.config.json edits / pulling new switch defaults after cli upgrade
  • 🚀 Tailwind v4 applier (v0.2.1)
    • sw init interactive flow can enable Tailwind v4 in one step
    • Auto-injects @import "tailwindcss" + necessary vite plugins + tailwind.css + stylelint whitelist
    • Six-step injection is idempotent and rerunnable; auto-rollback on disable
  • 🚀 swift.config.json slimmed (v0.2.1)
    • Removes obsolete fields, aligned with latest ModuleOptions shape
    • sw add page template adds useSeo placeholder aligned with framework's mandatory SEO
  • 🚀 @swift-web scope migrated to group registry (across v0.2.x)
    • From project-level (432 / 435 for framework / cli) to group-level (group 754)
    • Single token grants access to all packages; install scripts (install.sh / install.ps1) updated accordingly
    • Simplified token management (previously one deploy token per package)

docs

  • 🔧 Sidebar adjusted
    • Component doc pages synced with v0.5 component renaming (SwBtn → SwButton etc.)
    • Added "Rendering Mode Quick Guide" entry (links to upcoming rendering-quick-guide)
  • 🚀 6 zh-CN markdown frontmatter expanded
    • Aligned with new useSeo length thresholds (CJK 25-80 = English 50-160)
    • use-add-to-homescreen / use-login-by / quick-start zh-CN / zh-TW × 3
    • SEO length recommendation table adds "display width" explanation + zh-en comparison
  • 🚀 24-page useSeo() migration (pairs with fw v0.6.0)
    • docs itself follows starter
    • en pages use silenceLengthWarnings: true (translated from zh-CN, length uncontrollable, length warnings have no meaning for them)
  • 🚀 Tri-lingual framework/seo.md synced (pairs with fw v0.6.0)
    • Complete useSeo() API docs, origin / image / robots / JSON-LD detailed explanation, SEO mandatory mechanism
    • /guide/seo Appendix A provides MR review self-check list + eslint rule name mapping

2026-03

5-repo initial release month.

framework

  • 🚀 Initial release v0.1.0
    • 20 unstyled core components: SwButton / SwImage / SwModal / SwToast / SwUploadImage / SwPlayer / SwQrcodeGenerator etc.
    • 8 composables: useModal / useToast / useTheme / useUploader / useLoginCheck / useLoginDialog / useLoginBy / useUserActive
    • 2 global stores: userInfo (user info + token management) / systemInfo (system config)
    • Theme system: light / dark / auto, localStorage persistence
    • Route guard auth system: guardedPaths / guestPaths config
    • Global error monitoring: captures and reports Vue runtime errors
    • Ecosystem integrations: @nuxt/image / @vite-pwa/nuxt / @nuxtjs/i18n

starter

  • 🚀 Initial release
    • Nuxt 4 project structure + ESLint + strict TypeScript
    • Built-in CI/CD (Dockerfile + GitLab CI pipeline)
    • SEO infrastructure (useSeoMeta() / sitemap / OG image generation)
    • Serves as swift-web's sole downstream business template source

cli

  • 🚀 Initial release v0.1.0
    • sw create <project-name> — bootstrap a complete Nuxt project from the starter template
    • sw config — interactive manager for swiftWebFramework config (enable / disable sub-modules)
    • sw add <type> — code generator for pages / components / stores etc.
    • --text mode — plain-text fallback for CI environments without color support

docs

  • 🚀 Initial release
    • Official docs site built on Nuxt Content
    • Custom docs layout + navigation (DocsNavSidebar / DocsToc / DocsPageHeader / DemoBox / ApiTable purpose-built components)
    • Props / Events / Slots reference pages and interactive Demos for all 20 Sw* components
    • useModal / useToast / useTheme composable reference pages
    • Framework config reference page (Module Options)
    • Light / dark / system theme, PWA offline support
    • Tri-lingual switching (Simplified Chinese / Traditional Chinese / English)