# Project Qingtian / FRO Share File --- Development Report

## 2026-09-12

### Overall status

Today Case 2 (Client → FRO → QNAP Receive File) progressed from Phase 3
production verification into Phase 4 resumable-upload implementation and
real-browser testing.

### Phase 3 --- Closed and archived

Final classification:

**COMPLETE + PRODUCTION VERIFIED + REAL BROWSER VERIFIED**

Phase 3 provides: - Public branded QINGTIAN / FRO Receive File page. -
Multiple-file picker and drag/drop. - Sequential bounded-memory
streaming upload. - Per-file and aggregate progress. - Safe temporary
storage under `.fro-incoming`. - Atomic publication into
`Received/{drop_uuid}/`. - SQLite upload metadata and committed offset
tracking. - Token-scoped upload capability with no public file
listing/download/delete.

Real browser upload verification: - drop_id:
`4198fea5-028f-4d33-b231-bc1de3aec02b` - upload_id:
`6bd3962a-2282-445f-9d61-756c317e0832` - file:
`2179_08192026_PUTB1164_Subang_Bistari_title_Booking.rar` -
expected_size: `907282` bytes - committed_offset: `907282` bytes -
status: `Completed` - final filesystem size: `907282` bytes - `.part`:
absent - incoming directory: empty

Phase 3 archival regression: **33/33 passed, OK**.

Verified Phase 3 checkpoints: -
`app/receive.py.checkpoint_20260912_receive_phase3_verified` -
`app/config.py.checkpoint_20260912_receive_phase3_verified` -
`templates/receive.html.checkpoint_20260912_receive_phase3_verified` -
`tests/test_receive_uploads.py.checkpoint_20260912_receive_phase3_verified` -
`tests/test_receive_links.py.checkpoint_20260912_receive_phase3_verified`

### Tailscale production routing

A public-browser 404 was diagnosed as Tailscale stripping the matched
`/filedrop` prefix.

Final working mappings: - `/filedrop` → `http://127.0.0.1:8000` -
`/filedrop/receive` → `http://127.0.0.1:8000/filedrop/receive`

Funnel remains enabled on HTTPS port 443.

### Phase 4 --- Resumable Upload

Sequential resumable upload support was implemented and activated.

Key behavior: - New token-scoped upload-state endpoint. -
Server-authoritative `committed_offset`. - Resume only from the exact
server offset; stale/future/arbitrary offsets rejected. - Same-file
identity uses filename, exact size, browser `lastModified`, and SHA-256
fingerprint of bounded first/last 64 KiB slices plus metadata. -
IndexedDB persists resumable upload mapping. - `.part` size must match
SQLite committed offset. - One active writer per upload; concurrent
writer rejected. - Expired/revoked links cannot query or resume. -
Completed files remain immutable and are never overwritten.

Phase 4 tests: - Pre-change baseline: **33/33 OK** - Focused Phase 4:
**12/12 OK** - Full regression: **45/45 OK** - Post-production-restart
regression: **45/45 OK**

SQLite migration added: - `client_last_modified` - `file_fingerprint`

The previous Phase 3 completed upload remained intact after migration.

### Phase 4 browser issue and correction

The first Phase 4 browser attempt exposed a JavaScript syntax error in
`templates/receive.html`: `render()}}}catch(error)`

It was corrected to: `render()}}catch(error)`

A checkpoint was created:
`templates/receive.html.checkpoint_20260912_before_phase4_js_syntax_fix`

Only one unmatched `}` was removed. After correction: - JavaScript
parsed successfully. - Focused tests: **12/12 OK** - Full regression:
**45/45 OK** - No restart was required because Jinja reloaded the
template.

### Real-browser resumable upload evidence

Test drop: `1336cdb8-b282-4d7b-a7d6-6b749cfea8b6`

Test file: `DJI_20251220191452_0388_D.MP4`

Observed browser behavior: - Upload was intentionally interrupted. -
Reopening the same Receive URL and re-selecting the same local file
displayed: **Resume Available · 60.00 MB / 564.86 MB** - Overall
progress resumed at approximately **11%**, not 0%. - Upload was then
resumed and completed successfully.

This is strong real-browser evidence that Phase 4 recovered a non-zero
committed upload rather than simply initializing the UI at zero.

### Current stopping point

**Do not formally archive Phase 4 yet.**

Tomorrow's first task is a final **read-only server-side verification**
of the completed resumable upload: - SQLite upload_id and status. -
`expected_size == committed_offset`. - final NAS file size. - `.part`
absent. - `.fro-incoming` state. - confirm same upload_id and, if
retained logs permit, a non-zero `Upload-Offset` on the resumed request.

After that evidence is collected, Phase 4 may be classified as:

**COMPLETE + PRODUCTION VERIFIED + REAL BROWSER RESUME VERIFIED**

No further code changes are required tonight.
