# Files changed · PR \#22208 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/22208/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(web\): don't save retention age until committed](https://git.cafe/pingdotgg/t3code/pull/22208.md)

Version: 34

Head commit: 1e57ef1ce923c9c219ec18f63494311909dc5b4d

Base commit: ef92441313da327139df75daa8f723c0f42348f6

Comparison base: ef92441313da327139df75daa8f723c0f42348f6

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/components/settings/StorageSettings.browser.test.tsx

Status: modified; +155; −1

```diff
@@ -3,10 +3,12 @@
 import { DEFAULT_SERVER_SETTINGS, type StorageCleanupReport } from "@t3tools/contracts";
 import { type ReactNode } from "react";
 import { expect, it, vi } from "vite-plus/test";
+import { userEvent } from "vite-plus/test/browser";
 
 const state = vi.hoisted(() => ({
   settings: {} as typeof DEFAULT_SERVER_SETTINGS,
   run: vi.fn(),
+  update: vi.fn(),
   report: null as StorageCleanupReport | null,
 }));
 
@@ -61,7 +63,7 @@
   ...(await original()),
   useScopedSettings: (() => state.settings) as never,
   useScopedSettingsMixed: () => false,
+  useUpdateScopedSettings: () => state.update,
-  useUpdateScopedSettings: () => vi.fn(),
   useClearScopedSettings: () => vi.fn(),
 }));
 // The real page container needs a router and the real row reads write permission and
@@ -103,6 +105,7 @@
   };
   state.report = null;
   state.run.mockReset();
+  state.update.mockReset();
 }
 
 const panel = () => (
@@ -124,3 +127,154 @@
   await act(async () => button.click());
   expect(state.run).not.toHaveBeenCalled();
 });
+
+it("saves a newly enabled retention rule only once its visible age is committed", async () => {
+  const label = "Delete inactive worktrees";
+  setup();
+  await using screen = await render(panel());
+  await act(async () =>
+    screen.container.querySelector<HTMLElement>(`[role="switch"][aria-label="${label}"]`)!.click(),
+  );
+  expect(state.update).not.toHaveBeenCalled();
+
+  const input = screen.container.querySelector<HTMLInputElement>(
+    `input[aria-label="${label} in days"]`,
+  )!;
+  await act(async () => {
+    Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, "30");
+    input.dispatchEvent(new Event("input", { bubbles: true }));
+    input.blur();
+  });
+  expect(state.update).toHaveBeenCalledExactlyOnceWith({
+    storageCleanup: { worktreeAfterDays: 30 },
+  });
+});
+
+const inactive = "Delete inactive worktrees";
+const ruleSwitch = (root: ParentNode, label: string) =>
+  root.querySelector<HTMLElement>(`[role="switch"][aria-label="${label}"]`)!;
+const daysInput = (root: ParentNode, label: string) =>
+  root.querySelector<HTMLInputElement>(`input[aria-label="${label} in days"]`);
+function setInputValue(input: HTMLInputElement, value: string) {
+  Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, value);
+  input.dispatchEvent(new Event("input", { bubbles: true }));
+}
+const pressKey = (input: HTMLInputElement, key: string) =>
+  input.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }));
+async function openDraft(label: string) {
+  await using stack = new AsyncDisposableStack();
+  const screen = stack.use(await render(panel()));
+  await act(async () => ruleSwitch(screen.container, label).click());
+  stack.move();
+  return screen;
+}
+
+it("saves the visible default when Enter commits an untouched retention draft", async () => {
+  setup();
+  await using screen = await openDraft(inactive);
+  await act(async () => pressKey(daysInput(screen.container, inactive)!, "Enter"));
+  expect(state.update).toHaveBeenCalledExactlyOnceWith({
+    storageCleanup: { worktreeAfterDays: 8 },
+  });
+});
+
+it("does not save a retention draft whose visible text is not a number", async () => {
+  setup();
+  await using screen = await openDraft(inactive);
+  const input = daysInput(screen.container, inactive)!;
+  for (const text of ["+", "."]) {
+    input.select();
+    await userEvent.keyboard(`${text}{Enter}`);
+    expect(input.value).toBe(text);
+  }
+  expect(state.update).not.toHaveBeenCalled();
+  input.select();
+  await userEvent.keyboard("30{Enter}");
+  expect(state.update).toHaveBeenCalledExactlyOnceWith({
+    storageCleanup: { worktreeAfterDays: 30 },
+  });
+});
+
+it("still cancels a retention draft left after clicking its days label", async () => {
+  setup();
+  await using screen = await openDraft(inactive);
+  const input = daysInput(screen.container, inactive)!;
+  await userEvent.click(input.parentElement!.querySelector("span[aria-hidden]")!);
+  const outside = document.createElement("button");
+  document.body.append(outside);
+  await userEvent.click(outside);
+  outside.remove();
+  expect(daysInput(screen.container, inactive)).toBeNull();
+  expect(state.update).not.toHaveBeenCalled();
+});
+
+it("saves a retention step from the drafted age", async () => {
+  const label = "Delete old rotated logs";
+  setup();
+  await using screen = await openDraft(label);
+  const step = screen.container.querySelector<HTMLElement>(`[aria-label="Increase ${label}"]`)!;
+  await act(async () => step.click());
+  expect(state.update).toHaveBeenCalledExactlyOnceWith({ storageCleanup: { logsAfterDays: 9 } });
+});
+
+it("returns a retention rule to off when saving its age fails", async () => {
+  setup();
+  state.update.mockResolvedValue(false);
+  await using screen = await openDraft(inactive);
+  await act(async () => pressKey(daysInput(screen.container, inactive)!, "Enter"));
+  expect(state.update).toHaveBeenCalledOnce();
+  expect(daysInput(screen.container, inactive)).toBeNull();
+});
+
+it("saves an age edited while an earlier enabling save is pending", async () => {
+  setup();
+  let failFirst!: (saved: boolean) => void;
+  state.update
+    .mockReturnValueOnce(new Promise<boolean>((resolve) => (failFirst = resolve)))
+    .mockReturnValueOnce(new Promise<boolean>(() => {}));
+  await using screen = await openDraft(inactive);
+  const input = daysInput(screen.container, inactive)!;
+  await act(async () => pressKey(input, "Enter"));
+  await act(async () => {
+    setInputValue(input, "30");
+    input.blur();
+  });
+  expect(state.update).toHaveBeenNthCalledWith(2, { storageCleanup: { worktreeAfterDays: 30 } });
+
+  await act(async () => failFirst(false));
+  await vi.waitFor(() => expect(daysInput(screen.container, inactive)?.value).toBe("30"));
+});
+
+it("keeps a retention rule off when its draft is left, cancelled, or switched off", async () => {
+  setup();
+  await using screen = await openDraft(inactive);
+  const outside = document.createElement("button");
+  document.body.append(outside);
+  await act(async () => outside.focus());
+  outside.remove();
+  expect(daysInput(screen.container, inactive)).toBeNull();
+
+  await act(async () => ruleSwitch(screen.container, inactive).click());
+  await act(async () => pressKey(daysInput(screen.container, inactive)!, "Escape"));
+  expect(daysInput(screen.container, inactive)).toBeNull();
+
+  await act(async () => ruleSwitch(screen.container, inactive).click());
+  await act(async () => setInputValue(daysInput(screen.container, inactive)!, "30"));
+  await act(async () => {
+    ruleSwitch(screen.container, inactive).focus();
+    ruleSwitch(screen.container, inactive).click();
+  });
+  expect(daysInput(screen.container, inactive)).toBeNull();
+  expect(state.update).not.toHaveBeenCalled();
+});
+
+it("turns an enabled retention rule off immediately", async () => {
+  const label = "Delete old browser artifacts";
+  setup();
+  Object.assign(state.settings.storageCleanup, { browserArtifactsAfterDays: 30 });
+  await using screen = await render(panel());
+  await act(async () => ruleSwitch(screen.container, label).click());
+  expect(state.update).toHaveBeenCalledExactlyOnceWith({
+    storageCleanup: { browserArtifactsAfterDays: null },
+  });
+});
```

