Skip to content

Merge#9

Merged
MusicMaster4 merged 10 commits into
mainfrom
testing
Jul 6, 2026
Merged

Merge#9
MusicMaster4 merged 10 commits into
mainfrom
testing

Conversation

@MusicMaster4

Copy link
Copy Markdown
Owner

No description provided.

@MusicMaster4 MusicMaster4 merged commit 74035b8 into main Jul 6, 2026
4 checks passed
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Web Share Target support (allowing other apps to share files directly into WaterDrop), a text composer panel for sending typed/pasted text as a file, cancel-stalled-upload functionality, and a startup cleanup of leftover temp files. It also fixes a pre-existing gap where manifest.webmanifest and waterdrop-sw.js were not served by the server, which would have blocked PWA installation entirely.

  • Share Target: Both the service worker (intercepts the browser POST) and the server (fallback for non-SW environments) handle the /share-target route; text-only shares are synthesised into a .txt file on both paths.
  • Upload queue hygiene: syncStartedAt tracking enables stall detection for background fetch uploads; the stale-temp-files cleanup on startup prevents orphaned chunks from a previous server run.
  • New API surface: DELETE /api/uploads/:id cancels an in-flight or pending upload server-side, and the client wires this into the cancel button shown only for uploads stuck in the "syncing" state beyond the stall threshold.

Confidence Score: 4/5

Safe to merge; changes are well-structured, all new server paths have corresponding integration tests, and the two findings are minor.

The share-target, cancel-upload, and text-composer additions are logically sound. The pre-existing bug where manifest.webmanifest and waterdrop-sw.js were not served by the server is correctly fixed. Two small issues: a redundant application/octet-stream entry in the manifest accept list, and non-DELETE requests to /api/uploads/:id silently fall through instead of returning a 405. Neither affects correctness for normal usage.

No files require special attention. dropServer.js has the most surface area in this PR but its new paths are well-tested.

Important Files Changed

