# Continuity ledger (per-branch)

## Human intent (must not be overwritten)

<!--
Write 1–5 bullets capturing the human goal and motivation.
This section is intentionally stable: do not overwrite it when updating the ledger.
-->

- Add public SDK-side file upload support as the next step toward file inputs for the public Runs API.

## Goal (incl. success criteria)

- Add a public Studio endpoint to upload a file for an App via team secret-key auth and return `UploadedFileData`.
- Add matching `@giselles-ai/sdk` support (`client.files.upload`) with tests + docs.

## Constraints/Assumptions

- Keep implementation small and consistent with existing public endpoints (`/api/apps/[appId]/run`, `/api/apps/[appId]/tasks/[taskId]`).
- Do not leak `appId` existence to unauthenticated callers.
- Enforce a ~4.5MB upload cap (Vercel Functions body limit) and return a clear error message.

## Key decisions

- Return `{ file: UploadedFileData }` from the upload endpoint so callers have all metadata without a follow-up lookup.
- Extend the Runs API request body to accept optional file input (via `{ fileId }` reference or inline base64 with a size cap).
- Persist uploaded file metadata server-side so a later `{ fileId }` reference can be resolved into `UploadedFileData`.
- SDK `apps.list()` now derives `name` from `name`, `workspaceName`, or `workspace.name`, falling back to `"Untitled"` when missing.

## State

- Before: SDK supported only `client.app.run()` / `runAndWait()` and explicitly rejected file input.
- After: SDK supports uploading a file to an App’s workspace via `client.files.upload(...)`, returning an `UploadedFileData` payload.

## Done

- Added public upload route: `POST /api/apps/[appId]/files/upload` (multipart/form-data) with auth + rate limit and `UploadedFileData` response.
- Added route unit tests with mocked DB/auth/giselle dependencies.
- Added SDK `client.files.upload()` implementation + tests + README docs.
- Added a copy-pastable SDK README example to upload `./receipt.pdf` via `client.files.upload(...)`.
- Fixed CI flake in `POST /api/apps/[appId]/files/upload` test: avoid strict `File` instance comparison (because `request.formData()` may produce a new `File` with slightly different `lastModified`); assert stable file properties (name/type/size) + other args instead.
- Persist file metadata at upload time (`metadata.json`) so `{ fileId }` runs can reconstruct `UploadedFileData`.
- Extend Runs API: `POST /api/apps/[appId]/run` accepts `{ text, file?: { fileId } | { base64, name, type } }` and maps it to the App’s first `files` parameter.
- Add SDK support: `client.app.run()` / `runAndWait()` now accept `input.file` (fileId reference or inline base64) and send it to the Runs API; inline base64 is client-checked for a 3MB decoded limit.
- Update SDK README + mark the sdk plan task complete in `docs/plan/sdk.md`.
- Progressed `/api/giselle` deprecation rollout (keeping catch-all during migration):
  - Added `POST /webhooks/github` (signature verified, delegates to `giselle.handleGitHubWebhookV2`).
  - Added `GET /api/generations/[generationId]/generated-images/[filename]` (serves generated image blob).
  - Updated `internal-packages/workflow-designer-ui` generated-image URLs to `/api${content.pathname}`.
  - Updated `apps/studio.giselles.ai/app/robots.ts` to disallow `/api/generations/`.
  - Refactored Studio `app/giselle.ts` to export `githubWebhookCallbacks` and typed callbacks to satisfy `tsc`.
  - Fixed `pnpm check-types` failures and rebuilt SDK packages (`pnpm build-sdk`) to sync dist types.
  - Tweaked `packages/giselle/src/secrets/add-secret.ts` to omit `workspaceId` when undefined (typecheck strictness).
- Documented that `docs/github/create-webhook.md` is a GitHub reference and should not include Giselle-specific instructions (`docs/AGENTS.md`).
- Implemented SDK `client.apps.list()` with protocol App validation, tests, and README update.
- Added `@giselles-ai/protocol` dependency to SDK for App schema parsing.
- Marked the apps.list plan task complete in `docs/plan/sdk.md`.
- Extended SDK `apps.list()` to return `name` with each app and accept multiple name shapes (`name`, `workspaceName`, `workspace.name`) with a `"Untitled"` fallback.
- Updated SDK apps.list tests and README to reflect the name return.
- Removed Studio `/api/giselle` catch-all route and cleaned up Studio leftovers (basePath constant, robots disallow, proxy matcher exception).
- Updated Studio playground file attachment previews to use an optional `basePath` prop (no `/api/giselle` dependency).
- Updated `apps/studio.giselles.ai/lib/internal-api/AGENTS.md` to English.
- Added workspace authorization to internal-api secrets: delete requires workspace-bound secret and membership; getWorkspaceSecrets checks membership.
- Translated the internal-api secrets comment to English in `apps/studio.giselles.ai/lib/internal-api/secrets.ts`.

## Now

- Local checks completed (format/build-sdk/check-types/tidy/test).
- Studio `/api/giselle` catch-all removal is complete; remaining work is any follow-up UI/endpoint polish.
- Internal-api secrets authorization implemented; tests not run in this change.

## Next

- Confirm `pnpm -F @giselles-ai/sdk test` and relevant Studio tests pass locally.
- Follow-up: add UI in `studio.giselles.ai/tasks` to make public API (SDK) origins obvious.
- Implement dedicated endpoints and remove the catch-all handler per `docs/plan/api-giselle-deprecation.md`.

## Open questions (UNCONFIRMED if needed)

- Should the upload endpoint also accept `workspaceId` explicitly, or remain App-scoped only?
- Should we store server-side metadata for uploads (beyond returning it in the response) to support later listing?
- Do we want a public API to fetch file metadata by `fileId`, or is `metadata.json` access only internal/server-side?

## Working set (files/ids/commands)

- `apps/studio.giselles.ai/app/api/apps/[appId]/files/upload/route.ts`
- `apps/studio.giselles.ai/app/api/apps/[appId]/files/upload/route.test.ts`
- `packages/sdk/src/sdk.ts`
- `packages/sdk/src/sdk.test.ts`
- `packages/sdk/README.md`
- `packages/sdk/package.json`
- `apps/studio.giselles.ai/app/api/apps/[appId]/run/route.ts`
- `packages/giselle/src/files/upload-file.ts`
- `apps/studio.giselles.ai/app/giselle.ts`
- `apps/studio.giselles.ai/app/api/giselle/[...next-giselle]/route.ts`
- `apps/studio.giselles.ai/app/basePath.ts`
- `apps/studio.giselles.ai/app/robots.ts`
- `apps/studio.giselles.ai/proxy.ts`
- `apps/studio.giselles.ai/app/(main)/playground/file-attachments.tsx`
- `apps/studio.giselles.ai/lib/internal-api/secrets.ts`
- `docs/plan/api-giselle-deprecation.md`
- Tests: `pnpm -F @giselles-ai/sdk test`, `pnpm -F studio.giselles.ai test`