## apps/web/src/components/settings/StorageSettings.tsx

Status: modified; +99; −14

```diff
@@ -4,7 +4,7 @@
   WorktreeCleanupRules,
 } from "@t3tools/contracts";
 import { resolveWorktreeCleanup } from "@t3tools/shared/projectSettings";
-import { useRef, useState } from "react";
+import { useLayoutEffect, useRef, useState } from "react";
 import { useAtomValue } from "@effect/atom-react";
 import {
   isAtomCommandInterrupted,
@@ -112,19 +112,77 @@
 }: {
   label: string;
   value: number | null;
-  onChange: (value: number | null) => void;
+  onChange: (value: number | null) => Promise<boolean> | void;
   minimum?: number;
 }) {
   const [draft, setDraft] = useState(value);
   const [savedValue, setSavedValue] = useState(value);
+  // Turning a rule on only opens an age draft: saving any age can start a cleanup sweep,
+  // so nothing is published until that age is committed. The ref lets a blur see a
+  // commit made earlier in the same event.
+  const [enabling, setEnablingState] = useState<"draft" | "saving" | null>(null);
+  const enablingRef = useRef(enabling);
+  const setEnabling = (next: typeof enabling) => {
+    enablingRef.current = next;
+    setEnablingState(next);
+  };
+  const pressing = useRef<"switch" | "row" | null>(null);
+  // The latest age sent while enabling, so a repeated commit of it is not resent and an
+  // older save that fails cannot close the rule over a newer one.
+  const submitted = useRef<{ days: number } | null>(null);
   if (savedValue !== value) {
     setSavedValue(value);
     setDraft(value);
+    setEnablingState(null);
   }
+  useLayoutEffect(() => {
+    enablingRef.current = enabling;
+  }, [enabling]);
 
+  const commit = (next: number) => {
+    const days = Math.min(3650, Math.max(minimum, Math.round(next)));
+    setDraft(days);
+    if (value === null && enablingRef.current === "saving" && submitted.current?.days === days) {
+      return;
+    }
+    const save = onChange(days);
+    if (value !== null) return;
+    const submission = { days };
+    submitted.current = submission;
+    setEnabling("saving");
+    void save?.then((saved) => {
+      if (!saved && submitted.current === submission && enablingRef.current === "saving") {
+        setEnabling(null);
+      }
+    });
+  };
+  const releasePointer = () => (pressing.current = null);
+
   return (
+    <div
+      className="flex items-center gap-3"
+      onPointerDown={() => {
+        if (pressing.current === null) pressing.current = "row";
+      }}
+      onPointerUp={releasePointer}
+      onPointerLeave={releasePointer}
+      onPointerCancel={releasePointer}
+      // A press on the row's text or gaps would otherwise blur the draft while the blur
+      // handler treats it as a press inside the row, leaving the draft open after it.
+      onMouseDown={(event) => {
+        const target = event.target;
+        if (enablingRef.current === "draft" && target instanceof Element) {
+          if (!target.closest("button, input")) event.preventDefault();
+        }
+      }}
+      onBlur={(event) => {
+        if (enablingRef.current !== "draft" || pressing.current) return;
+        const next = event.relatedTarget;
+        if (next instanceof Node && event.currentTarget.contains(next)) return;
+        setEnabling(null);
-    <div className="flex items-center gap-3">
+      }}
-      {value !== null ? (
+    >
+      {value !== null || enabling !== null ? (
         <NumberField
           value={draft}
           min={minimum}
@@ -133,21 +191,38 @@
           size="sm"
           className="w-auto"
           onValueChange={setDraft}
-          onValueCommitted={(next) => {
-            if (next === null) setDraft(value);
-            else {
-              const days = Math.min(3650, Math.max(minimum, Math.round(next)));
-              setDraft(days);
+          onValueCommitted={(next, details) => {
+            // Moving from a draft to its switch is on the way to turning the rule off.
+            const target = details.event instanceof FocusEvent ? details.event.relatedTarget : null;
+            const toSwitch =
-              onChange(days);
+              pressing.current === "switch" ||
-            }
+              (target instanceof Element && target.getAttribute("role") === "switch");
+            if (enablingRef.current === "draft" && toSwitch) return;
+            if (next === null) setDraft(value ?? Math.max(minimum, 8));
+            else commit(next);
           }}
         >
           <NumberFieldGroup>
             <NumberFieldDecrement aria-label={`Decrease ${label}`} />
             <NumberFieldInput
               aria-label={`${label} in days`}
+              autoFocus={value === null}
+              size={new Intl.NumberFormat().format(draft ?? value ?? 8).length}
-              size={new Intl.NumberFormat().format(draft ?? value).length}
               className="field-sizing-content w-auto min-w-[1ch] grow-0 text-right"
+              // The field reports a value only for text it can parse, and does so right after
+              // this runs, so text like "+" leaves no hidden age for Enter to commit.
+              onChange={() => setDraft(null)}
+              onKeyDown={(event) => {
+                if (event.nativeEvent.isComposing || enablingRef.current !== "draft") return;
+                if (event.key === "Escape") {
+                  event.preventDefault();
+                  event.stopPropagation();
+                  setEnabling(null);
+                } else if (event.key === "Enter" && draft !== null) {
+                  event.preventDefault();
+                  commit(draft);
+                }
+              }}
             />
             <span aria-hidden="true" className="self-center pr-2 text-xs">
               days
@@ -160,8 +235,18 @@
       )}
       <Switch
         aria-label={label}
+        checked={value !== null || enabling !== null}
+        onPointerDown={() => (pressing.current = "switch")}
+        onCheckedChange={(enabled) => {
+          if (enabled) {
-        checked={value !== null}
+            setDraft(Math.max(minimum, 8));
-        onCheckedChange={(enabled) => onChange(enabled ? 8 : null)}
+            setEnabling("draft");
+            return;
+          }
+          const publish = value !== null || enablingRef.current === "saving";
+          setEnabling(null);
+          if (publish) onChange(null);
+        }}
       />
     </div>
   );
```

