diff --git a/docs/redesign/phase-5/p5-15/p5-15-01/diagnostics/preferences.spec.ts b/docs/redesign/phase-5/p5-15/p5-15-01/diagnostics/preferences.spec.ts index c83a913..d60d013 100644 --- a/docs/redesign/phase-5/p5-15/p5-15-01/diagnostics/preferences.spec.ts +++ b/docs/redesign/phase-5/p5-15/p5-15-01/diagnostics/preferences.spec.ts @@ -63,8 +63,8 @@ test('[P5-15-01 diagnostic] native language and notification saves persist durin await page.goto('/settings'); await notification.goto('/settings'); await notification.getByRole('tab', { name: 'Notifications', exact: true }).click(); - // Three alternating rounds reproduce concurrency; this is bounded diagnosis, not a load test. - for (const language of ['nb', 'en', 'nb'] as const) { + // Twelve alternating rounds qualify the recorded overlap; this is not production load evidence. + for (const language of Array.from({ length: 12 }, (_, index) => index % 2 ? 'en' : 'nb')) { const current = language === 'nb' ? translations.en : translations.nb; const target = translations[language]; const enabled = language === 'en'; @@ -100,11 +100,40 @@ test('[P5-15-01 diagnostic] native language and notification saves persist durin await expect(page.getByRole('combobox', { name: target.settingsPreferredLanguage, exact: true })).toBeVisible(); } await page.reload(); await notification.reload(); - await expect(page.locator('html')).toHaveAttribute('lang', 'nb-NO'); - await notification.getByRole('tab', { name: translations.nb.settingsTabNotifications, exact: true }).click(); - await expect(notification.getByLabel(translations.nb.settingsNotificationsFollowUpReminders, { exact: true })).not.toBeChecked(); + await expect(page.locator('html')).toHaveAttribute('lang', 'en'); + await notification.getByRole('tab', { name: translations.en.settingsTabNotifications, exact: true }).click(); + await expect(notification.getByLabel(translations.en.settingsNotificationsFollowUpReminders, { exact: true })).toBeChecked(); } finally { await info.attach('native-preference-concurrency', { body: JSON.stringify(evidence, null, 2), contentType: 'application/json' }); await notification.close(); } }); + +test('[P5-15-04 PRES-J07 J05] native full-result Board retains membership at 200 and 1000 owned jobs', async ({ page, headers }, info) => { + test.setTimeout(360_000); + const company = await page.request.post(`${apiUrl}/companies`, { headers, data: { name: 'Synthetic scaling company' } }); + expect(company.status()).toBe(201); + const companyId = (await company.json()).id; + const observations = []; + let createdCount = 0; + for (const count of [200, 1000]) { + while (createdCount < count) { + const response = await page.request.post(`${apiUrl}/jobapplications`, { headers, data: { companyId, jobTitle: `Scaling fixture ${++createdCount}`, status: 'Saved' } }); + expect(response.status()).toBe(201); + } + const apiStarted = Date.now(); + const board = await page.request.get(`${apiUrl}/jobapplications/board`, { headers }); + expect(board.status()).toBe(200); + const body = await board.json(); + expect(body).toHaveLength(count); + const apiMs = Date.now() - apiStarted; + const started = Date.now(); + await page.goto('/jobs?view=board'); + await expect(page.locator('[data-kanban-card-id]')).toHaveCount(count, { timeout: 60_000 }); + const renderedIds = await page.locator('[data-kanban-card-id]').evaluateAll(nodes => nodes.map(node => Number(node.getAttribute('data-kanban-card-id'))).sort((a, b) => a - b)); + expect(renderedIds).toEqual(body.map((job: { id: number }) => job.id).sort((a: number, b: number) => a - b)); + observations.push({ count, apiMs, renderMs: Date.now() - started, responseBytes: (await board.body()).length, renderedCards: renderedIds.length }); + await info.attach(`board-${count}-native-membership`, { body: JSON.stringify(observations.at(-1)), contentType: 'application/json' }); + } + await info.attach('board-full-result-scaling', { body: JSON.stringify({ observations, threshold: 'No approved production performance threshold; timings are observations, not a scalability acceptance claim', physicalTouch: 'NOT VERIFIED' }), contentType: 'application/json' }); +}); diff --git a/docs/redesign/phase-5/p5-15/p5-15-04/README.md b/docs/redesign/phase-5/p5-15/p5-15-04/README.md index 777df1c..b0f086a 100644 --- a/docs/redesign/phase-5/p5-15/p5-15-04/README.md +++ b/docs/redesign/phase-5/p5-15/p5-15-04/README.md @@ -16,9 +16,9 @@ No API/schema migration, destructive operation, renderer geometry/font/print-rul ## Reliability and capacity investigation -The diagnostic qualification extends the existing native preference scenario to twelve alternating English/Bokmål language and notification writes while four real report/notification reads overlap. It checks actual PUT statuses and owned readback, then reloads. This directly exercises the recorded overlap without mocking persistence or inventing a common cause for two historical failures. +The diagnostic qualification extends the existing native preference scenario to twelve alternating English/Bokmål language and notification writes while four real report/notification reads overlap. All twelve rounds returned language204/notification200, all four reads200 and correct owned readback, with EN/enabled-reminder persistence after reload. This directly exercises the recorded overlap without mocking persistence or inventing a common cause for two historical failures. Historical500 causes are **not reproduced in this finite run**, not repaired or closed. [Actual evidence](evidence/reliability-capacity.json). -The new Board diagnostic creates synthetic owned populations of 200 and 1,000 jobs, verifies the real full-result API, and compares every rendered card ID with its membership. API latency, payload bytes and render timing are observations; there is no approved production performance threshold to declare satisfied. Neither a query cap nor speculative pagination/virtualisation was introduced. The existing full-array O(N) architecture and physical-touch acceptance remain open. These diagnostic additions are not yet executed or claimed passing in this classification checkpoint; the native runner will execute them after its complete ordinary/security lanes. Their completed evidence will be pushed as a separate unit. +The new Board diagnostic created synthetic owned populations of 200 and 1,000 jobs and passed exact real API/DOM ID membership at both sizes. Observations:200jobs/API33ms/render1,040ms/236,253bytes;1,000jobs/API19ms/render1,612ms/1,182,578bytes. These are finite local measurements, with no approved production performance threshold to declare satisfied. Neither a query cap nor speculative pagination/virtualisation was introduced. The existing full-array O(N) architecture and physical-touch acceptance remain open. All three separate diagnostics pass, versus the inherited two; extending preference rounds does not add twelve test cases. The diagnostic/evidence unit is committed/pushed independently. The [CV save-state investigation](../p5-15-01/INTERMITTENT_FAILURES.md) qualifies the editor's existing 800ms autosave boundary with a controlled test clock: pending draft/focus/no write before it, exact versioned save afterward. No saving implementation changes. The untraced historical incident remains OPEN; passing reruns alone do not establish its original cause. diff --git a/docs/redesign/phase-5/p5-15/p5-15-04/evidence/reliability-capacity.json b/docs/redesign/phase-5/p5-15/p5-15-04/evidence/reliability-capacity.json new file mode 100644 index 0000000..00258c6 --- /dev/null +++ b/docs/redesign/phase-5/p5-15/p5-15-04/evidence/reliability-capacity.json @@ -0,0 +1,452 @@ +{ + "unsaved-rule-tab-retention": { + "original": 14, + "draft": 5, + "visibleDraft": "5", + "persisted": 14, + "ruleWrites": 0, + "readbackStatus": 200 + }, + "native-preference-concurrency": [ + { + "requestedLanguage": "nb-NO", + "requestedNotifications": false, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "nb-NO", + "notifications": false + }, + "localLanguage": "nb-NO", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "en", + "requestedNotifications": true, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "en", + "notifications": true + }, + "localLanguage": "en", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "nb-NO", + "requestedNotifications": false, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "nb-NO", + "notifications": false + }, + "localLanguage": "nb-NO", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "en", + "requestedNotifications": true, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "en", + "notifications": true + }, + "localLanguage": "en", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "nb-NO", + "requestedNotifications": false, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "nb-NO", + "notifications": false + }, + "localLanguage": "nb-NO", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "en", + "requestedNotifications": true, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "en", + "notifications": true + }, + "localLanguage": "en", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "nb-NO", + "requestedNotifications": false, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "nb-NO", + "notifications": false + }, + "localLanguage": "nb-NO", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "en", + "requestedNotifications": true, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "en", + "notifications": true + }, + "localLanguage": "en", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "nb-NO", + "requestedNotifications": false, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "nb-NO", + "notifications": false + }, + "localLanguage": "nb-NO", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "en", + "requestedNotifications": true, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "en", + "notifications": true + }, + "localLanguage": "en", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "nb-NO", + "requestedNotifications": false, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "nb-NO", + "notifications": false + }, + "localLanguage": "nb-NO", + "errorsVisible": { + "language": 2, + "notification": 2 + } + }, + { + "requestedLanguage": "en", + "requestedNotifications": true, + "languageStatus": 204, + "notificationStatus": 200, + "reads": [ + { + "path": "/jobapplications/stats", + "status": 200 + }, + { + "path": "/jobapplications/analytics-overview", + "status": 200 + }, + { + "path": "/jobapplications/analytics?months=6", + "status": 200 + }, + { + "path": "/notifications?limit=10", + "status": 200 + } + ], + "readback": { + "meStatus": 200, + "notificationStatus": 200, + "language": "en", + "notifications": true + }, + "localLanguage": "en", + "errorsVisible": { + "language": 2, + "notification": 2 + } + } + ], + "board-full-result-scaling": { + "observations": [ + { + "count": 200, + "apiMs": 33, + "renderMs": 1040, + "responseBytes": 236253, + "renderedCards": 200 + }, + { + "count": 1000, + "apiMs": 19, + "renderMs": 1612, + "responseBytes": 1182578, + "renderedCards": 1000 + } + ], + "threshold": "No approved production performance threshold; timings are observations, not a scalability acceptance claim", + "physicalTouch": "NOT VERIFIED" + } +}