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
@@ -146,6 +146,42 @@ final class PolishStyleLearningServiceTests: XCTestCase {
XCTAssertEqual(window.examples.map(\.finalText), ["older", "newer"])
}
func testTrainingWindowHonorsCustomMaximumCharacterCount() {
// Total available characters: 7,000. With a 5,000-character
// cap (training-corpus export), newest (3,000) + middle (2,000)
// fills the window and oldest is dropped.
let oldest = PolishStyleLearningExample(
prePolishText: String(repeating: "", count: 2_000),
finalText: "oldest",
polishStyleID: nil,
createdAt: Date(timeIntervalSince1970: 1)
)
let middle = PolishStyleLearningExample(
prePolishText: String(repeating: "", count: 2_000),
finalText: "middle",
polishStyleID: nil,
createdAt: Date(timeIntervalSince1970: 2)
)
let newest = PolishStyleLearningExample(
prePolishText: String(repeating: "", count: 3_000),
finalText: "newest",
polishStyleID: nil,
createdAt: Date(timeIntervalSince1970: 3)
)
let window = PolishStyleLearningCorpusBuilder.trainingWindow(
from: [oldest, newest, middle],
maximumCharacterCount: PolishStyleLearningCorpusBuilder
.trainingExtractionMaximumCharacterCount
)
XCTAssertEqual(window.effectiveCharacterCount, 5_000)
XCTAssertEqual(
window.examples.map(\.finalText),
["middle", "newest"]
)
}
func testGenerationRunsExtractorBeforeSynthesizerWithSeparatedPayloads() async throws {
var catalog = PolishStyleCatalog()
let activeStyle = PolishStylePack(