## apps/web/src/components/settings/useScopedSettings.ts

Status: modified; +7; −5

```diff
@@ -61,9 +61,9 @@
         title: "Setting not saved",
         description: plan.unavailableReason,
       });
-      return;
+      return Promise.resolve(false);
     }
-    void persistScopedSettingsPatch(
+    return persistScopedSettingsPatch(
       plan,
       async (request) => {
         const missing = requiredScopesForServerSettingsPatch(request.input.patch).find(
@@ -82,7 +82,7 @@
       },
       persistClientSettingsPatch,
     ).then(({ failedEnvironments, savedEnvironments, savedEnvironmentCount }) => {
+      if (failedEnvironments.length === 0) return true;
-      if (failedEnvironments.length === 0) return;
       toastManager.add({
         type: "error",
         title:
@@ -96,6 +96,7 @@
             : []),
         ].join("\n"),
       });
+      return false;
     });
   };
 }
@@ -103,7 +104,8 @@
 export function useUpdateScopedSettings() {
   const { scope, environments } = useSettingsScope();
   const run = useRunScopedPlan();
+  return (patch: ScopedSettingsPatch): Promise<boolean> | void =>
-  return (patch: ScopedSettingsPatch) => run(planScopedSettingsPatch(scope, environments, patch));
+    run(planScopedSettingsPatch(scope, environments, patch));
 }
 
 export function useClearScopedSettings() {
@@ -111,7 +113,7 @@
   const run = useRunScopedPlan();
   return (keys: readonly ProjectScopedServerSettingKey[]) => {
     if (context === null) return;
+    void run(planScopedSettingsClear(context.scope, context.environments, keys));
-    run(planScopedSettingsClear(context.scope, context.environments, keys));
   };
 }
 
```

## knip.jsonc

Status: modified; +2; −1

