diff --git a/.gitignore b/.gitignore
index 8e0c0150..62775ab2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,8 @@ configs/**/data/**
*.bak
**/node_modules
**/.DS_Store
-logs/
\ No newline at end of file
+logs/
+
+# Build sırasında git tag'lerinden üretilir (api/scripts/generate-release-notes.sh).
+# Takip edilirse deploy'daki `git pull` yerel değişiklikle çakışır ve dosya eskir.
+api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
\ No newline at end of file
diff --git a/api/scripts/generate-release-notes.sh b/api/scripts/generate-release-notes.sh
index 337c5a2a..3a1e30c0 100644
--- a/api/scripts/generate-release-notes.sh
+++ b/api/scripts/generate-release-notes.sh
@@ -2,26 +2,29 @@
set -e
# Sürüm notlarını git tag'lerinden üretir:
-# api/release-notes/release-notes.json
+# api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
#
-# Bu dosya API konteynerine /etc/api/release-notes altına MOUNT edilir (imaja
-# GÖMÜLMEZ). Uygulama açılışında ReleaseNoteStartupService onu `Sas_H_ReleaseNote`
-# tablosuyla eşitler. Yani yeni bir sürüm tag'i için ne DbMigrator ne de imaj
-# derlemesi gerekir; deploy sırasında bu script koşar ve konteyner yeniden
-# başlar. İstemci sürüm numarasını ve changelog'u application-configuration
-# yanıtındaki `extraProperties.changeLogs` alanından okur.
+# Dosya csproj'daki `Content Update="release-notes.json"` kuralıyla publish
+# çıktısına kopyalanır, yani imaja gömülür ve konteynerde ContentRootPath
+# altında (/srv/app) yer alır. ReleaseNoteStartupService açılışta tam bu yolu
+# okuyup `Sas_H_ReleaseNote` tablosuyla eşitler; DbMigrator gerekmez.
#
-# Kaynak ağacının DIŞINDA bir klasöre yazılmasının sebebi Docker katman cache'i:
-# dosya `src/` altında olsaydı Dockerfile'daki `COPY . .` her tag'de geçersizleşip
-# tüm `dotnet publish` adımını yeniden çalıştırırdı.
+# ÖNEMLİ: Çıktı yolu ReleaseNoteStartupService'in okuduğu yolla aynı olmak
+# ZORUNDA. Daha önce script `api/release-notes/` altına yazıyordu; orası ne
+# publish çıktısına kopyalanıyor ne de konteynere mount ediliyordu, dolayısıyla
+# imajdaki eski (commit'lenmiş) dosya kullanılıyor ve yeni tag'ler hiç
+# görünmüyordu.
#
-# Deploy akışında deploy/app.sh tarafından otomatik çağrılır; elle de
-# çalıştırılabilir. Çıktı git'e commit EDİLMEZ (.gitignore).
+# Çıktı .gitignore'dadır: takip edilseydi deploy'daki `git pull` her seferinde
+# yerel değişiklikle çakışırdı.
+#
+# Deploy akışında configs/deployment/scripts/build/api.sh tarafından otomatik
+# çağrılır; elle de çalıştırılabilir.
#
# Dış bağımlılığı yoktur (jq/node gerekmez): JSON kaçışları sed ile yapılır.
cd "$(dirname "$0")/.."
-OUTPUT="release-notes/release-notes.json"
+OUTPUT="src/Sozsoft.Platform.HttpApi.Host/release-notes.json"
mkdir -p "$(dirname "$OUTPUT")"
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs
index 42b63910..b246872b 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ReleaseNoteStartupService.cs
@@ -108,8 +108,12 @@ public class ReleaseNoteStartupService : IHostedService
using var scope = _serviceProvider.CreateScope();
var (inserted, updated, deleted) = await SyncItemsAsync(scope.ServiceProvider, items);
+ // En son sürüm de loglanır: dosya eskiyse (üretim adımı atlanmış
+ // veya yanlış yola yazılmışsa) "0 eklendi" tek başına yanıltıcı olur.
_logger.LogInformation(
- "Sürüm notları senkronize edildi: {Total} tag, {Inserted} eklendi, {Updated} güncellendi, {Deleted} silindi.",
+ "Sürüm notları senkronize edildi ({Path}, en son sürüm {Latest}): {Total} tag, {Inserted} eklendi, {Updated} güncellendi, {Deleted} silindi.",
+ path,
+ items[0].Version,
items.Count,
inserted,
updated,
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json b/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
index 5ff35f57..413de7f9 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/release-notes.json
@@ -1,15 +1,25 @@
{
"ReleaseNotes": [
{
- "version": "1.2.0",
+ "version": "1.2.1",
"releaseDate": "2026-08-13",
- "commit": "798acbef9e6a26e29e58f10b13cb8e63ac5fa6b4",
+ "commit": "ee121db5211ffbd28496b87ce9e4776ff96f7640",
"notes": [
- "Claude.ai optimizasyon güncellemesi",
+ "Zaafiyet testleri ve güncellemeleri",
+ "React 19 upgrade",
+ "Devexpress 26.1.4",
"Migrations ve Seeds klasör düzenlemesi",
"New Version Check komponent düzenlemesi"
]
},
+ {
+ "version": "1.1.11",
+ "releaseDate": "2026-08-11",
+ "commit": "783c1fe1d35f7cb8f23fb63fad26123cbe54aafc",
+ "notes": [
+ "Claude.ai optimizasyon güncellemesi"
+ ]
+ },
{
"version": "1.1.10",
"releaseDate": "2026-07-31",
diff --git a/configs/deployment/scripts/build/api.sh b/configs/deployment/scripts/build/api.sh
index 4e071e07..1c843722 100644
--- a/configs/deployment/scripts/build/api.sh
+++ b/configs/deployment/scripts/build/api.sh
@@ -1,3 +1,9 @@
+#!/bin/bash
+# set -e: sürüm notu üretimi sessizce başarısız olursa imaj eski/eksik notlarla
+# derlenmesin. Daha önce böyle olmuştu: dosya git'te takip edildiği için hata
+# fark edilmiyordu.
+set -e
+
cd ~/sozsoft-platform
git checkout main
# --tags --force: sürüm notları git tag'lerinden üretiliyor; yerel tag origin'den
@@ -8,7 +14,19 @@ cd ~/sozsoft-platform/api
# Sürüm notları git tag'lerinden üretilir ve API imajına gömülür; uygulama
# açılışta bunları veritabanına senkronize eder (DbMigrator gerekmez).
-./scripts/generate-release-notes.sh
+# `bash ...` ile çağrılır: depo Windows'tan yönetildiği için çalıştırma bitinin
+# kaybolması "Permission denied" verip sürüm notlarını sessizce düşürüyordu.
+RELEASE_NOTES=src/Sozsoft.Platform.HttpApi.Host/release-notes.json
+bash scripts/generate-release-notes.sh
+
+# Dosya publish çıktısına csproj üzerinden girer; yoksa imaj sürüm notsuz kalır
+# ve API açılışta "release-notes.json bulunamadı" uyarısı verir. Sessizce devam
+# etmek yerine burada dur.
+if [ ! -s "$RELEASE_NOTES" ]; then
+ echo "HATA: ${RELEASE_NOTES} üretilemedi, imaj derlenmiyor." >&2
+ exit 1
+fi
+
VERSION=$(cat ~/sozsoft-platform/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json \
| grep -o '"[^"]*"\s*:\s*"[^"]*"' \
| grep -E '^"(Version)"' \
diff --git a/ui/src/components/template/UserDropdown.tsx b/ui/src/components/template/UserDropdown.tsx
index 6fb4a21f..c16fc19d 100644
--- a/ui/src/components/template/UserDropdown.tsx
+++ b/ui/src/components/template/UserDropdown.tsx
@@ -11,6 +11,7 @@ import { Link } from 'react-router-dom'
import { Avatar } from '../ui'
import {
FcEngineering,
+ FcNews,
FcNext,
FcNfcSign,
FcSynchronize,
@@ -142,6 +143,18 @@ const UserDropdownBase = ({ className }: CommonProps) => {
+
+
+
+
+
+ {translate('::App.ChangeLog')}
+
+
+
{/* */}
diff --git a/ui/src/store/locale.model.ts b/ui/src/store/locale.model.ts
index 26cacd31..4a4b443b 100644
--- a/ui/src/store/locale.model.ts
+++ b/ui/src/store/locale.model.ts
@@ -7,6 +7,8 @@ export interface LocaleStoreModel {
currentLang: string
currentUiVersion: string | undefined
currentTenantName?: string
+ /** Dolu ise kullanıcı bir sonraki girişinde ChangeLog ekranına yönlendirilir. */
+ pendingChangelogVersion?: string
}
export interface LocaleStoreActions {
@@ -14,6 +16,7 @@ export interface LocaleStoreActions {
onSetLang: ThunkOn
setUiVersion: Action
setTenantName: Action
+ clearPendingChangelog: Action
}
export type LocaleModel = LocaleStoreModel & LocaleStoreActions
@@ -22,6 +25,7 @@ const initialState: LocaleStoreModel = {
currentLang: appConfig.locale,
currentUiVersion: appConfig.uiVersion,
currentTenantName: undefined,
+ pendingChangelogVersion: undefined,
}
export const localeModel: LocaleModel = {
@@ -36,10 +40,41 @@ export const localeModel: LocaleModel = {
helpers.getStoreActions().abpConfig.getConfig(false)
},
),
+ /**
+ * Backend'den gelen çalışan sürümü işler (application-configuration →
+ * `extraProperties.changeLogs.uiVersion`).
+ *
+ * Sürüm son görülenden farklıysa changelog bayrağı yazılır; kullanıcı bir
+ * sonraki girişinde ChangeLog ekranına yönlendirilir. Tetikleyici bilerek
+ * bundle değil sürümdür: yalnızca yeni bir git tag'i eklendiğinde de kullanıcı
+ * değişiklikleri görür.
+ *
+ * Referans ayrı bir alan değil `currentUiVersion`'ın kendisidir; zaten persist
+ * edildiği için "son görülen sürüm" bilgisini o taşır. Ayrı bir alan eklemek
+ * mevcut kullanıcılarda bir sürümlük boşluk yaratırdı (yeni alan boş olduğu
+ * için ilk deploy sessizce atlanır).
+ *
+ * Yeni tarayıcıda `currentUiVersion` boştur (`appConfig.uiVersion = undefined`);
+ * kullanıcı ilk girişinde changelog'a düşmesin diye bayrak yazılmaz.
+ *
+ * Durum bu model üzerinden persist edilir (`[EasyPeasyStore][0].locale`);
+ * ayrı `localStorage` anahtarı KULLANILMAZ.
+ */
setUiVersion: action((state, payload) => {
+ // Config henüz yüklenmediğinde çağrı boş gelir; persist edilmiş "son görülen
+ // sürüm" bu yüzden ezilmemeli, yoksa her açılışta referans sıfırlanır ve
+ // bayrak bir daha hiç yazılmaz.
+ if (!payload) return
+
+ const lastSeen = state.currentUiVersion
state.currentUiVersion = payload
+
+ if (lastSeen && lastSeen !== payload) state.pendingChangelogVersion = payload
}),
setTenantName: action((state, payload) => {
state.currentTenantName = payload
}),
+ clearPendingChangelog: action((state) => {
+ state.pendingChangelogVersion = undefined
+ }),
}
diff --git a/ui/src/utils/hooks/useAuth.ts b/ui/src/utils/hooks/useAuth.ts
index 0cf960b7..f10a44d9 100644
--- a/ui/src/utils/hooks/useAuth.ts
+++ b/ui/src/utils/hooks/useAuth.ts
@@ -1,7 +1,7 @@
import { jwtDecode } from 'jwt-decode'
import { useNavigate } from 'react-router-dom'
import useQuery from './useQuery'
-import { useStoreActions, useStoreState } from '../../store/store'
+import { store, useStoreActions, useStoreState } from '../../store/store'
import {
FailedSignInResponse,
SignInCredential,
@@ -13,7 +13,6 @@ import { isLoginSuccess } from '../../proxy/account/models'
import { REDIRECT_URL_KEY } from '../../constants/app.constant'
import appConfig from '../../proxy/configs/app.config'
import { register } from '../../services/account.service'
-import { hasPendingChangelog } from '@/views/version/swRegistration'
import { ROUTES_ENUM } from '@/routes/route.constant'
type Status = 'success' | 'failed' | 'error'
@@ -72,7 +71,11 @@ function useAuth() {
const redirectUrl = query.get(REDIRECT_URL_KEY)
// Uygulama yeni bir sürümle/deploy ile açıldıysa kullanıcı önce
// değişiklikleri görsün. Bayrak ChangeLog ekranında tüketilir.
- const target = hasPendingChangelog()
+ //
+ // Değer hook state'inden değil doğrudan store'dan okunur: bayrak giriş
+ // sayfasındaki config yüklemesiyle yazılıyor, render kapanışı bayat
+ // kalabilir.
+ const target = store.getState().locale.pendingChangelogVersion
? ROUTES_ENUM.protected.admin.changeLog
: (redirectUrl ?? appConfig.authenticatedEntryPath)
navigate(target)
diff --git a/ui/src/views/version/ChangeLog.tsx b/ui/src/views/version/ChangeLog.tsx
index 9b10adfe..c54200b4 100644
--- a/ui/src/views/version/ChangeLog.tsx
+++ b/ui/src/views/version/ChangeLog.tsx
@@ -6,7 +6,8 @@ import React, { useEffect, useState } from 'react'
import PageTitle from '@/components/shared/PageTitle'
import { FaRegCheckCircle, FaSyncAlt, FaTag } from 'react-icons/fa'
import { releaseNoteService, type ReleaseNoteDto } from '@/services/releaseNote.service'
-import { checkForAppUpdate, clearPendingChangelog } from './swRegistration'
+import { checkForAppUpdate } from './swRegistration'
+import { useStoreActions } from '@/store'
// User dropdown reuses the exact update-check flow owned by this component.
// eslint-disable-next-line react-refresh/only-export-components
@@ -106,6 +107,7 @@ const Log = ({
const Changelog = () => {
const { translate } = useLocalization()
const { isCheckingUpdate, checkForUpdate } = useManualUpdateCheck()
+ const clearPendingChangelog = useStoreActions((actions) => actions.locale.clearPendingChangelog)
// Kaynak `ReleaseNote` entity'sidir; liste yalnızca bu ekran açıldığında
// istenir (application-configuration sadece çalışan sürümü taşır).
@@ -120,7 +122,7 @@ const Changelog = () => {
.getList()
.then(setReleases)
.catch((error: unknown) => console.warn('Sürüm notları alınamadı.', error))
- }, [])
+ }, [clearPendingChangelog])
return (
diff --git a/ui/src/views/version/swRegistration.ts b/ui/src/views/version/swRegistration.ts
index 7bf272f0..998e7e99 100644
--- a/ui/src/views/version/swRegistration.ts
+++ b/ui/src/views/version/swRegistration.ts
@@ -19,10 +19,11 @@ import './installPrompt'
* 2. Yeni worker bulunursa kurulum başlar; `src/sw.ts` indirdiği her dosyayı
* isim/boyut/yüzde olarak yayınlar, bu modül state'e çevirir.
* 3. Kurulum bitince SKIP_WAITING → `controllerchange` → reload.
- * 4. Backend'den gelen sürüm son görülenden farklıysa changelog bayrağı
- * yazılır; login sonrası kullanıcı ChangeLog ekranına yönlendirilir.
- * Tetikleyici sürümdür, bundle değil: yalnızca yeni bir sürüm notu (git
- * tag) eklendiğinde de kullanıcı değişiklikleri görür.
+ * 4. Changelog yönlendirmesi bu modülün işi DEĞİLDİR: son görülen sürüm ve
+ * bekleyen changelog bayrağı store'un `locale` diliminde tutulur
+ * (`setUiVersion` / `pendingChangelogVersion`), böylece durum tek yerde ve
+ * `[EasyPeasyStore][0]` içinde persist edilir. Bu modül yalnızca güncelleme
+ * penceresinin gösterdiği sürüm/not bilgisini tutar.
*
* Bu dosya entry chunk'ındadır: React/UI/store bağımlılığı yoktur, pencere
* yalnızca gerçekten indirme varken lazy yüklenir.
@@ -111,14 +112,22 @@ const VERSION_POLL_INTERVAL = 30 * 60 * 1000
const MIN_CHECK_GAP = 30 * 1000
const ACTIVATION_RETRY_DELAY = 8_000
const ACTIVATION_TIMEOUT = 45_000
+/** İndirme bittikten sonra geçişin kendiliğinden başlaması için tanınan süre. */
+const ACTIVATION_HANDOFF_TIMEOUT = 15_000
const REGISTRATION_TIMEOUT = 10_000
const UPDATE_CALL_TIMEOUT = 20_000
/** Kurulum/aktivasyon başarısız olduğunda otomatik yeniden deneme süresi. */
const RETRY_DELAY_SECONDS = 15
-const LAST_VERSION_KEY = 'sozsoft:last-version'
-const PENDING_CHANGELOG_KEY = 'sozsoft:pending-changelog'
-const CHUNK_RELOAD_GUARD_KEY = 'sozsoft:chunk-reload'
+/**
+ * Chunk yeniden yükleme koruması: sonsuz reload döngüsünü keser.
+ *
+ * Store'da tutulamaz — reload'ın hemen öncesinde, React/store mount edilmeden
+ * yazılması gerekiyor. Bu yüzden modüldeki tek doğrudan depo anahtarı budur;
+ * sürüm/changelog durumu store'un `locale` diliminde durur. Bayrak 10 saniye
+ * sonra silindiği için kalıcı bir kayıt bırakmaz.
+ */
+const CHUNK_RELOAD_GUARD_KEY = 'chunkReloadGuard'
/** Service worker yalnızca prod'da, dev'de ise VITE_PWA_DEV=true ile aktiftir. */
const isServiceWorkerEnabled = import.meta.env.PROD || import.meta.env.VITE_PWA_DEV === 'true'
@@ -129,19 +138,18 @@ const withTimeout = (promise: Promise, ms: number) =>
new Promise((resolve) => window.setTimeout(() => resolve(undefined), ms)),
])
-const readStorage = (key: string, session = false) => {
+const readStorage = (key: string) => {
try {
- return (session ? sessionStorage : localStorage).getItem(key) ?? undefined
+ return localStorage.getItem(key) ?? undefined
} catch {
return undefined
}
}
-const writeStorage = (key: string, value: string | undefined, session = false) => {
+const writeStorage = (key: string, value: string | undefined) => {
try {
- const store = session ? sessionStorage : localStorage
- if (value === undefined) store.removeItem(key)
- else store.setItem(key, value)
+ if (value === undefined) localStorage.removeItem(key)
+ else localStorage.setItem(key, value)
} catch {
/* storage kapalıysa yoksay */
}
@@ -240,9 +248,13 @@ function recalculate(
/* ------------------------------------------------------------------ */
/**
- * Sürüm bilgisini application-configuration yanıtından alır.
+ * Sürüm bilgisini application-configuration yanıtından alır ve yalnızca bu
+ * modülün GÖRÜNTÜLEME durumunu (güncelleme penceresi) besler.
+ *
+ * Kalıcı sürüm takibi — son görülen sürüm ve changelog bayrağı — burada değil,
+ * store'un `locale` diliminde tutulur (`setUiVersion`). Bu modül entry chunk'ta
+ * olduğu için store'a bağımlı olmamalı; bayrağı yazan taraf store'dur.
*
- * Store, config'i her yüklediğinde bunu çağırır; ayrı bir sürüm isteği yoktur.
* Yük şekli: `extraProperties.changeLogs = { uiVersion, tags[] }`.
*/
export const setAppVersionInfo = (raw: unknown) => {
@@ -253,29 +265,6 @@ export const setAppVersionInfo = (raw: unknown) => {
currentVersion: data.uiVersion,
changeLog: Array.isArray(data.tags) ? data.tags.filter(Boolean) : [],
})
-
- markVersionSeen(data.uiVersion)
-}
-
-/** Login sonrası changelog yönlendirmesi için bayrak. */
-export const hasPendingChangelog = () => !!readStorage(PENDING_CHANGELOG_KEY)
-
-export const clearPendingChangelog = () => writeStorage(PENDING_CHANGELOG_KEY, undefined)
-
-/**
- * Sürüm numarası son görülenden farklıysa changelog bayrağını yazar; kullanıcı
- * bir sonraki girişinde ChangeLog ekranına yönlendirilir.
- *
- * Tetikleyici bilerek BUNDLE DEĞİL, backend'den gelen sürümdür: yalnızca yeni
- * bir sürüm notu (git tag) eklendiğinde de — hiçbir bileşen değişmemiş olsa
- * bile — kullanıcı değişiklikleri görür. İlk açılışta (kayıt yokken) bayrak
- * yazılmaz, yalnızca mevcut sürüm not edilir.
- */
-function markVersionSeen(version: string) {
- const lastSeen = readStorage(LAST_VERSION_KEY)
- if (lastSeen === version) return
- if (lastSeen) writeStorage(PENDING_CHANGELOG_KEY, version)
- writeStorage(LAST_VERSION_KEY, version)
}
/* ------------------------------------------------------------------ */
@@ -291,6 +280,7 @@ let lastCheckAt = 0
let pollTimer: number | undefined
let activationRetryTimer: number | undefined
let activationTimeoutTimer: number | undefined
+let activationHandoffTimer: number | undefined
const watchedRegistrations = new WeakSet()
const watchedWorkers = new WeakSet()
@@ -298,8 +288,39 @@ const watchedWorkers = new WeakSet()
function clearActivationTimers() {
window.clearTimeout(activationRetryTimer)
window.clearTimeout(activationTimeoutTimer)
+ window.clearTimeout(activationHandoffTimer)
activationRetryTimer = undefined
activationTimeoutTimer = undefined
+ activationHandoffTimer = undefined
+}
+
+/**
+ * İndirme bitti ('ready') ama geçişi kimse başlatmadıysa devreye girer.
+ *
+ * Normal akışta geçişi worker'ın `installed` olayı tetikler. O olay kaçabiliyor:
+ * dinleyici yalnızca `reg.installing` görüldüğünde kuruluyor ve sayfa hard reload
+ * ile açıldığında kayıt zaten `waiting` durumunda olabiliyor. Watchdog olmadan
+ * durum sonsuza kadar 'ready'de kalıyordu — ekran "yeni sürüme geçiliyor"da
+ * donuyor, zaman aşımı da yoktu çünkü sayaçlar yalnızca aktivasyon başladıktan
+ * sonra kuruluyor.
+ */
+function scheduleActivationHandoff() {
+ if (activationHandoffTimer || activating || reloading) return
+
+ activationHandoffTimer = window.setTimeout(() => {
+ activationHandoffTimer = undefined
+ if (activating || reloading) return
+
+ if (registration?.waiting) {
+ activateWaitingWorker()
+ return
+ }
+
+ // Bekleyen worker yok: geçiş sessizce kaybolmuş. Kabuk cache'i tazelenerek
+ // yenilenir, aksi halde aynı eski index.html geri gelir.
+ console.warn('Yeni sürüme geçiş tetiklenmedi, sayfa yenileniyor.')
+ void reloadWithFreshShell()
+ }, ACTIVATION_HANDOFF_TIMEOUT)
}
function reloadForUpdate() {
@@ -539,6 +560,12 @@ function handleWorkerMessage(event: MessageEvent) {
*/
const transitions = controlled && data.phase !== 'warmup'
+ // Isıtma kurulumla aynı anda koşabiliyor (eski aktif worker ısıtırken yenisi
+ // kuruluyor). İki oturumun dosya listeleri ayrı index uzayında olduğu için
+ // birleşince satırlar tekrarlanıyor ve toplam boyut aşıyordu; geçiş sürecinde
+ // ısıtma mesajları yok sayılır.
+ if (data.phase === 'warmup' && (activating || state.status === 'ready')) return
+
const totals = { loadedBytes: data.loadedBytes, totalBytes: data.totalBytes }
switch (data.type) {
@@ -575,7 +602,8 @@ function handleWorkerMessage(event: MessageEvent) {
visible: true,
percent: 100,
})
- if (!transitions) hideDialogLater()
+ if (transitions) scheduleActivationHandoff()
+ else hideDialogLater()
break
case 'SW_PRECACHE_ERROR':
if (!transitions) {
@@ -714,8 +742,14 @@ function scheduleWarmup(reg: ServiceWorkerRegistration) {
function trackInstallingWorker(reg: ServiceWorkerRegistration) {
const worker = reg.installing
- // Controller yoksa bu ilk kurulumdur; kullanıcıya güncelleme demek yanlış olur.
- if (!worker || !navigator.serviceWorker.controller || watchedWorkers.has(worker)) return
+ if (!worker || watchedWorkers.has(worker)) return
+
+ // İlk kurulumda kullanıcıya "güncelleme" demek yanlış olur. Ölçüt yalnızca
+ // `controller` olamaz: hard reload'da sayfa kontrolsüz açılabiliyor, ama ortada
+ // güncellenen bir kayıt (aktif worker) var. Bu ayrım yapılmazsa dinleyici hiç
+ // kurulmuyor ve geçişi tetikleyecek `installed` olayı kaçıyordu.
+ const isUpdate = !!navigator.serviceWorker.controller || !!reg.active
+ if (!isUpdate) return
watchedWorkers.add(worker)
void showUpdateDialog('downloading')
@@ -776,8 +810,8 @@ function startWatchers() {
function handleChunkLoadFailures() {
window.addEventListener('vite:preloadError', (event) => {
if (reloading) return
- if (readStorage(CHUNK_RELOAD_GUARD_KEY, true)) return
- writeStorage(CHUNK_RELOAD_GUARD_KEY, '1', true)
+ if (readStorage(CHUNK_RELOAD_GUARD_KEY)) return
+ writeStorage(CHUNK_RELOAD_GUARD_KEY, '1')
event.preventDefault()
console.warn('Eksik chunk tespit edildi, sayfa yenileniyor.')
reloading = true
@@ -785,7 +819,7 @@ function handleChunkLoadFailures() {
})
// Sayfa sorunsuz açıldıysa koruma bayrağını temizle.
- window.setTimeout(() => writeStorage(CHUNK_RELOAD_GUARD_KEY, undefined, true), 10_000)
+ window.setTimeout(() => writeStorage(CHUNK_RELOAD_GUARD_KEY, undefined), 10_000)
}
/** Kapalıyken önceki oturumlardan kalan kayıtları temizler. */