Context
PlotLink has two existing API endpoints that the OWS app can use:
- `/api/upload-cover` — upload cover image (will require wallet signature after plotlink#1136)
- `/api/storyline/update` — update genre, language, NSFW flag, cover CID (requires wallet signature)
The OWS app already signs messages via OWS wallet (`createOwsAccount` in `app/lib/publish.ts`). We can reuse this to call both endpoints.
Depends On
Changes
1. Add Edit Panel UI
After a story is published, show an "Edit" button/section in the preview panel or story detail area. The edit panel should allow:
- Cover image: upload area (WebP/JPEG, max 500KB, 600×900px recommended), preview, remove
- Genre: dropdown selector (same as publish flow)
- Language: dropdown selector (same as publish flow)
- NSFW: checkbox toggle
Only show for published stories where the OWS wallet is the author.
2. Cover Image Upload Flow
IMPORTANT: Use `createOwsAccount` + `account.signMessage()` for signing — NOT raw `owsSignMsg` directly. The `createOwsAccount` wrapper (line 34-51 in publish.ts) normalizes the signature format to be compatible with viem's `recoverMessageAddress` on the PlotLink server.
```ts
import { createOwsAccount } from "./publish";
const account = createOwsAccount(walletName, walletAddress);
// Sign for upload
const timestamp = Date.now();
const uploadMessage = `PlotLink: Upload cover image\nTimestamp: ${timestamp}`;
const uploadSignature = await account.signMessage({ message: uploadMessage });
// Build FormData
const formData = new FormData();
formData.append("file", imageFile);
formData.append("message", uploadMessage);
formData.append("signature", uploadSignature);
// Upload to PlotLink API
const res = await fetch(`${PLOTLINK_URL}/api/upload-cover`, { method: "POST", body: formData });
const { cid } = await res.json();
```
3. Update Story Metadata Flow
```ts
// Sign for update (different message format — must match PlotLink API regex exactly)
const timestamp = Date.now();
const updateMessage = `PlotLink: Update storyline #${storylineId}\nTimestamp: ${timestamp}`;
const updateSignature = await account.signMessage({ message: updateMessage });
// POST to PlotLink update API
await fetch(`${PLOTLINK_URL}/api/storyline/update`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
storylineId,
signature: updateSignature,
message: updateMessage,
coverCid, genre, language, isNsfw
}),
});
```
Note: Upload and update use DIFFERENT message formats:
- Upload: `"PlotLink: Upload cover image\nTimestamp: {ts}"`
- Update: `"PlotLink: Update storyline #{id}\nTimestamp: {ts}"`
The update message format must match the regex `/^PlotLink: Update storyline #(\d+)\nTimestamp: (\d+)$/` exactly.
4. Add Helpers in publish.ts
Create reusable helpers:
```ts
export async function uploadCoverImage(
walletName: string,
walletAddress: `0x${string}`,
imageFile: File
): Promise // returns CID
export async function updateStoryline(
walletName: string,
walletAddress: `0x${string}`,
storylineId: number,
updates: { coverCid?: string | null; genre?: string; language?: string; isNsfw?: boolean }
): Promise
```
Both use `createOwsAccount` internally for signing.
5. Post-Publish Guidance Update
After publishing, update the guidance message to mention the edit panel is now available directly in the OWS app (instead of directing to plotlink.xyz).
Files
- `app/lib/publish.ts` — add `updateStoryline()` and `uploadCoverImage()` helpers
- `app/web/components/PreviewPanel.tsx` — add edit panel UI for published stories
- `app/web/components/StoriesPage.tsx` — wire up edit actions
- `app/routes/publish.ts` — add update route handler if needed
Acceptance Criteria
Context
PlotLink has two existing API endpoints that the OWS app can use:
The OWS app already signs messages via OWS wallet (`createOwsAccount` in `app/lib/publish.ts`). We can reuse this to call both endpoints.
Depends On
Changes
1. Add Edit Panel UI
After a story is published, show an "Edit" button/section in the preview panel or story detail area. The edit panel should allow:
Only show for published stories where the OWS wallet is the author.
2. Cover Image Upload Flow
IMPORTANT: Use `createOwsAccount` + `account.signMessage()` for signing — NOT raw `owsSignMsg` directly. The `createOwsAccount` wrapper (line 34-51 in publish.ts) normalizes the signature format to be compatible with viem's `recoverMessageAddress` on the PlotLink server.
```ts
import { createOwsAccount } from "./publish";
const account = createOwsAccount(walletName, walletAddress);
// Sign for upload
const timestamp = Date.now();
const uploadMessage = `PlotLink: Upload cover image\nTimestamp: ${timestamp}`;
const uploadSignature = await account.signMessage({ message: uploadMessage });
// Build FormData
const formData = new FormData();
formData.append("file", imageFile);
formData.append("message", uploadMessage);
formData.append("signature", uploadSignature);
// Upload to PlotLink API
const res = await fetch(`${PLOTLINK_URL}/api/upload-cover`, { method: "POST", body: formData });
const { cid } = await res.json();
```
3. Update Story Metadata Flow
```ts
// Sign for update (different message format — must match PlotLink API regex exactly)
const timestamp = Date.now();
const updateMessage = `PlotLink: Update storyline #${storylineId}\nTimestamp: ${timestamp}`;
const updateSignature = await account.signMessage({ message: updateMessage });
// POST to PlotLink update API
await fetch(`${PLOTLINK_URL}/api/storyline/update`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
storylineId,
signature: updateSignature,
message: updateMessage,
coverCid, genre, language, isNsfw
}),
});
```
Note: Upload and update use DIFFERENT message formats:
The update message format must match the regex `/^PlotLink: Update storyline #(\d+)\nTimestamp: (\d+)$/` exactly.
4. Add Helpers in publish.ts
Create reusable helpers:
```ts
export async function uploadCoverImage(
walletName: string,
walletAddress: `0x${string}`,
imageFile: File
): Promise // returns CID
export async function updateStoryline(
walletName: string,
walletAddress: `0x${string}`,
storylineId: number,
updates: { coverCid?: string | null; genre?: string; language?: string; isNsfw?: boolean }
): Promise
```
Both use `createOwsAccount` internally for signing.
5. Post-Publish Guidance Update
After publishing, update the guidance message to mention the edit panel is now available directly in the OWS app (instead of directing to plotlink.xyz).
Files
Acceptance Criteria