```diff
@@ -22,8 +22,9 @@
       // Windows verify script with esbuild through a path knip does not trace, and
       // electron-builder loads the macOS signer by path.
       "entry": [
-        // These fixture scripts are injected into a browser page by path.
+        // Evidence fixtures are run by node or injected into a browser page by path.
         "fixtures/issue-16534/*.js",
+        "fixtures/issue-14743/*.{js,mjs}",
         "knip-schemas.ts",
         "mobile-native-client.ts",
         "setup-worktree.ts",
```

## scripts/fixtures/issue-14743/prepare-scoped.py

Status: added; +48; −0

```diff
@@ -1,0 +1,48 @@
+from pathlib import Path
+import hashlib,json,sys
+root=Path(__file__).resolve().parents[3]; out=root/'apps/web/src/evidence-14743'
+report_dir=Path(sys.argv[1]); report_dir.mkdir(parents=True,exist_ok=True)
+hook=(root/'apps/web/src/components/settings/useScopedSettings.ts').read_text()
+# Only imports at runtime boundaries change. Actual useUpdateScopedSettings and runner bodies are copied verbatim.
+hook=hook.replace('from "../../state/session"','from "./boundary"').replace('from "../../hooks/useSettings"','from "./boundary"').replace('from "../../state/server"','from "./boundary"').replace('from "../../state/use-atom-command"','from "./boundary"').replace('from "../ui/toast"','from "../components/ui/toast"').replace('from "./SettingsScopeContext"','from "./context"').replace('from "./scopedSettings"','from "../components/settings/scopedSettings"')
+(out/'scoped-hook.ts').write_text(hook)
+ctx=(root/'apps/web/src/components/settings/SettingsScopeContext.tsx').read_text()
+a=ctx.index('const SettingsScopeContext ='); b=ctx.index('export function SettingsScopeProvider')
+c=ctx[a:b].replace('const SettingsScopeContext =','export const SettingsScopeContext =')
+(out/'context.tsx').write_text('import {createContext,useContext} from "react";\n'+c+'\n'+ctx[ctx.index('export function useOptionalSettingsScope'):])
+(out/'boundary.ts').write_text('''export const serverEnvironment={updateSettings:{}};
+export const readEnvironmentScope=()=>true;
+export const useEnvironmentsWithScope=()=>new Set(['proof-a','proof-b']);
+export const useMergedSettings=(x,s)=>s?s(x):x;
+export const useMigrateModelPreferences=()=>{};
+export const persistClientSettingsPatch=()=>{throw Error('Unexpected client write')};
+export const useAtomCommand=()=>request=>window.runnerCommand(request);
+''')
+control=(out/'after.tsx').read_text(); a=control.index('function RetentionControl'); b=control.index('const logs=[]')
+control=control[a:b]
+(out/'runner.tsx').write_text('''import React,{useState,useRef,useLayoutEffect} from 'react';
+import {createRoot} from 'react-dom/client';
+import {createRouter,createRootRoute,RouterProvider} from '@tanstack/react-router';
+import {DEFAULT_SERVER_SETTINGS} from '@t3tools/contracts';
+import * as Cause from 'effect/Cause';
+import {Switch} from '../components/ui/switch';
+import {NumberField,NumberFieldDecrement,NumberFieldGroup,NumberFieldIncrement,NumberFieldInput} from '../components/ui/number-field';
+import {ToastProvider} from '../components/ui/toast';
+import {SettingsScopeContext} from './context';
+import {useUpdateScopedSettings} from './scoped-hook';
+import '../index.css';
+'''+control+'''
+const journal=[];const pending=[];let api={};
+window.proof={journal,pending,reset:()=>api.reset(),unavailable:()=>api.unavailable(),resolve:(id,ok)=>{const p=pending.find(p=>p.id===id);if(!p||p.settled)throw Error('Unknown or settled request '+id);p.settled=true;journal.push({ack:id,ok,at:new Date().toISOString()});let saved;if(ok)saved=api.publish(p.request);p.resolve(ok?{_tag:'Success',value:saved}:{_tag:'Failure',cause:Cause.fail(new Error('Controlled server command failure'))})}};
+function Control({value}){const update=useUpdateScopedSettings();return <section className="flex items-center justify-between gap-2 border p-4"><span>Delete inactive worktrees</span><RetentionControl label="Delete inactive worktrees" value={value} onChange={v=>{const result=update({storageCleanup:{worktreeAfterDays:v}});return result?.then(ok=>{journal.push({outcome:ok,at:new Date().toISOString()});api.refresh();return ok})}}/></section>}
+function App(){const [values,setValues]=useState([null,null]);const [two,setTwo]=useState(false);const [connected,setConnected]=useState(true);const [key,setKey]=useState(0);const [,refresh]=useState(0);
+const settings=i=>({...DEFAULT_SERVER_SETTINGS,storageCleanup:{...DEFAULT_SERVER_SETTINGS.storageCleanup,worktreeAfterDays:values[i],worktreeSettledAfterDays:null,browserArtifactsAfterDays:null,logsAfterDays:null,worktreeOnMerge:false,worktreeOnDelete:false,worktreeUnchanged:false},worktreeCleanup:null});
+api={refresh:()=>refresh(x=>x+1),reset:()=>{pending.length=0;journal.length=0;setValues([null,null]);setConnected(true);setKey(k=>k+1)},unavailable:()=>setConnected(false),publish:request=>{const i=request.environmentId==='proof-a'?0:1;const v=request.input.patch.storageCleanup.worktreeAfterDays;setValues(xs=>xs.map((x,j)=>i===j?v:x));return {...settings(i),storageCleanup:{...settings(i).storageCleanup,worktreeAfterDays:v}}}};
+window.runnerCommand=request=>new Promise(resolve=>{const id=pending.length+1;pending.push({id,request,resolve});journal.push({request:id,...request,at:new Date().toISOString()});refresh(x=>x+1)});
+const environments=(two?[0,1]:[0]).map(i=>({environmentId:i===0?'proof-a':'proof-b',label:i===0?'Displayed A':'Target B',connection:{phase:connected?'connected':'disconnected'},serverConfig:{settings:settings(i)}}));
+const context={scope:{kind:'all',environmentIds:environments.map(e=>e.environmentId)},environments};
+return <ToastProvider timeout={60000}><main className="p-8 space-y-6"><h1>Actual scoped settings runner — controlled command boundary</h1><p>Verbatim useUpdateScopedSettings / useRunScopedPlan, actual context and toast provider. No transport or disk persistence claim.</p><label>Two targets <input type="checkbox" checked={two} onChange={e=>setTwo(e.target.checked)}/></label><SettingsScopeContext value={context}><Control key={key} value={values[0]}/></SettingsScopeContext><button onClick={()=>api.reset()}>Reset seed</button><button onClick={()=>api.unavailable()}>Make plan unavailable</button><button onClick={()=>refresh(x=>x+1)}>Refresh log</button><button id="outside">Outside focus target</button><pre className="max-h-60 overflow-auto text-xs">{JSON.stringify({values,connected,journal},null,2)}</pre></main></ToastProvider>}
+const route=createRootRoute({component:App});const router=createRouter({routeTree:route});createRoot(document.getElementById('root')).render(<RouterProvider router={router}/>);
+''')
+(root/'apps/web/evidence-14743-runner.html').write_text('<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="root"></div><script type="module" src="/src/evidence-14743/runner.tsx"></script></body></html>')
+(report_dir/'source-equivalence.json').write_text(json.dumps({'hookOriginalSha256':hashlib.sha256((root/'apps/web/src/components/settings/useScopedSettings.ts').read_bytes()).hexdigest(),'hookBodyVerbatim':hook[hook.index('function useRunScopedPlan'):]==(root/'apps/web/src/components/settings/useScopedSettings.ts').read_text()[(root/'apps/web/src/components/settings/useScopedSettings.ts').read_text().index('function useRunScopedPlan'):],'contextDeclarationVerbatimExceptExport':True,'commandBoundary':'useAtomCommand controlled; readEnvironmentScope true; actual planner, persistence aggregation, hook, context and toast provider'},indent=2))
```

