1.4.1 (build 46): fix cambio de Chart Type roto por el cache de snapshots

El fetch del cache dependía del timeRange activo (monthsLimit = timeRange.months),
así que al reutilizarlo entre chart types/rangos (optimización del build 45) los
charts recibían historia truncada — Year vs Year o "All" con solo 12 meses, charts
vacíos o incompletos al cambiar de tipo.

Fix: el cache siempre guarda la historia completa (maxHistoryMonths) y el recorte
por rango se hace en memoria (el filtro por cutoffDate ya existía justo después).
Bonus: hiddenHistoryMonths ahora se calcula contra la ventana completa (teaser más
preciso).

Verificado en iPad Pro 13" simulador: Evolution 12M → All → Compare →
Period vs Period → Evolution, todos renderizan con datos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
This commit is contained in:
alexandrev-tibco
2026-07-03 16:35:42 +02:00
parent a0710a6f76
commit 7c92ea36c0
3 changed files with 49 additions and 15 deletions
+8 -8
View File
@@ -459,7 +459,7 @@
CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournalDebug.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournalDebug.entitlements;
ENABLE_USER_SCRIPT_SANDBOXING = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -498,7 +498,7 @@
CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournal.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournal.entitlements;
ENABLE_USER_SCRIPT_SANDBOXING = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -655,7 +655,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
@@ -688,7 +688,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets; DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
@@ -719,7 +719,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -743,7 +743,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -766,7 +766,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -789,7 +789,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 45; CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = 2825Q76T7H; DEVELOPMENT_TEAM = 2825Q76T7H;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
@@ -448,17 +448,19 @@ class ChartsViewModel: ObservableObject {
sources = sourceRepository.sources.filter { shouldIncludeSource($0) } sources = sourceRepository.sources.filter { shouldIncludeSource($0) }
} }
let monthsLimit = timeRange.months ?? maxHistoryMonths
let sourceIds = sources.compactMap { $0.id } let sourceIds = sources.compactMap { $0.id }
// Performance: Reuse cached snapshots when possible // Performance: cache the FULL history once and slice by time range in memory.
// The cache is shared across chart types and ranges, so it must never be
// fetched with a range-dependent months limit (that truncated Year vs Year
// and "All" after visiting a chart with a shorter range).
var snapshots: [Snapshot] var snapshots: [Snapshot]
if let cached = cachedSnapshots { if let cached = cachedSnapshots {
snapshots = cached snapshots = cached
} else { } else {
snapshots = snapshotRepository.fetchSnapshots( snapshots = snapshotRepository.fetchSnapshots(
for: sourceIds, for: sourceIds,
months: monthsLimit months: maxHistoryMonths
) )
hiddenHistoryMonths = freemiumValidator.hiddenHistoryMonths(in: snapshots) hiddenHistoryMonths = freemiumValidator.hiddenHistoryMonths(in: snapshots)
snapshots = freemiumValidator.filterSnapshots(snapshots) snapshots = freemiumValidator.filterSnapshots(snapshots)
@@ -151,10 +151,42 @@ final class PortfolioJournalUITests: XCTestCase {
} }
Thread.sleep(forTimeInterval: 3.0) Thread.sleep(forTimeInterval: 3.0)
let shot = XCTAttachment(screenshot: capture.screenshot()) func snap(_ name: String) {
shot.name = "Charts_Redesign" let shot = XCTAttachment(screenshot: capture.screenshot())
shot.lifetime = .keepAlways shot.name = name
add(shot) shot.lifetime = .keepAlways
add(shot)
}
snap("01_Evolution_default")
// Switch period to All (exercises the shared snapshot cache across ranges)
let allSegment = capture.buttons["All"].firstMatch
if allSegment.waitForExistence(timeout: 3) {
allSegment.tap()
Thread.sleep(forTimeInterval: 2.0)
snap("02_Evolution_All")
}
// Switch chart type to a free multi-control chart and back (cache reuse path)
let compareTile = capture.buttons["Compare"].firstMatch
if compareTile.waitForExistence(timeout: 3) {
compareTile.tap()
Thread.sleep(forTimeInterval: 2.0)
snap("03_Compare")
}
let periodTile = capture.buttons["Period vs Period"].firstMatch
if periodTile.waitForExistence(timeout: 3) {
periodTile.tap()
Thread.sleep(forTimeInterval: 2.0)
snap("04_PeriodVsPeriod")
}
let evolutionTile = capture.buttons["Evolution"].firstMatch
if evolutionTile.waitForExistence(timeout: 3) {
evolutionTile.tap()
Thread.sleep(forTimeInterval: 2.0)
snap("05_Evolution_back")
}
} }
/// Captures full-screen screenshots of the main tabs with demo data for App Store /// Captures full-screen screenshots of the main tabs with demo data for App Store