v1.1 WP-C: browser release coverage
Drives in a real browser the reader workflows v1 proved only through the API or the component suite, including an export that leaves the browser as a file. Final run: 91 checks (the 38 existing M11 checks plus 53 new), 0 failed, 0 skipped, on the production build over trusted-LAN HTTPS. - tools/m11_browser.py: scenarios for Retry and takes, Save Point create / restore / Redo, state correction (accepted, and a refused correction with its reason), narration length reaching each turn's prompt, failed generation (an unserved model blocked up front; a listed model that cannot narrate failing in the open) and recovery, and export download from the library and from campaign settings, imported into a fresh application. Rows are tagged M11 / WP-C and counted separately; --only for development. The M11 checks now wait on conditions instead of sleeping. - tools/m11_webdriver.py: Firefox download preferences, a $HOME-only download folder, a download wait that ignores partial, empty, pre-existing and still-growing files, centred real clicks, tabs, and condition waits. - tests/test_v11_c_browser_helpers.py: the download wait, prefs and $HOME guard, without a browser. - frontend: a correction the story refused was presented as "Generation failed" with a Retry offer and a typed-input claim. It is now "That correction was not applied", not retryable, with the reason kept (errors.js, FailureNotice.jsx; 3 regression tests). - DEVELOPMENT.md: the harness command, download profile and $HOME rule, what counts as a finished download, and the no-sleep rule. - docs: V1.1-PLAN, VERSION v4.4, planning README, reports/v1.1/V1.1-WP-C-REPORT.md. Open for the owner: "Correct" on an Important Facts row is always refused (K1), and a partly refused correction is not reachable from the reader UI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VvegagkhuCZoFPdv4M1egY
This commit is contained in:
co-authored by
Claude Opus 5
parent
0c1ba836ba
commit
59b5ebc2d8
@@ -45,6 +45,25 @@ export function classifyError(message) {
|
||||
const detail = String(message || '').trim() || 'No detail was reported.'
|
||||
const low = detail.toLowerCase()
|
||||
|
||||
// ---- A correction the story refused ----
|
||||
//
|
||||
// v1.1 WP-C, found in the browser. The State panel's refusals ("That
|
||||
// correction can't be applied — no fact 'f1' to invalidate.") matched none of
|
||||
// the rules below and fell through to "Generation failed", with a Retry button
|
||||
// and a line about what you typed. No turn was attempted and nothing was
|
||||
// typed: the reader corrected the story's state and the rules refused it. So
|
||||
// it is said as that, and the reason stays under the technical details.
|
||||
if (low.includes("correction can't be applied")) {
|
||||
return {
|
||||
kind: KIND.STATE,
|
||||
title: 'That correction was not applied',
|
||||
detail,
|
||||
hint: 'Nothing in the story or its state was changed. The reason is in the technical details.',
|
||||
retryable: false,
|
||||
keptInput: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Model / endpoint: the story cannot be told at all ----
|
||||
|
||||
// "No model configured — set one in Settings."
|
||||
|
||||
@@ -55,9 +55,13 @@ export function FailureNotice({ failure, partial, onRetry, onDismiss }) {
|
||||
|
||||
{failure.hint && <p className="failure-hint">{failure.hint}</p>}
|
||||
|
||||
<p className="failure-kept">
|
||||
Your story is unchanged, and what you typed is still in the box below.
|
||||
</p>
|
||||
{/* Every failed turn keeps what was typed (A05). A refused correction
|
||||
had nothing typed in the box, so it does not claim to (v1.1 WP-C). */}
|
||||
{failure.keptInput !== false && (
|
||||
<p className="failure-kept">
|
||||
Your story is unchanged, and what you typed is still in the box below.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{partial ? (
|
||||
<details className="failure-partial">
|
||||
|
||||
@@ -29,8 +29,8 @@ beforeEach(() => { vi.restoreAllMocks() })
|
||||
|
||||
describe('the failure notice states only what is true', () => {
|
||||
it('claims the typed words were kept — so the caller must actually keep them', async () => {
|
||||
// This assertion is the contract the defect broke. The notice is
|
||||
// unconditional, so every path that shows it owes the reader their text.
|
||||
// This assertion is the contract the defect broke. Every failed turn shows
|
||||
// it, so every path that shows it owes the reader their text.
|
||||
await renderWith(
|
||||
<FailureNotice failure={classifyError('Could not connect to http://127.0.0.1:9/v1')}
|
||||
partial={null} onRetry={vi.fn()} onDismiss={vi.fn()} />,
|
||||
@@ -64,6 +64,39 @@ describe('the failure notice states only what is true', () => {
|
||||
expect(onRetry).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// v1.1 WP-C, found in the browser: a correction the story refused fell through
|
||||
// to "Generation failed", offered to try the turn again, and claimed the
|
||||
// typed words were kept — none of which a State-panel correction involves.
|
||||
const REFUSAL = "That correction can't be applied — no fact 'f1' to invalidate."
|
||||
|
||||
it('classifies a refused correction as a state refusal, not a failed turn', () => {
|
||||
const refusal = classifyError(REFUSAL)
|
||||
expect(refusal.kind).toBe(KIND.STATE)
|
||||
expect(refusal.title).toBe('That correction was not applied')
|
||||
expect(refusal.retryable).toBe(false)
|
||||
expect(refusal.keptInput).toBe(false)
|
||||
expect(refusal.detail).toBe(REFUSAL)
|
||||
})
|
||||
|
||||
it('shows a refused correction with its reason, and no turn to retry', async () => {
|
||||
await renderWith(
|
||||
<FailureNotice failure={classifyError(REFUSAL)} partial={null}
|
||||
onRetry={vi.fn()} onDismiss={vi.fn()} />,
|
||||
)
|
||||
expect(screen.getByText('That correction was not applied')).toBeInTheDocument()
|
||||
expect(screen.getByText(/no fact 'f1' to invalidate/)).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'Try that turn again' })).toBeNull()
|
||||
expect(screen.queryByText(/what you typed is still in the box/)).toBeNull()
|
||||
})
|
||||
|
||||
it('still claims the typed words were kept for a failed turn', async () => {
|
||||
await renderWith(
|
||||
<FailureNotice failure={classifyError('boom')} partial={null}
|
||||
onRetry={vi.fn()} onDismiss={vi.fn()} />,
|
||||
)
|
||||
expect(screen.getByText(/what you typed is still in the box/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('labels partial prose as not kept rather than showing it as story', async () => {
|
||||
await renderWith(
|
||||
<FailureNotice failure={classifyError('boom')} partial="The tavern door swung"
|
||||
|
||||
Reference in New Issue
Block a user