## scripts/fixtures/issue-14743/prepare.mjs

Status: added; +63; −0

```diff
@@ -1,0 +1,63 @@
+import * as NodeChildProcess from "node:child_process";
+import * as NodeFS from "node:fs";
+import * as NodePath from "node:path";
+const root = NodePath.resolve(import.meta.dirname, "../../..");
+// The harnesses import app modules relative to apps/web/src, so they are generated
+// there, where typecheck and reactCompilerCoverage.test.ts also read them. Remove
+// them once the browser capture is done.
+if (process.argv[2] === "--cleanup") {
+  NodeFS.rmSync(`${root}/apps/web/src/evidence-14743`, { recursive: true, force: true });
+  for (const name of NodeFS.readdirSync(`${root}/apps/web`))
+    if (/^evidence-14743-.*\.html$/.test(name)) NodeFS.rmSync(`${root}/apps/web/${name}`);
+  console.log("Removed issue-14743 component harnesses from apps/web.");
+  process.exit(0);
+}
+const home = process.argv[2];
+if (!home?.startsWith("/tmp/issue-14743-"))
+  throw new Error("Use a disposable /tmp/issue-14743- home");
+NodeFS.mkdirSync(`${home}/userdata`, { recursive: true });
+const cleanup = {
+  worktreeAfterDays: null,
+  worktreeSettledAfterDays: null,
+  worktreeOnMerge: false,
+  worktreeOnDelete: false,
+  worktreeUnchanged: false,
+  browserArtifactsAfterDays: null,
+  logsAfterDays: null,
+  worktreeKeepWhen: "uncommitted-changes",
+};
+NodeFS.writeFileSync(
+  `${home}/userdata/settings.json`,
+  JSON.stringify({ storageCleanup: cleanup, projectSettingsOverrides: {} }, null, 2),
+);
+NodeFS.mkdirSync(`${home}/project`, { recursive: true });
+NodeChildProcess.execFileSync("git", ["init", "--quiet", `${home}/project`]);
+for (const [name, sha] of [
+  ["before", "22840ab943b8418744b48223411e46ba4fdcef1a"],
+  ["after", "d28e64cfc1a2d17f12681a22b6487fbc05023821"],
+]) {
+  const src = NodeChildProcess.execFileSync(
+    "git",
+    ["show", `${sha}:apps/web/src/components/settings/StorageSettings.tsx`],
+    { cwd: root, encoding: "utf8" },
+  );
+  const control = src.slice(
+    src.indexOf("function RetentionControl("),
+    src.indexOf("const KEEP_WHEN_LABELS"),
+  );
+  const dir = `${root}/apps/web/src/evidence-14743`;
+  NodeFS.mkdirSync(dir, { recursive: true });
+  NodeFS.writeFileSync(
+    `${dir}/${name}.tsx`,
+    `import React,{useState,useRef,useLayoutEffect} from 'react';\nimport {createRoot} from 'react-dom/client';\nimport {Switch} from '../components/ui/switch';\nimport {NumberField,NumberFieldDecrement,NumberFieldGroup,NumberFieldIncrement,NumberFieldInput} from '../components/ui/number-field';\nimport '../index.css';\n${control}\nconst logs=[];let pending=[];let publish;let reset;window.proof={logs,pending,resolve:(id,ok,value)=>{const p=pending.find(p=>p.id===id);if(!p)throw new Error("Unknown pending request "+id);if(p.settled)throw new Error("Already settled request "+id);p.settled=true;logs.push({ack:id,ok,value,at:new Date().toISOString()});if(value!==undefined)publish(value);p.resolve(ok)},broadcast:v=>{logs.push({broadcast:v});publish(v)},reset:()=>reset()};\nfunction App(){const [value,setValue]=useState(null);const [key,setKey]=useState(0);const [mode,setMode]=useState('success');const [log,setLog]=useState([]);publish=setValue;reset=()=>{setValue(null);setKey(k=>k+1);pending.length=0;logs.length=0;setLog([])};return <main className="p-8 space-y-6"><h1>Actual RetentionControl component harness — ${name}</h1><p>Controlled callback outcomes; NOT real scoped runner / persistence proof.</p><p>Source ${sha}; pending requests require explicit acknowledgement.</p><label>Callback mode <select value={mode} onChange={e=>setMode(e.target.value)}><option>success</option><option>pending</option><option>failure</option></select></label><section className="flex items-center justify-between border p-4"><span>Delete inactive worktrees</span><RetentionControl key={key} label="Delete inactive worktrees" value={value} onChange={v=>{const id=logs.filter(x=>'request' in x).length+1;logs.push({request:id,value:v,at:new Date().toISOString()});setLog([...logs]);if(mode==='success'){setValue(v);return Promise.resolve(true)}if(mode==='failure')return Promise.resolve(false);return new Promise(resolve=>pending.push({id,value:v,resolve}))}}/></section><button onClick={()=>reset()}>Reset seed</button><button onClick={()=>setLog([...logs])}>Refresh log</button><button>Outside focus target</button><pre>{JSON.stringify({authoritative:value,requests:log},null,2)}</pre></main>}createRoot(document.getElementById('root')).render(<App/>);`,
+  );
+  NodeFS.writeFileSync(
+    `${root}/apps/web/evidence-14743-${name}.html`,
+    `<html><head><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="root"></div><script type="module" src="/src/evidence-14743/${name}.tsx"></script></body></html>`,
+  );
+}
+console.log(
+  "Prepared fresh isolated home and exact-source component harnesses. Start dev with --home-dir " +
+    home +
+    ". After capture, run `node scripts/fixtures/issue-14743/prepare.mjs --cleanup`.",
+);
```