Filename Overview
src/server/dropServer.js Adds /share-target route, cancelUpload method, startup temp-file cleanup, and static-file serving for manifest/SW; logic is well-guarded and tested.
src/renderer/public/waterdrop-sw.js Adds fetch-event handler for share-target interception, syncStartedAt tracking, and a guard in markUploadQueued to prevent re-queuing non-syncing records.
src/renderer/src/App.jsx Adds text composer UI, cancelUpload flow, copyFileText helper, and isDesktop guards for background fetch; canCancelStalledUpload correctly gates the cancel button.
src/renderer/src/uploadQueue.js Adds STALLED_BACKGROUND_UPLOAD_MS constant, syncStartedAt field to all status-transition helpers, and exposes updatedAt/syncStartedAt in uploadRecordToView.
src/renderer/public/manifest.webmanifest Adds share_target definition with a broad accept list; application/octet-stream is redundant because application/* already covers it.
test/dropServer.test.js Adds five new integration tests covering share-target upload, error redirect, text-only share, cancel-in-flight, and startup temp cleanup — all new paths are exercised.
src/renderer/src/styles.css Adds styling for text-compose panel and upload-cancel button; no functional concerns.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as Mobile App
    participant Browser
    participant SW as Service Worker
    participant IDB as IndexedDB
    participant Server as WaterDrop Server

    Note over App,Server: Web Share Target flow (SW intercepts)
    App->>Browser: Share file/URL
    Browser->>SW: POST /share-target (multipart)
    SW->>SW: isShareTargetRequest() ✓
    SW->>SW: queueShareTargetUploads()
    SW->>IDB: putUploadRecord(status:"queued")
    SW->>SW: sync.register("waterdrop-upload-queue")
    SW-->>Browser: "303 → /?shared=N"
    SW->>Browser: postMessage(WATERDROP_UPLOAD_QUEUED)
    Browser->>SW: background sync fires
    SW->>SW: drainUploadQueue()
    SW->>Server: POST api/files/raw (blob)
    Server-->>SW: 201 OK

    Note over App,Server: Server fallback (no SW / desktop)
    App->>Browser: Share file/URL
    Browser->>Server: POST /share-target (multipart)
    Server->>Server: handleShareTarget()
    Server->>Server: "handleUpload(redirectTo="/")"
    Server->>Server: buildSharedTextUpload() if text-only
    Server->>Server: store.addFromTemp()
    Server-->>Browser: 303 → /

    Note over App,Server: Cancel stalled upload
    Browser->>Browser: canCancelStalledUpload() → show button
    Browser->>IDB: clearQueuedUpload(id)
    Browser->>SW: backgroundFetch.abort(id)
    Browser->>Server: DELETE /api/uploads/:id
    Server->>Server: store.cancelUpload(id)
    Server->>Server: rememberDeletedUploads + cancelInFlightUploads
    Server-->>Browser: "200 {cancelled:true}"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App as Mobile App
    participant Browser
    participant SW as Service Worker
    participant IDB as IndexedDB
    participant Server as WaterDrop Server

    Note over App,Server: Web Share Target flow (SW intercepts)
    App->>Browser: Share file/URL
    Browser->>SW: POST /share-target (multipart)
    SW->>SW: isShareTargetRequest() ✓
    SW->>SW: queueShareTargetUploads()
    SW->>IDB: putUploadRecord(status:"queued")
    SW->>SW: sync.register("waterdrop-upload-queue")
    SW-->>Browser: "303 → /?shared=N"
    SW->>Browser: postMessage(WATERDROP_UPLOAD_QUEUED)
    Browser->>SW: background sync fires
    SW->>SW: drainUploadQueue()
    SW->>Server: POST api/files/raw (blob)
    Server-->>SW: 201 OK

    Note over App,Server: Server fallback (no SW / desktop)
    App->>Browser: Share file/URL
    Browser->>Server: POST /share-target (multipart)
    Server->>Server: handleShareTarget()
    Server->>Server: "handleUpload(redirectTo="/")"
    Server->>Server: buildSharedTextUpload() if text-only
    Server->>Server: store.addFromTemp()
    Server-->>Browser: 303 → /

    Note over App,Server: Cancel stalled upload
    Browser->>Browser: canCancelStalledUpload() → show button
    Browser->>IDB: clearQueuedUpload(id)
    Browser->>SW: backgroundFetch.abort(id)
    Browser->>Server: DELETE /api/uploads/:id
    Server->>Server: store.cancelUpload(id)
    Server->>Server: rememberDeletedUploads + cancelInFlightUploads
    Server-->>Browser: "200 {cancelled:true}"
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/renderer/public/manifest.webmanifest:26-27
`application/octet-stream` is already covered by the `application/*` wildcard that precedes it, making this entry redundant. Browsers process the accept list by matching against each entry, so the duplicate is harmless but unnecessary clutter.

```suggestion
            "application/*",
```

### Issue 2 of 2
src/server/dropServer.js:834-841
Non-DELETE methods that match `/api/uploads/:id` fall through the `if (uploadMatch)` block without a response, eventually reaching a generic 404 or 405 handler further down (if one exists). Returning 405 here makes the API contract explicit and prevents accidental fall-through side-effects.

```suggestion
  const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
  if (uploadMatch) {
    if (req.method === "DELETE") {
      const result = await store.cancelUpload(uploadMatch[1]);
      sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
      return;
    }
    sendJson(res, 405, { error: "Method not allowed" });
    return;
  }
```

Reviews (1): Last reviewed commit: "Merge pull request #8 from MusicMaster4/..." | Re-trigger Greptile

Comment on lines +26 to +27
"application/*",
"application/octet-stream",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 application/octet-stream is already covered by the application/* wildcard that precedes it, making this entry redundant. Browsers process the accept list by matching against each entry, so the duplicate is harmless but unnecessary clutter.

Suggested change
"application/*",
"application/octet-stream",
"application/*",
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/public/manifest.webmanifest
Line: 26-27

Comment:
`application/octet-stream` is already covered by the `application/*` wildcard that precedes it, making this entry redundant. Browsers process the accept list by matching against each entry, so the duplicate is harmless but unnecessary clutter.

```suggestion
            "application/*",
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread src/server/dropServer.js
Comment on lines +834 to +841
const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
if (uploadMatch) {
if (req.method === "DELETE") {
const result = await store.cancelUpload(uploadMatch[1]);
sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
return;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Non-DELETE methods that match /api/uploads/:id fall through the if (uploadMatch) block without a response, eventually reaching a generic 404 or 405 handler further down (if one exists). Returning 405 here makes the API contract explicit and prevents accidental fall-through side-effects.

Suggested change
const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
if (uploadMatch) {
if (req.method === "DELETE") {
const result = await store.cancelUpload(uploadMatch[1]);
sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
return;
}
}
const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
if (uploadMatch) {
if (req.method === "DELETE") {
const result = await store.cancelUpload(uploadMatch[1]);
sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
return;
}
sendJson(res, 405, { error: "Method not allowed" });
return;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/dropServer.js
Line: 834-841

Comment:
Non-DELETE methods that match `/api/uploads/:id` fall through the `if (uploadMatch)` block without a response, eventually reaching a generic 404 or 405 handler further down (if one exists). Returning 405 here makes the API contract explicit and prevents accidental fall-through side-effects.

```suggestion
  const uploadMatch = relative.match(/^\/api\/uploads\/([a-f0-9-]+)$/);
  if (uploadMatch) {
    if (req.method === "DELETE") {
      const result = await store.cancelUpload(uploadMatch[1]);
      sendJson(res, result.ok ? 200 : 400, result.ok ? result : { error: "Invalid upload id" });
      return;
    }
    sendJson(res, 405, { error: "Method not allowed" });
    return;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant