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/Ndoralways(ISR TTL remains in seconds). Upload props change fromisSmalltothumbnail,retryNumtoretries, andthreadNumtoconcurrency; the back-to-top layer token becomes--sw-back-top-z - ⚠️ Explicit ISR TTL bounds —
isr: trueis rejected; declared TTLs must be integer seconds from 60 to 3600. Outgoings-maxageis capped at 60 seconds to limit stale content across cache layers (#394 / #414) - 🚀 Media structured data —
useSeoaddsvideo/media/itemListfor VideoObject, Movie / TVSeries / TVEpisode, and ItemList, with video sitemap support; consumers supply real content data (#381) - ⚠️ Unified request configuration —
request.transportsupports base64 / binary.nitroApiFetchreads runtimeConfig and globalrequest.headersdirectly, retiring the flatswTimeout/apiTransportkeys and application bridge plugin; plaintext business failures retain their actualerrorCode(#383 / #391 / #392 / #409) - ⚠️ API generation boundaries — Source configuration moves to
swiftConfig.apiDocsinnuxt.config.ts, with credentials inAPI_DOCS_TOKEN. OpenAPI prefers tags for grouping; generated files carry@generated, while handwritten APIs live in automatically registeredapp/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.deviceLoginindependently disables device login, whileauth.enabledgates automatic login paths. Relogin separates identity cookies from user profiles, and client system info is reused forclientRefreshTtl(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
useExternalRedirectfor safe external redirect logic, SwVideoPreviewsettledfor terminal outcomes, anduseHoverPreview.closeOnScrollfor 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 carryX-Site-Id; proxy routes follow the project'sAPI_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/Refsand issue closeout semantics (#70 / #73 / #78)
cli
- ⚠️ Handwritten API command —
sw add apigenerates top-level function modules inapp/api/manual/;sw_project_apisrecognizes 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.tsmay override JSON settings
docs
- 🚀 Expanded reference guides — Adds the trilingual
useExternalRedirectreference 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
~/apiauto-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 fromdev(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
- ⚠️
SwVideoPreviewnow stops after 60 seconds of playback, andloopdefaults tofalse(#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.
AddsmaxDuration(default 60,0= uncapped), measured as cumulative playback time (pauses and buffering don't count); on reaching it the component callshls.stopLoad()+pause()(keeping the last frame) and emitscapped. 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%.loopflipped tofalsein 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: nullwithout aurl/ emptyid/ fetch returning no usable address) previously emittedloadingand 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 newemptyevent (noterror— "there simply is no preview source" is a valid business state).
Migration: passloopexplicitly if you need looping andmaxDurationif you need longer; applier-side workarounds like "unmount the player withv-ifafter N seconds" and "force-clear loading on a timeout" can be deleted wholesale - 🐛
useHoverPreviewnow 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 nomouseleaveafter 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 prefersPointerEvent.pointerType(which input started this interaction) and only falls back tomatchMedia, withallowTouchas 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 reportstrue(finger taps still get through) while a touch-first 2-in-1 with a mouse reportsfalse(mouse hover gets killed, worse than the original bug). Callers should move from@mouseenter="show"to@pointerenter="show"to get the precise check - ⚠️
--container-paddinggains 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, makingmax-widtha 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-widthdecides "where the container stops growing" (relevant only on 2K/4K displays);--container-paddingdecides "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-widthand--container-paddingtogether — 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'soverrides.css. - ⚠️
--content-max-widthdefault 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.container1320 and Tailwindmax-w-screen-2xl1536), 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 writes120rem, 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-paddingremains); 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 ownmax-width(all 8 static pages in starter use44rem), independent of the site container.
Migration: sites that want the old width set--content-max-width: 1200pxback in their ownoverrides.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: falsecannot 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 byssr, not a blanket error:ssr: falseis 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 eslintno-query-in-cacheable-ssrat the same time (that rule only fires when the file declares isr/swr/cache/prerender, while this one declaresisr: 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/proxyrules 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/homeredirect). ⚠️ 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 staticrouteRulesinnuxt.config— that does not see the globs that page-leveldefineRouteRulesexpands 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) —
errorHandlergains atoastswitch: whether an error shows a user-visible toast. Defaults tofalse= 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_URLthe 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: theisr→cachetranslation now always injectsvaries: ['host', 'x-forwarded-proto']so cached pages getHostback. 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 everyserver_name, havedefault_serverreturn 444). Relatedly, "NUXT_PUBLIC_SITE_URLnot set" went from a warning to a normal state — it is only required when the project hasprerenderpages - ⚠️ 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.pwanarrowed to a pure boolean, manifest content moved intoswift.config.json(#359, 0.125.0);SwAddToHomescreenGuideadapted 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-shapepublish-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, andth:hovervisual selectors must targetbutton.th-inner); SwTable warns in dev when@row-clickis 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 interceptionblockHijackingBrowsers(UC / Quark / QQ / Baidu blocked with a prompt by default, WeChat deliberately not blocked, customizable via the#hijacking-browserslot) - 🚀 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
defineRouteRulesconflicting with configrouteRuleson 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/loadingprops and@searchevent; 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 fromprerender: truetoisr: true, andNUXT_PUBLIC_SITE_URLbecame optional. The reason is that prerender runs at build time with no real request and therefore noHost, 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.ymldown 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 addedci-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 pnpmis 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 withpnpm: 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 syncself-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,--checkhas exit-code semantics - 🔄 Three-layer rule architecture (#43) — downstream convention files split from a single monolith into three layers: an always-loaded
AGENTS.mdindex (P0 red lines + selection table + detail channels) + on-demand.claude/skills/sw-*details + the standardsrules/source of truth; sync target families extended accordingly (agents / skills / thin shells), with Codex / Copilot sharing the same content via the.agents/skillsmirror - 🚀 Lint enforcement batch — prettier base flipped to
semi: true+singleAttributePerLine(0.11.0, ecosystem-wide reformat); new stylelint ruleshover-media-gate(:hovermust be wrapped in@media (hover: hover), paired with the touch hover-stickiness fix) andcomponent-token-prefix; eslintroot-class-sw-prefixupgraded to v3 (the root class name must match the component name)
cli
- 🐛
sw config setrejected legitimate object configs (#46, 0.16.5) —framework.pwaandframework.errorHandlerare typedboolean | XxxOptionson 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 readgetNestedValue(...) === true⇒ any object evaluates to false ⇒disablePwa()runs. The config said PWA was enabled while the dependency was removed frompackage.json, with the notice buried in the success output. Fixed by mirroring framework's ownresolvePwanormalization (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 thebinpath (0.16.7) — silences the"bin[sw]" script name ... was invalid and removedwarning 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 upgradegained--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); theframework.pwaconfig shape narrowed to a pure boolean alongside framework#359,validatePwaShaperemoved
docs
- 🐛
/composables/use-hover-previewhad 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 localpnpm devworked 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/mainas the default branch and the long-liveddev/vNbranches were fully retired; onlydev/main(integration / testing) andmain(production) remain long-lived, everything else is a short-lived branch deleted on merge. A production release is adev/main → mainrelease MR: mr-review posts a "🚀 release summary" for release MRs (D5) and stops auto-merging MRs that touch risk paths likeserver// CI / dependencies (D6, human merges); the starter CI gained arelease-guardadvisory 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
isrhas 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 losingHostso 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.modeknob (system / toggle / dark / light) + color-scheme decoupling +resolvedTheme; built-inSwThemeSwitch(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-brandtokens; 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;SwTableColumnetc. 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 controlledrefreshingprop + a#refresh-indicatorslot for brand indicators (floating, never shifts content, touch only) - 🐛 useSeo lint now covers error.vue (#240, 0.74.2) —
require-useSeo-in-pagespreviously only matchedapp/pages/**, contradicting the documented scope; withisErrorPageFilethe 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-apimachine-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 --checkdrift 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); newSwInputcomponent (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;falsedisables 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;
deltaModenormalization added (one Firefox mouse-wheel notch now flips one item) - 🚀 Tracking capacity trio (#249 / #250 / #251, 0.81.0) —
trackevent 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 cumulativewatch_time; ad-close tracking viauseAdTrack.reportClose(withclose_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
computePreviewBoxpositioning 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-i18nbuilt-in dictionary plususeSwText; all built-in copy andaria-labels across 19 components now go through it (previously hardcoded Chinese, which showed through on non-Chinese sites) - 🚀
@swift-web/framework/storesubpath 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
swrand does not consumeisr(only the vercel / netlify / zeabur presets recognize it), so writingisron a self-hosted deployment meant zero caching and a full SSR per request. It is now translated at build time into an equivalentcachekey 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 writingisr - 🚀 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 (
fscache driver × parent and child path both cacheable ⇒ ENOTDIR, 0.93.0) / #276 (a path with ≥2 dynamic segments makes Nuxt drop the page'sdefineRouteRulesentirely, 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) andno-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
localhostas their canonical (#278, 0.95.0) — before entering the handler,cachedEventHandlerkeeps only the request headers listed incache.varies;Hostis dropped, souseRequestURL().originfalls back tohttp://localhostand 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 appliedmin-height: 44pxto everya/buttonand 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
autoplaynow 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/useSessiongenerics no longer collapse tounknown
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.jsonchecked 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
MovieHoverPreviewoverlay and apreviewprop onMovieCard/MovieFloor(on by default); the mechanism later moved up into framework and starter deleted its local copy in favor ofuseHoverPreview/computePreviewBox - 🔄 Audit fix batch (0.31.0) — deploy no longer clobbers the hand-written
.envon 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 toonMountedbackfill 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]].vueplus boundary validation, along with anuxt.configrouteRules fallback (Nuxt silently dropped that page'sdefineRouteRules— 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 pagetemplate 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 confignow 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) - 🔄
--textmode delivers on its plain-text promise (0.16.2) — ANSI escapes are now stripped uniformly; also fixed cross-repo drift inKNOWN_DESCRIPTIONSand alignedsw info's framework-version read withversion-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
/Pmatches/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/mdcunder counter-intuitive rules (dots are swallowed, consecutive-collapse into one, a leading digit gets a_prefix); a newdocs-anchors.spec.tsnow 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
isractually 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/designpipeline (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-buildreads component-card CSS straight from each.vuefile'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 cardsSwAdText/SwAdMarquee/SwAdNative, popup / floatingSwAdSplash/SwAdPopup/SwAdFloating, player slotsSwAdPreroll(pre-roll, play_ads protocol adapter) /SwAdPause(pause ad), compliance badgeSwAdLabel— 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) :trackbuilt-in component tracking (SwLink / SwButton / SwCarousel / players, #123) + the genericuseExposurecomposable
- 12 self-rendering ad components landed over the month: image
- 🚀 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 publicloginByDevice();useLoginDialogshow → open;SwAdSide→SwAdFloatingrename; ad popups split by scenario intoSwAdSplash/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 toNUXT_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 toSwAdSplash(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.clientchain: the old version overwrote the page's useSeo title/description with backend header values viauseSeoMetaafter hydration, causing<title>to duplicate intoSiteName | SiteNameand description to be overwritten - SEO is now fully explicit, single entry: useSeo no longer falls back to backend
systemStore.headerfor title/description/keywords/og_image (missing values become devconsole.warn, no silent fallback) - titleTemplate handed back to Nuxt native: removed
ModuleOptions.head.titleTemplate+useSeo'sdisableTitleTemplateinput; appliers configure the site-name suffix viaapp.head.titleTemplateinnuxt.config.ts(sourced fromswift.config.json'sproject.title) - JSON-LD
WebSite.namereadsruntimeConfig.public.siteName, no longer backendsite_title; og:image falls back to local/og-image.pngonly - Upgrading appliers must: pass title/description explicitly per page + configure
app.head.titleTemplate+runtimeConfig.public.siteNameinnuxt.config.ts
- Removed the
- 🚀 sitemap.xml refinement + i18n strategy detection (refs framework#85, alongside #87)
- GAP1: sitemap
siteUrllocked toNUXT_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/ buildconsole.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/i18nstrategy ==='no_prefix'→ devconsole.warnguiding towardprefix*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 contradictsuseSeo({ 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
- GAP1: 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→ L3runtimeConfig.public.siteUrl→ L4useRequestURL().origin) → 1 source of truth + 1 explicit override - Main path:
useRequestURL().origin(SSR Node parsesHost/X-Forwarded-*headers; CSR useswindow.location.origin) - L1 explicit
input.siteUrlis 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">andwindow.location.origin. Mismatch = nginx forwarding misconfigured → devconsole.warn/ prodconsole.error+ fix guide + docs URL (nginx origin header forwarding) SystemInfo.site_urlis preserved as a backend field for business use (e.g. "Latest Address" jump link), no longer involved in SEO origin resolutionNUXT_PUBLIC_SITE_URLis 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)
- 4-tier fallback (L1 explicit → L2
- 🔧 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.preinstallfrom the publish manifest, preventing consumers from accidentally running pnpm check scripts during install - workspace config
@swift-web/standards: falseprevents consumers from running standards' preinstall
- framework switched to self-hosted verdaccio private registry (
- ⚠️ 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
defineRouteRulesbut 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
createErrornow throws directly instead of falling back to a default error page
- Appliers must explicitly enable
- 🐛 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 createErrorswitched toerror.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
- SystemInfo onError defaults to fail-fast throw; removed SSR deviceId guard so
- ⚠️ SwButton API redesign (v0.12.0 BC, refs framework#56 / #57)
- Since v0.6.0, SwButton used
onTapcallback prop + 100ms built-in throttling + actively filteredonClick— against vue idiom + against industry norms (el-button / antd / mui / reka-ui all don't do built-in throttling, all emit@click) - Removed
onTapcallback prop; use standard@clickevent:<SwButton :on-tap="fn">→<SwButton @click="fn"> - Removed
waitprop + 100ms built-in throttling; preventing double-submit during async is now the parent's responsibility via:loadingstate - Removed
inheritAttrs:false; vue-standard pass-through, allclass/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#loadingslot auto-renders (~10 lines CSS,1em+currentColorauto-adapts to button font-size and color) - Backed by standards mr-review SB-001 (
@clickbound to async handler must pair with:loading) + SB-002 (form submit must passnative-type="submit")
- Since v0.6.0, SwButton used
- 🚀 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-utilscreatePage hydration timing issues
- 🔧 Release flow + engineering infrastructure (v0.12.0+)
releasescript split three ways:release:patch/release:minor/release:major; barereleaseerrors out forcing explicit version selection (lesson from v0.12.0 BC accidentally released as patch)- release script explicitly passes
--tag-version-prefix=fw/vto 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.createObjectURLmemory leak (refs framework#61, included in SwImage refactor) - SwTiptapEditor: template inline
astype cast failed rollup parsing in downstream appliers → moved to script setupcomputed
- ⚠️ framework takes over head elements (v0.10.0 / v0.11.0 BC, refs framework#51)
<html>htmlAttrs owned by framework (v0.11.0):langsingle-sources fromframework.i18n.defaultLocale;data-build-id/data-build-timeread in priority orderCI_COMMIT_SHORT_SHA→VERCEL_GIT_COMMIT_SHA→GITHUB_SHA→'local'- i18n plugin auto-injects
<html lang>(refs framework#51), replacing starter's hard-codednuxt.config.app.head.htmlAttrs.lang <title>titleTemplate owned by framework (v0.10.0): default'%s | %site_title',%site_titlereplaced at runtime with backendsystem.info.header.site_titleuseSeono longer falls back tosite_titlefor title, avoiding the homepagesiteTitle | siteTitleduplication bug from titleTemplate stackinguseSeo({ 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/**/*.vuemust explicitly call useSeo, blocks AI / developer omissions - Appliers can remove the entire
app.head.htmlAttrsblock and top-levelbuildId/buildTimeconstants
- ⚠️ 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/loginByQrcodeunconditionally calledsetAccount, writing password to cookie for 30 days regardless of "Remember me" intent; 0.9.0 honors user intent viaoptions.remember AccountInfointerface adds requiredremember: booleanfield;loginByUsername/loginByQrcodesignatureextra?: Record→options?: { remember?, extra? },rememberdefaults totruefor backward compatibility- Core cookies (token / deviceId / channel_code / share_code) are AES-CBC encrypted +
secure: trueby default (localhost / 127.0.0.1 browser-exempted) - New
userStore.clearAccount()action (clears AccountInfo alone, leaves tokenInfo) +useTheme.setTheme(theme)action useCookiewrapper extended to supportmaxAge/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-appblocks direct read/write of sensitive cookies underapp/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.tsplugin is the single entry point system.server.tssimplified: removed SSR-sideuserStore.login()+ built-inIGNORE_PATHS, kept systemInfo fetch + initCodeuseAuthReadycomposable lets businessrequest()transparently wait for auth.client- framework
module.tsauto-injectsexperimental.inlineRouteRules: trueso page-leveldefineRouteRulesactually works - New
@swift-web/framework/eslintsubpath 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.authwidened toboolean | AuthOptions,auth: falsedisables the entire client auth init - BC:
systemInfoIgnorePaths: string[]removed, replaced withsystemInfo: 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/imagedependency; introduced external-image provider abstraction - SwDecodeImage deleted; its encryption capability merged into SwImage (cleaned up business-specific
.cursor-scale/.cursor-scale-avstyles) - 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
clickevent (covers overlay),titleprop (hover tooltip / SEO),placeholderextended tostring | { dark, light } | false,#placeholderslot,@load/@errorforwarding,hover-zoomclass
- Removed
- 🚀 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
validateSeoLengthsused 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
displayWidthfunction (CJK Unified Ideographs / Extension A-H / full-width symbols / CJK punctuation count as 2, ASCII as 1) title/descriptionuse displayWidth;keywordschecks count and total length, still uses.length- Threshold numbers (10/60/70 / 50/160/200) unchanged, unit semantics upgraded to "display width"
- Original
- 🔄 codegen extracted into framework (v0.7.0)
- Moved starter's
optimize-images/generate-icons/check-og-imagebuild scripts into@swift-web/framework/codegensubmodule - Downstream
scripts/*.tscollapsed 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'sonFaviconConflictcallback); terminal interactions stay in the caller - starter migrated five scripts
- Moved starter's
starter
- ⚠️ Adapt to framework#89 SEO single entry (v0.14.2)
nuxt.config.tsswitched to Nuxt-nativeapp.head.titleTemplate: \%s | ${swiftConfig.project.title}`+runtimeConfig.public.siteName(site name sourced fromswift.config.json'sproject.title`, single source of truth)- Also carries the starter#24 fix:
.npmignoreexcludes the publish-onlytemplate-integrity.spec.tsto prevent leaking intosw 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/postpackhooks 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 .npmrcswitched to verdaccio- Locked Vue stack to single version, fixed production build SSR 500
- Patched vue-router 5.0.7 to fix
@nuxt/test-utilsTypeError
- Bumped framework
- 🔧 Added pnpm 11+ preinstall guard
check-pnpm.mjsvalidatesprocess.env.npm_config_user_agentduring 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.inlineRouteRulesfor 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
- Removed self-maintained
- 🚀 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.jsonsections 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
- Filenames lost number prefixes:
- 🚀 component-review-rules system (progressive expansion)
- New chapter organizing mr-review rules vertically by component name
- First batch SwButton: SB-001 (
@clickbound to async handler must pair with:loading, prevents double-submit) + SB-002 (form submit must passnative-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/apiKeyexposed in client bundle); real security relies on HTTPS + backend auth - Tri-lingual
/guide/client-encryption-boundarylaunched 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.jsonexplicitly controlling concat order pnpm syncscript injects rule sections into 4 downstream CLAUDE.md files via marker protocol- Published as npm package
@swift-web/standardsexposing/eslintsubpath - 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+/stylelintsubpaths let each repo's config useimport base + spread overridepattern
cli
- 🔧 Adapt to framework#89 (v0.5.5)
applyProjectVarsremoved the titleTemplate placeholder replacement (starter now reads a template literal fromswift.config.json, sosw create-derived projects get their own site name natively, no string replacement needed)add.test.tsstore demo input changed toshopping-cartto 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 createcloned@swift-web/startervia SSH git, requiring new machines to have GitLab SSH key + corresponding repo permissions - From v0.5.0 uses
npm pack @swift-web/starterto 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
applyProjectVarsplaceholder replacement forpackage.json/nuxt.config.ts/README.mdunchanged
- Previously
- 🔧 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:$allallows 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
- Default
- 🔧 release script split three ways + bare release errors (v0.3.1+)
- Aligned with framework:
release:patch/release:minor/release:major; barepnpm releaseerrors 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
- Aligned with framework:
- 🔄 Command-layer output unified through core/output (v0.3.1)
- Previously command files scattered direct
console.log/console.errorcalls - 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
- Previously command files scattered direct
- 🚀 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.jsonmigration synced
- ⚠️ Removed
sw init-mcpcommand (v0.3.0 BC)- MCP config now follows doc-guided manual
.mcp.jsonwriting, no longer generated by cli command - Paired with
sw_framework_docsMCP tool provided one-stop inside cli
- MCP config now follows doc-guided manual
docs
- 🔧 publishConfig + .npmrc switched to verdaccio
- Bumped downstream deps framework
^0.14.0/ standards^0.1.1
- Bumped downstream deps framework
- 🔧 Added pnpm 11+ preinstall guard
- Same
check-pnpm.mjsas starter
- Same
- 🔧 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/apiKeyexposed 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
systemInfoIgnorePathssection with newsystemInfoschema docs + migration notes - systemInfo Store auto-init section updated to new API
- Introduction example config updated from
systemInfoIgnorePathstosystemInfo
- Replaced old
- 🚀 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)
initCodechannel-code URL injection guard: middleware rejects channel codes injected via URL on?switch=1paths, 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: truemute switch (for private / utility pages) - v0.6.1: JSON-LD switched to
@unhead/schema-org/vueimport path (resolves signature divergence with nuxt-schema-org)
- 🚀 SEO milestone release (v0.6.0)
useSeo()composable: one call per page emits fullog:*/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/robotssub-modules (cannot be disabled; onlyaiBotPolicyoption controls GPTBot/ClaudeBot/CCBot/PerplexityBot training crawlers) - SwImage adds
preloadprop to auto-inject<link rel="preload" as="image">, paired withloading="eager"+fetchpriority="high"for hero-image LCP optimization - Backend
system.info.header.og_imagefield (CMS-uploaded 1200×630 PNG/JPG); legacyimgfield 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 SafariVersion/X.X Mobile/...matched and was wrongly routed to redirect login; fixed to require literal<space>Chrome/after Version/X.X - SwModal:
descriptionfailed to render when default slot was also provided (aria-describedbywarning); now always renders whendescriptionis set - useToast / useModal:
crypto.randomUUIDunavailable in non-HTTPS contexts (e.g.http://0.0.0.0); addedDate.now + Math.randomfallback - SwTiptapEditor: removed template-binding
as Editortype assertion that causedvite-plugin-vue-tracerparse error
- useLoginBy iOS Safari mis-detection as WebView: original
- 🚀 SwImage capability expansion (c-image migration gaps, v0.5)
- Wrapper-level
clickevent (covers overlay region) titleprop (hover tooltip / SEO)placeholdertype extended tostring | { dark, light } | false, object form uses CSS-only theme switchingplaceholderClassprop + built-in minimal placeholder fallback styles#placeholderslot (slot takes precedence over prop);@load/@errorevent forwardinghover-zoomclass (opt-in) + CSS variable--sw-image-hover-zoom-scale
- Wrapper-level
- 🚀 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;
rememberForprop 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-zfor theming - SwQrcodeScanner: image-based QR code scanner with
trigger/previewslots and full event stream - SwQrcodeGenerator: QR code generator with configurable size, error correction, foreground/background colour, and PNG download
- SwFormField simple mode:
nameis now optional; newrequired/hint/errorprops let the component work as a form field layout wrapper without vee-validate
- 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;
- 🚀 Business component "tri-state prop" pattern unified (v0.5)
onRecord/fetchPlayUrland 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; addedtypesemantic prop +loadingprop +#loading/#icon/#icon-rightslots - SwAlink → SwLink:
sw-alink→sw-link; security fixtarget="_blank"auto-addsrel="noopener noreferrer", external links additionally getnofollow - SwPlayer → SwVideoPlayer: rename only, API unchanged
- SwPlayerAudio → SwAudioPlayer: rename + fully rewritten to align with SwVideoPlayer. Added
url/type/linkCodev-model /initialSeekTime(replacesseekTime) / 14 emit / 6 expose (incl.switchLinkpreserving progress); removedplayMode; fixed "switch line N times → N timeupdate listeners stacked" - SwPlayerPreview → SwVideoPreview: rename + capability expansion.
fetchPlayUrltri-state defaults to/movie/play; newurldirect passing skips fetch + mp4/webm support +reload()for URL expiry - AudioRecordParams type change: added
event/view_time/codefields (aligned with VideoRecordParams); customonRecordneeds to update type signature
- SwBtn → SwButton: CSS class
- 🔧 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/qrcodeauto-injected into ViteoptimizeDeps.include; consumer projects no longer need manual config under pnpm strict mode
- Added global
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-metaESLint rule enforces useSeo on every page- Private pages (profile / login / register / mine etc., 4 pages) set
robots: 'private' / 'noindex'
- 6 starter pages migrated to framework-owned
- 🚀 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.tsverifies 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 incustom.css
- 🔧 CSS rem unit enforcement (pairs with fw v0.4)
- stylelint
declaration-property-unit-allowed-listenforces 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
- stylelint
- 🔧 auth middleware enhancement (pairs with fw v0.4)
guest.globalmiddleware 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 upgradeusednpm view @swift-web/framework versionwithout passing~/.npmrc_authToken, hitting 401 - Fixed to correctly pass token, can now fetch latest framework version from private registry
- Original
- 🚀 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-mcpto generate.mcp.json(later v0.3.0 removedinit-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
scanBusinessI18nUsagemodule scans business code for residual i18n API usage - Trimmed enable reports; lists disable residues
- 🚀
sw config synccommand (v0.2.3)- Re-runs all feature-flag appliers (i18n / pwa / tailwind etc.) to force-sync
swift.config.jsonwithnuxt.config.ts - Used for: syncing project files after manual
swift.config.jsonedits / pulling new switch defaults after cli upgrade
- Re-runs all feature-flag appliers (i18n / pwa / tailwind etc.) to force-sync
- 🚀 Tailwind v4 applier (v0.2.1)
sw initinteractive 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
useSeoplaceholder 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/seoAppendix 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/guestPathsconfig - 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 templatesw config— interactive manager forswiftWebFrameworkconfig (enable / disable sub-modules)sw add <type>— code generator for pages / components / stores etc.--textmode — 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)