## scripts/fixtures/issue-14743/project-capture.js

Status: added; +163; −0

```diff
@@ -1,0 +1,163 @@
+async function runProject(variant, width, theme) {
+  const tabId = load("tab"),
+    url = load("projectUrl");
+  const actions = [];
+  const checkpoints = [];
+  const call = async (name, fn) => {
+    const at = new Date().toISOString();
+    const r = await fn();
+    actions.push({ name, at, completedAt: new Date().toISOString() });
+    if (r?.isError) throw Error(JSON.stringify(r));
+    return r;
+  };
+  const ev = async (expression) =>
+    (await call("evaluate", () => tools.mcp__t3_code__preview_evaluate({ tabId, expression })))
+      .structuredContent.value;
+  const nav = async (target) => {
+    await call("navigate " + target, () =>
+      tools.mcp__t3_code__preview_navigate({ tabId, url: target }),
+    );
+    await call("loaded", () =>
+      tools.mcp__t3_code__preview_wait_for({
+        tabId,
+        text: target.includes("/storage?") ? "Delete inactive worktrees" : "Applying settings for",
+      }),
+    );
+    await ev(load("runtime"));
+  };
+  const click = async (locator) =>
+    call("click " + locator, () => tools.mcp__t3_code__preview_click({ tabId, locator }));
+  const snap = async (name) => {
+    await call("neutral pointer", () =>
+      tools.mcp__t3_code__preview_hover({
+        tabId,
+        locator: 'nav[aria-label="Settings breadcrumb"]',
+      }),
+    );
+    await ev(
+      `(async()=>{await document.fonts.ready;for(let i=0;i<4;i++){const a=document.getAnimations().filter(a=>a.playState==='running'&&Number.isFinite(a.effect?.getComputedTiming().endTime));if(!a.length)break;await Promise.all(a.map(a=>a.finished.catch(()=>{})))}await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));return true})()`,
+    );
+    const state = await ev(
+      `({dark:document.documentElement.classList.contains('dark'),theme:localStorage.getItem('t3code:theme'),follow:localStorage.getItem('t3code:theme-follow-system'),appearance:localStorage.getItem('t3code:theme-appearance-mode'),checked:document.querySelector('[role=switch][aria-label="Delete inactive worktrees"]')?.getAttribute('aria-checked'),input:document.querySelector('input[aria-label="Delete inactive worktrees in days"]')?.value,focus:document.activeElement?.getAttribute('aria-label'),scroll:Array.from(document.querySelectorAll('*')).filter(e=>e.scrollTop).map(e=>({tag:e.tagName,className:e.className,top:e.scrollTop})),writes:proofWrites,acks:proofAcks,events:proofEvents,themeLog:proofThemeLog})`,
+    );
+    if (state.dark !== (theme === "dark")) throw Error("Theme mismatch " + name);
+    const read = await ev(
+      `proofRpc('server.getSettings').then(d=>({exit:d.exit._tag,storageCleanup:d.exit.value?.storageCleanup,overrides:d.exit.value?.projectSettingsOverrides}))`,
+    );
+    const shot = await call("checkpoint " + name, () =>
+      tools.mcp__t3_code__preview_snapshot({ tabId, save: true }),
+    );
+    checkpoints.push({
+      name,
+      at: new Date().toISOString(),
+      ...shot.structuredContent,
+      state,
+      read,
+    });
+  };
+  await call("resize", () =>
+    tools.mcp__t3_code__preview_resize({
+      tabId,
+      mode: "freeform",
+      width,
+      height: width === 1440 ? 1000 : 844,
+    }),
+  );
+  await ev(
+    `localStorage.setItem('t3code:theme',${JSON.stringify(theme)});localStorage.setItem('t3code:theme-follow-system','false');localStorage.setItem('t3code:theme-appearance-mode',${JSON.stringify(theme)});true`,
+  );
+  await nav(url);
+  const reset = await ev(
+    `proofRpc('server.updateSettings',{patch:{enableProviderUpdateChecks:false,projectSettingsOverrides:{'proof-project':{worktreeCleanup:{mode:'custom',rules:{worktreeAfterDays:null,worktreeSettledAfterDays:null,worktreeOnMerge:false,worktreeOnDelete:false,worktreeUnchanged:false,worktreeKeepWhen:'uncommitted-changes'}}}}}}).then(d=>d.exit._tag)`,
+  );
+  if (reset !== "Success") throw Error("seed failed");
+  await nav(url);
+  const start = await call("recording start", () =>
+    tools.mcp__t3_code__preview_recording_start({ tabId }),
+  );
+  await snap("Off");
+  await click('role=switch[name="Delete inactive worktrees"]');
+  await snap("enable");
+  await click('input[aria-label="Delete inactive worktrees in days"]');
+  await call("select 8", () =>
+    tools.mcp__t3_code__preview_press({ tabId, key: "a", modifiers: ["Control"] }),
+  );
+  await snap("enable-selected");
+  await call("replace with 30", () =>
+    tools.mcp__t3_code__preview_type({
+      tabId,
+      locator: 'input[aria-label="Delete inactive worktrees in days"]',
+      text: "30",
+      clear: true,
+    }),
+  );
+  await call("commit Enter", () => tools.mcp__t3_code__preview_press({ tabId, key: "Enter" }));
+  await click('role=heading[name="Worktrees"]');
+  await ev("proofWait30()");
+  await ev(
+    `(async()=>{const r=await proofRpc('server.getSettings');if(r.exit.value.projectSettingsOverrides['proof-project'].worktreeCleanup.rules.worktreeAfterDays!==30)throw Error('Not committed30');return true})()`,
+  );
+  await snap("committed30");
+  await nav(url);
+  await snap("reload30");
+  await click('role=switch[name="Delete inactive worktrees"]');
+  await snap("Off-before-leave");
+  await click('role=switch[name="Delete inactive worktrees"]');
+  await snap("enable-before-leave");
+  await nav(url.replace("/storage?", "/general?"));
+  await snap("General");
+  await nav(url);
+  await snap("loaded-return");
+  // Hold the loaded state across actual frames; animation frames preserve the raw transition recording.
+  await ev(
+    `new Promise(r=>{const end=performance.now()+1200;function tick(){if(performance.now()>=end)r(true);else requestAnimationFrame(tick)}tick()})`,
+  );
+  const stop = await call("recording stop", () =>
+    tools.mcp__t3_code__preview_recording_stop({ tabId }),
+  );
+  const result = {
+    variant,
+    width,
+    theme,
+    startedAt: start.structuredContent.startedAt,
+    video: stop.structuredContent,
+    actions,
+    checkpoints,
+  };
+  const results = load("results") || [];
+  results.push(result);
+  store("results", results);
+  return { variant, width, theme, video: result.video.path, checkpoints: checkpoints.length };
+}
+
+// Evaluate this file in functions.exec, after loading project-runtime.js into
+// store('runtime', source.trim()) and store('tab', the paired preview tabId).
+// Register once per fresh home; reuse that same stopped-server snapshot for
+// both commits. Copy these fixtures outside the checkout before baseline checkout.
+async function registerProofProject(home) {
+  if (!home.startsWith("/tmp/issue-14743-")) throw Error("Disposable home required");
+  const tabId = load("tab");
+  const evaluate = async (expression) => {
+    const result = await tools.mcp__t3_code__preview_evaluate({ tabId, expression });
+    if (result.isError) throw Error(JSON.stringify(result));
+    return result.structuredContent.value;
+  };
+  await evaluate(load("runtime"));
+  const payload = {
+    type: "project.create",
+    commandId: "proof-project-create",
+    projectId: "proof-project",
+    title: "Retention proof",
+    workspaceRoot: home + "/project",
+  };
+  const result = await evaluate('proofRpc("projects.mutate",' + JSON.stringify(payload) + ")");
+  if (result.exit?._tag !== "Success") throw Error(JSON.stringify(result));
+  const descriptor = await evaluate('fetch("/.well-known/t3/environment").then(r=>r.json())');
+  const origin = await evaluate("location.origin");
+  store(
+    "projectUrl",
+    origin +
+      "/settings/storage?project=" +
+      encodeURIComponent(descriptor.environmentId + ":" + home + "/project"),
+  );
+}
```

## scripts/fixtures/issue-14743/project-runtime.js

Status: added; +87; −0

```diff
@@ -1,0 +1,87 @@
+(async () => {
+  window.proofEvents = [];
+  window.proofWrites = [];
+  window.proofAcks = [];
+  const oldSend = WebSocket.prototype.send;
+  WebSocket.prototype.send = function (data) {
+    let d;
+    try {
+      d = JSON.parse(data);
+    } catch {}
+    if (d?.tag === "server.updateSettings") {
+      proofWrites.push({ at: new Date().toISOString(), ...d });
+      if (!this.proofWatching) {
+        this.proofWatching = true;
+        this.addEventListener("message", (e) => {
+          let a;
+          try {
+            a = JSON.parse(e.data);
+          } catch {}
+          if (a?.requestId && proofWrites.some((w) => w.id === a.requestId)) {
+            proofAcks.push({ at: new Date().toISOString(), ...a });
+            window.dispatchEvent(new Event("proofAck"));
+          }
+        });
+      }
+    }
+    return oldSend.call(this, data);
+  };
+  let counter = 94000;
+  const socket = new WebSocket("ws://localhost:7763/ws?orchestrationProtocol=2");
+  await new Promise((r, j) => {
+    socket.onopen = r;
+    socket.onerror = j;
+  });
+  window.proofRpc = (tag, payload = {}) =>
+    new Promise((resolve, reject) => {
+      const id = String(++counter);
+      const h = (e) => {
+        const d = JSON.parse(e.data);
+        if (d.requestId === id) {
+          socket.removeEventListener("message", h);
+          resolve(d);
+        }
+      };
+      socket.addEventListener("message", h);
+      socket.send(JSON.stringify({ _tag: "Request", id, tag, payload, headers: [] }));
+    });
+  window.proofThemeLog = [
+    { at: new Date().toISOString(), className: document.documentElement.className },
+  ];
+  new MutationObserver(() =>
+    proofThemeLog.push({
+      at: new Date().toISOString(),
+      className: document.documentElement.className,
+    }),
+  ).observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
+  for (const type of ["click", "keydown", "focusin", "focusout"])
+    document.addEventListener(
+      type,
+      (e) =>
+        proofEvents.push({
+          at: new Date().toISOString(),
+          type,
+          key: e.key,
+          target: e.target.getAttribute?.("aria-label") || e.target.textContent?.slice(0, 50),
+        }),
+      true,
+    );
+  window.proofWait30 = () =>
+    new Promise((resolve) => {
+      const check = () => {
+        if (
+          proofAcks.some(
+            (a) =>
+              a.exit?.value?.projectSettingsOverrides?.["proof-project"]?.worktreeCleanup?.rules
+                ?.worktreeAfterDays === 30,
+          )
+        ) {
+          window.removeEventListener("proofAck", check);
+          resolve(true);
+        }
+      };
+      window.addEventListener("proofAck", check);
+      check();
+    });
+  return "installed";
+})();
```

