fix(polish): gate test-build style generation at 1,250 chars and cap export at 5,000

Remove the test-mode unlimited personal-style corpus bypass and replace it with a real 1,250-character minimum gate. Production stays at 2,500. Training-corpus export and the diagnostics view now use a 5,000-character upper bound so the user's full private history never leaves the device in a single export.

- Add PolishStyleLearningCorpusBuilder.testBuildEffectiveCharacterCount (1,250) and trainingExtractionMaximumCharacterCount (5,000); trainingWindow(from:maximumCharacterCount:) accepts a custom cap.
- PolishStylesView: drop bypassesStyleLearningCharacterGate in favor of styleLearningMinimumCharacterCount, unify the ready/remaining text, remove the obsolete polishStyles.learn.testBuildReady key.
- PolishStyleCorpusExportStore: pass the 5,000 cap to trainingWindow and expose trainingExtractionMaximumCharacterCount in the export schema.
- FlowDiagnosticsSettingsView: show the export-sized (5,000-cap) corpus so the diagnostics match what the export ZIP actually contains.
- Tests: rewrite the export cap test for 5,000, add below-cap coverage, add trainingWindow(maximumCharacterCount:) unit coverage, and convert the old UI test into a regression test for the 1,250 test-build minimum.
- L10n: drop the testBuildReady string in en + zh-Hans, update diagnostics.corpus.ready to mention 5,000.
- CHANGELOG: bilingual entry under [Unreleased].
This commit is contained in:
Rocky
2026-08-30 12:09:06 +08:00
parent eac44daaf4
commit 3033ca5b33
11 changed files with 175 additions and 35 deletions
@@ -46,6 +46,7 @@ final class PolishStyleCorpusExportStoreTests: XCTestCase {
XCTAssertEqual(export.appBuild, "94")
XCTAssertEqual(export.effectiveCharacterCount, 4)
XCTAssertEqual(export.requiredEffectiveCharacterCount, 2_500)
XCTAssertEqual(export.trainingExtractionMaximumCharacterCount, 5_000)
XCTAssertEqual(export.examples.count, 1)
XCTAssertEqual(export.examples[0].prePolishText, "你好 世界")
XCTAssertEqual(export.examples[0].finalText, "你好,世界。")
@@ -105,7 +106,50 @@ final class PolishStyleCorpusExportStoreTests: XCTestCase {
XCTAssertEqual(decoded.examples.count, 1)
}
func testExportUsesNewestCompleteExamplesThroughThreshold() throws {
func testExportUsesNewestCompleteExamplesThroughFiveThousandThreshold() throws {
// Total available characters: 7,000 above the export cap.
// Newest first: newest (3,000) + middle (2,000) = 5,000 (cap reached,
// stop). Oldest is dropped from the export window.
let history = SyncedSpeechHistory(
entries: [
SpeechHistoryEntry(
text: "oldest",
prePolishText: String(repeating: "", count: 2_000),
createdAt: Date(timeIntervalSince1970: 1)
),
SpeechHistoryEntry(
text: "middle-complete",
prePolishText: String(repeating: "", count: 2_000),
createdAt: Date(timeIntervalSince1970: 2)
),
SpeechHistoryEntry(
text: "newest-complete",
prePolishText: String(repeating: "", count: 3_000),
createdAt: Date(timeIntervalSince1970: 3)
)
]
)
let export = try XCTUnwrap(
PolishStyleCorpusExportStore(
directoryURL: temporaryDirectory()
).makeExport(from: history)
)
XCTAssertEqual(export.effectiveCharacterCount, 5_000)
XCTAssertEqual(export.requiredEffectiveCharacterCount, 2_500)
XCTAssertEqual(export.trainingExtractionMaximumCharacterCount, 5_000)
XCTAssertEqual(
export.examples.map(\.finalText),
["middle-complete", "newest-complete"]
)
XCTAssertEqual(export.examples[0].prePolishText.count, 2_000)
XCTAssertEqual(export.examples[1].prePolishText.count, 3_000)
}
func testExportKeepsEveryEligibleExampleWhenUnderFiveThousandThreshold() throws {
// 3,600 characters total below the 5,000 cap, so the export keeps
// every eligible example chronologically.
let history = SyncedSpeechHistory(
entries: [
SpeechHistoryEntry(
@@ -132,13 +176,11 @@ final class PolishStyleCorpusExportStoreTests: XCTestCase {
).makeExport(from: history)
)
XCTAssertEqual(export.effectiveCharacterCount, 2_600)
XCTAssertEqual(export.effectiveCharacterCount, 3_600)
XCTAssertEqual(
export.examples.map(\.finalText),
["middle-complete", "newest-complete"]
["oldest", "middle-complete", "newest-complete"]
)
XCTAssertEqual(export.examples[0].prePolishText.count, 1_600)
XCTAssertEqual(export.examples[1].prePolishText.count, 1_000)
}
func testEmptyCorpusRemovesPreviousExport() throws {