diff --git a/.claude/skills/changelog/watermark b/.claude/skills/changelog/watermark index c7c47ba88..10eee6c9e 100644 --- a/.claude/skills/changelog/watermark +++ b/.claude/skills/changelog/watermark @@ -1 +1 @@ -fae575c1935fd8d507f29a41941d0b440335728c +6d97de0d9948f26d544e7a2bc35a99f53c55cbf8 diff --git a/.claude/skills/docs/watermark b/.claude/skills/docs/watermark index 086d65cb6..108d4b049 100644 --- a/.claude/skills/docs/watermark +++ b/.claude/skills/docs/watermark @@ -1 +1 @@ -1945dc8e53ecba40f8f758f943bf2a7041f2124b +533785cd4c107cee673825847b1f8fe3d8d14dfe diff --git a/apps/fabro-web/app/install-app.test.tsx b/apps/fabro-web/app/install-app.test.tsx index b4f27f842..bff6a80c2 100644 --- a/apps/fabro-web/app/install-app.test.tsx +++ b/apps/fabro-web/app/install-app.test.tsx @@ -6,6 +6,10 @@ import InstallApp from "./install-app"; const INSTALL_ERROR_MESSAGE = "GitHub App setup failed before Fabro could save the app credentials. Continue again to retry the callback."; +const INSTALL_PREFILL = { + canonical_url: "https://fabro.example.com", + object_store_local_root: "/home/test/.fabro/storage/objects", +}; const SESSION_RESPONSE = { completed_steps: ["server", "object_store", "llm"], @@ -13,7 +17,7 @@ const SESSION_RESPONSE = { server: { canonical_url: "https://fabro.example.com" }, object_store: { provider: "local" }, github: null, - prefill: { canonical_url: "https://fabro.example.com" }, + prefill: INSTALL_PREFILL, }; type TestWindow = { @@ -201,7 +205,7 @@ describe("InstallApp", () => { slug: "fabro-brynary", allowed_username: "brynary", }, - prefill: { canonical_url: "https://fabro.example.com" }, + prefill: INSTALL_PREFILL, }; const fetchMock = mock((input: RequestInfo | URL) => { expect(String(input)).toBe("/install/session"); @@ -270,7 +274,7 @@ describe("InstallApp", () => { server: { canonical_url: "https://fabro.example.com" }, object_store: null, github: null, - prefill: { canonical_url: "https://fabro.example.com" }, + prefill: INSTALL_PREFILL, }), { status: 200, @@ -291,7 +295,7 @@ describe("InstallApp", () => { server: { canonical_url: "https://fabro.example.com" }, object_store: { provider: "local" }, github: null, - prefill: { canonical_url: "https://fabro.example.com" }, + prefill: INSTALL_PREFILL, }), { status: 200, @@ -345,7 +349,12 @@ describe("InstallApp", () => { "/install/object-store", "/install/session", ]); - expect(fetchCalls[1]?.init?.body).toBe(JSON.stringify({ provider: "local" })); + expect(fetchCalls[1]?.init?.body).toBe( + JSON.stringify({ + provider: "local", + root: INSTALL_PREFILL.object_store_local_root, + }), + ); await act(async () => { renderer?.unmount(); @@ -384,7 +393,7 @@ describe("InstallApp", () => { manual_credentials_saved: true, }, github: null, - prefill: { canonical_url: "https://fabro.example.com" }, + prefill: INSTALL_PREFILL, }), { status: 200, @@ -459,7 +468,7 @@ describe("InstallApp", () => { manual_credentials_saved: true, }, github: { strategy: "token", username: "octocat" }, - prefill: { canonical_url: "https://fabro.example.com" }, + prefill: INSTALL_PREFILL, }), { status: 200, diff --git a/apps/fabro-web/app/install-app.tsx b/apps/fabro-web/app/install-app.tsx index f0f8a1611..c04b5988e 100644 --- a/apps/fabro-web/app/install-app.tsx +++ b/apps/fabro-web/app/install-app.tsx @@ -84,6 +84,7 @@ type ObjectStoreProvider = "local" | "s3"; type ObjectStoreCredentialMode = "runtime" | "access_key"; type ObjectStoreForm = { provider: ObjectStoreProvider; + localRoot: string; bucket: string; region: string; credentialMode: ObjectStoreCredentialMode; @@ -120,6 +121,7 @@ export default function InstallApp() { const [finishState, setFinishState] = useState(null); const [timedOut, setTimedOut] = useState(false); const canonicalUrlInputRef = useRef(null); + const localRootInputRef = useRef(null); const bucketInputRef = useRef(null); const regionInputRef = useRef(null); const accessKeyIdInputRef = useRef(null); @@ -421,7 +423,13 @@ export default function InstallApp() { } backHref="/install/server" onSubmit={async () => { - if (objectStoreForm.provider === "s3") { + if (objectStoreForm.provider === "local") { + if (!objectStoreForm.localRoot.trim()) { + setSaveError("Enter the local object-store directory before continuing."); + focusInput(localRootInputRef); + return; + } + } else { if (!objectStoreForm.bucket.trim()) { setSaveError("Enter the S3 bucket before continuing."); focusInput(bucketInputRef); @@ -473,6 +481,8 @@ export default function InstallApp() { setObjectStoreForm((current) => ({ ...current, provider })); if (provider === "s3") { focusInput(bucketInputRef); + } else { + focusInput(localRootInputRef); } }} /> @@ -526,18 +536,22 @@ export default function InstallApp() { {objectStoreForm.credentialMode === "access_key" ? (
- + onChange={(event) => setObjectStoreForm((current) => ({ ...current, - accessKeyId: value, + accessKeyId: event.target.value, })) } + className={`${INPUT_CLASS} font-mono`} placeholder="AKIA..." + spellCheck={false} + autoComplete="off" + autoCapitalize="off" /> @@ -570,9 +584,31 @@ export default function InstallApp() { )}
) : ( -

- Fabro will keep using local disk for both SlateDB and run artifacts. -

+
+ + + setObjectStoreForm((current) => ({ + ...current, + localRoot: event.target.value, + })) + } + className={`${INPUT_CLASS} font-mono`} + placeholder="Local object-store directory" + spellCheck={false} + autoCapitalize="off" + /> + +

+ Fabro will store SlateDB and run artifacts under this directory. +

+
)} ) : location.pathname === "/install/github/done" ? ( @@ -806,14 +842,17 @@ function TokenEntryScreen({ -