## scripts/fixtures/issue-14743/seed-project.mjs

Status: added; +32; −0

```diff
@@ -1,0 +1,32 @@
+import * as NodeChildProcess from "node:child_process";
+import * as NodeFS from "node:fs";
+const home = process.argv[2];
+if (!home?.startsWith("/tmp/issue-14743-")) {
+  throw new Error("Use an isolated /tmp/issue-14743- home, with its server stopped");
+}
+const rules = {
+  worktreeKeepWhen: "uncommitted-changes",
+  worktreeAfterDays: null,
+  worktreeSettledAfterDays: null,
+  worktreeOnMerge: false,
+  worktreeOnDelete: false,
+  worktreeUnchanged: false,
+};
+NodeFS.mkdirSync(`${home}/userdata`, { recursive: true });
+NodeFS.mkdirSync(`${home}/project`, { recursive: true });
+NodeChildProcess.execFileSync("git", ["init", "--quiet", `${home}/project`]);
+NodeFS.writeFileSync(
+  `${home}/userdata/settings.json`,
+  JSON.stringify(
+    {
+      storageCleanup: { ...rules, browserArtifactsAfterDays: null, logsAfterDays: null },
+      enableProviderUpdateChecks: false,
+      projectSettingsOverrides: { "proof-project": { worktreeCleanup: { mode: "custom", rules } } },
+    },
+    null,
+    2,
+  ),
+);
+console.log(
+  `Start vp run dev --home-dir ${home}, pair the collaborative preview, then register the project through project-capture.js.`,
+);
```
